Profession & Efficient

保持对技术的敬畏之心

通俗易懂Vuex源码导读2-Store的创建及模块树介绍

Store对象的创建

上一章,我们介绍了Vuex组件的安装过程,安装的最后,进行了对Store对象的引用(或对Store的创建)。本章将介绍Store(Vuex存贮类)的创建。

创建语句索引

  • 创建Vuex的语句为new Vuex.Store({...})
  • Vuex的入口文件为index.js,Store是index.js导出的Store类。
  • Store类是store.js中定义的,所以打开store.js文件,并找到Store的构造函数constructor。

Store的构造函数constructor

  • 【判断vuex是否已经被注入】兼容Vue组件的另一种注入方法,即将vue挂载在window对象上。自动检测window.Vue,如果有挂载,而且没有被注册过。则调用注册方法。

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

  • 【一些断言】断言是否被注册,断言是否支持promise,断言类有没有被正确的实例化。这些断言语句,在vue build出来以后。报错信息会被忽略掉。

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

    • 在webpack配置中,npm build时,会将 process.env.NODE_ENV 设置为true
    • 例如,我们找到使用Vue-cli 产生的 webpack配置的 Vue 项目,找到 /build/webpack.prod.conf.js 文件

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

    • 我们可以看到 const env = require(‘../config/prod.env’),即 env = { NODE_ENV: ‘”production”‘ }
    • 然后再通过 webpack.DefinePlugin({ ‘process.env’: env }), 将 env 变量注入到前端环境的变量中去
    • 从而将node中的变量(后端变量),注入到前端环境中。webpack.DefinePlugin的详细介绍参考这里
  • 各项数据容器的定义,部分重要容器,我们在第0章中详细介绍过,(不理解的同学,可回头细读)

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

    • this._committing = false // 提交状态标识,在严格模式时,防止非commit操作下,state被修改
    • this._actions = Object.create(null) // action 函数的数组的对象,保存所有action回调函数,
    • // 从null中创建对象,Object.create(null)没有继承任何原型方法,也就是说它的原型链没有上一层。从而定义纯粹的对象
    • this._mutations = Object.create(null) // mutation 函数的数组的对象,保存所有的mutations回调函数,
    • this._subscribers = [] // 订阅 mutation 操作的函数数组。里面的每个函数,将在 commit 执行完成后被调用,该功能常用于插件,与主功能无关
    • this._actionSubscribers = [] // 订阅 action 操作的函数数组。里面的每个函数,将在 action函数被调用前被调用,该功能常用于插件,与主功能无关
    • this._wrappedGetters = Object.create(null) // 保存 getter 函数的函数数组对象容器。
    • this._modules = new ModuleCollection(options) // 解析并生成模块树,通过树结构,保存配置文件内容
    • this._modulesNamespaceMap = Object.create(null) // 保存命名空间的模块对象,以便在辅助函数createNamespacedHelpers中快速定位到带命名空间的模块
    • this._watcherVM = new Vue() // 定义一个Vue对象,Vue类在调用Vuex安装函数,install时,被传递进来
  • ModuleCollection方法内部较为复杂,我们在这里介绍(为了更好地理解程序逻辑,请按顺序观看,看完具体内容再往下看呦~)

  • 获取 dispatch函数 与 commit函数,并复写该函数

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

    • 复写的作用,是将两个函数的 this 绑定到 Vuex 实例本身。防止 this 的指向被修改。
    • 因为这两个函数,可以通过 mapMutations 和 mapActions 辅助函数转化为 Vue 中的普通函数,这时 this 将指向 Vue 组件,而不是 Vuex 实例。所以在这里先将this锁定好。map辅助函数具体逻辑将在后续介绍,Map的使用例子
    • 两个函数暂时还没使用到,暂不进行介绍
  • 获取根模块的 state 变量索引, const state = this._modules.root.state

  • installModule,安装根模块(设置 commit , dispatch 函数的重载 : 根据是否设置命名空间,设置参数前缀。将 getter,commit,action 放到对应容器中保存起来。详情看这里
  • resetStoreVM,借助 Vue 的 watch 功能和 computed 功能,实现数据的响应式。详情看这里
  • Vuex 插件注册,有插件,则调用插件的函数,与主功能无关
  • 最后是Vue调试工具的相关处理,与主功能无关

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

  • 至此为止,Store对象的创建过程,也是Vuex的创建过程,已经全部介绍完了,对整体数据结构有了大概了解。

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

  • 紧接着将对照 Vuex 官方使用文档,针对 Vuex 在使用做的逻辑进行一一介绍

带注释源码,戳这里

源码注释还未完全整理,纯当阅读笔记,大神请勿较真

《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

我又不要脸地来骗赞了

《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

创建模块树 new ModuleCollection(options)

构造函数

《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

  • 形参rawRootModule,在Store中new ModuleCollection(options)传入,而options是Store的构造函数参数,即我们在生成Store实例,调用new Vuex.Store({...})时传入的参数,如
    {
    state,
    getters,
    actions,
    mutations,
    }
    
  • 形参名为rawRootModule,顾名思义,是将配置文件作为「原始根模块」

  • 构造函数调用的是register函数,即构造函数的作用是注册根模块

注册函数,register

  • 参数解析,path, rawModule, runtime = true
    • path是被注册模块的层级关系数组,保存当前模块及各个祖先模块名字,如[a,b,c,当前模块],则对应配置为{modules:a{modules:{b:modules:{c:modules:{当前模块}}}}}}
    • rawModule是模块的配置参数,即在定义模块时,开发者定义的模块配置内容,如模块的state,getters,actions等等
    • runtime表示是否是运行状态,在运行状态下,不能进行某些特定操作
  • 【assertRawModule】

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

    • 对模块的配置信息进行断言
    • 判断了getters,mutations,actions等配置项的数据类型是否正确。不正确则抛出异常
  • 创建模块对象 const newModule = new Module(rawModule, runtime),找到module.js文件找到其构造函数。

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

    • 构造函数参数
    • rawModule 是模块的配置信息对象,即在定义模块时,开发者定义的模块配置内容,如模块的state,getters,actions等等
    • runtime表示是否是运行状态,在运行状态下,不能进行某些特定操作
    • 具体操作,
    • const rawState = rawModule.state 从配置参数中,获取了state数据
    • this._children = Object.create(null) 定义了子模块对象容器。Object.create(null)是为定义一个纯粹的对象
    • this._rawModule = rawModule 保存配置参数本身
    • this.state = (typeof rawState === ‘function’ ? rawState() : rawState) || {} 解析state,可以是函数,运用工厂模式产生配置对象
  • 通过 path 数组判断当前模块是否为根模块

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

    • 如果是根模块,则通过变量 this.root 保存
    • 不是根模块,则通过 get方法 获取父模块对象
    • get方法传入的是路径数组,slice(0, -1) 是为了去除本模块名,保留所有祖先模块的路径,从而获取父模块
    • get函数内部,使用了数组的reduce方法

      《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

      • reduce函数有两个参数,第一个是回调函数,第二个是初始值
      • 回调参数,参数一是该函数上一次的执行结果(即return值)或者reduce函数的第二参数设置的初始值。参数二,是被顺序调用的数组中的元素
      • 该函数循环地对数组中的每个元素进行函数处理,并将运行结果当做参数给下一次执行做参数
      • reduce函数的第二个参数,初始值是this.root,是前面保存起来的,根模块配置
      • reduce内部执行的操作是module.getChild(key)
      • getChild调用的是模块对象(Module)中的 getChill 函数,返回模块的 _children 子模块容器的特定子模块

      《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

      • 整合而言,get的作用,是从根模块出发,根据给出的path数组,循环寻找子模块对象,
      • 由于调用时,去除了本层次,所以获取得的是本模块的父模块
    • 调用父模块的addChild方法,path[path.length – 1]获得的是本模块的名,newModule 是由本模块配置文件生成的模块对象
      • addChild 与 getChild 相对应,是往模块对象的 _children 子模块容器里面,以子模块名(path的元素)为key值,添加子模块
      • 对应了上一步,获取parent的方法
  • if (rawModule.modules),判断模块配置信息中,有没有子模块的配置

    《通俗易懂Vuex源码导读2-Store的创建及模块树介绍》

    • 如果有,则递归调用注册函数register本身
    • 传入的参数一,加上了子模块名的 patn 模块层级数组,rawChildModule是子模块配置文件,runtime是继承而来的运行时标识,暂时还没用上
  • 小结
    • 根据上面的步骤,完成了模块的递归注册,通过模块的_children容器保存子模块的链接(以子模块名为key,对应模块对象为value),形成了模块树结构
    • 每个模块对象,包含state,和_rawModule,原配置信息
    • 看完这里记得回到「Store的构造函数」观看后续操作哦

总目录

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注