我看Vuex(二)

书接上回,上回书说了 vuex 的安装、以及 store 构造函数,下面我们来讲后面部分

收集 modules

vuex 允许我们自定义多个模块,防止应用的所有状态会集中到一个比较大的对象,导致 store 就变的臃肿了。

const moduleA = {state: { ... },mutations: { ... },actions: { ... },getters: { ... }
}const moduleB = {state: { ... },mutations: { ... },actions: { ... }
}const store = new Vuex.Store({modules: {a: moduleA,b: moduleB}
})store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
复制代码

这是如何实现的呢?

在store的构造函数里面有这样一段代码:this._modules = new ModuleCollection(options),他就是用来收集用户自定义的 modules,该函数位于 module/module-collection.js

现在假设我们的 modules 做如下设置:

store = {modules: {moduleA: {state: {},modules: {moduleC: {state: {},actions: {}}}},modulesB: {state: {},mutations: {// xxx}}}
}
复制代码

模块关系图如下:

来到 ModuleCollection 的构造函数,很简单,调用其 register 方法,并传入三个参数

register(path, rawModule, runtime = true) {if (process.env.NODE_ENV !== 'production') {assertRawModule(path, rawModule)}const newModule = new Module(rawModule, runtime)if (path.length === 0) {// 整个传入store 里的 option 为一个大的 module ,// 再对option 里的 modules 属性进行递归注册为 modulethis.root = newModule} else {const parent = this.get(path.slice(0, -1))parent.addChild(path[path.length - 1], newModule)}// register nested modulesif (rawModule.modules) {forEachValue(rawModule.modules, (rawChildModule, key) => {// 这里的 concat 并不改变原来的数组,所以如果是同级的 module ,那么他还是有着相同的父级this.register(path.concat(key), rawChildModule, runtime)})}}
复制代码

现在我们一步一步来分析。 先讲一下 register 里的对传入模块结构的断言,调用 assertRawModule(path, rawModule) 以确保 getters/mutations/actions 依次符合 函数/函数/(对象或者函数) 的结构,这部分代码并不难,但是作者的这种编码习惯非常值得学习,详见 assertRawModule

回到正题

第一次 调用 this.register([], rawRootModule, false)

此时传入 register 函数的 path空数组,rawModule 为最外层的 store 对象,即可以理解为 根module,runtime 为 false

接着调用 new Module(rawModule, runtime) 实例化这个 根module

constructor (rawModule, runtime) {this.runtime = runtime// 存储子 modulesthis._children = Object.create(null)// Store the origin module object which passed by programmer// 存储原始的这个传进来的 modulethis._rawModule = rawModuleconst rawState = rawModule.state  // 获取模块的 state// Store the origin module's statethis.state = (typeof rawState === 'function' ? rawState() : rawState) || {} // state 最终是一个对象}
复制代码

回到 register 函数,如果此时的 path 为 空数组,那么就将此模块设置为 整个状态树的 根模块,即this.root= 根 module,这里我们的 path=[],所以它是 根 module,不走 else

来到后面,判断该模块是否有 modules属性,即有子模块,有则继续循环注册子模块,我们这里有 moduleAmoduleB ,所以继续注册

// util.js
function forEachValue (obj, fn) {Object.keys(obj).forEach(key => fn(obj[key], key))}
复制代码

第二次调用 this.register(path.concat(key), rawChildModule, runtime)

此时传入 register 函数的 pathpath.concat(key),即 path =['moduleA'] ,rawModule 为 moduleA 对象,runtime 为 false

注:path.concat(key) 并不改变原始的 path,它返回一个新的数组,所以 根module的path栈 还是空,这一点很重要

继续重复第一步的步骤,不同的是,实例化完 moduleA 后,由于此时的 path =['moduleA'],所以它走 else

else {const parent = this.get(path.slice(0, -1)) parent.addChild(path[path.length - 1], newModule)}
复制代码

path.slice(0, -1) 返回 path 数组以外的其他元素,不改变原始数组,所以等价于 this.get([])

// 作用:获取当前的模块的父模块
// 传入的 path 是从根模块到父模块的一条链路
get(path) {return path.reduce((module, key) => {return module.getChild(key)}, this.root)}
复制代码

this.root为前面的 根 module,而 path 是空,所以 parent=根 module ,然后执行 parent.addChild(path[path.length - 1], newModule),此时获取 path 栈顶元素("moduleA")作为 key ,和 实例 moduleA 作为 value ,加入到 父模块(根 module)的子元素对象中

由于 moduleA 还有子模块,所以继续递归 子模块

第三次调用 this.register(path.concat(key), rawChildModule, runtime)

此时传入 register 函数的 pathpath.concat(key),即 path =['moduleA'] ,rawModule 为 moduleA 对象,runtime 为 false

继续上面步骤 来到 this.get,这是传入的参数是 ['moduleA'],即 moduleC 的父模块 moduleA。由于 根module 保存了 moduleA,所以通过这种类似于链的方式来获取 父模块,同理将 moduleC 加入 moduleA 的子模块对象中 至此,第一条链就讲完了,

返回到 根moduleforEachValue 循环中,这里我们讲到,他的 path 还是空,这就体现了 使用 concat 方法的好处与机智。 所以与处理 moduleA 的过程一模一样

第四次调用 this.register(path.concat(key), rawChildModule, runtime)

此时传入 register 函数的 pathpath.concat(key),即 path =['moduleB'] ,rawModule 为 moduleB 对象,runtime 为 false

终于将 this._modules = new ModuleCollection(options) 的过程分析完毕了 最终的 this._modules.root(不包括方法) 如下图所示

总的看下来挺佩服作者的思维以及处理方式的

看着挺长的了,其实就是多了几个循环过程的讲解,所以要不要再翻篇呢?呢?呢?????

回到 store.js 的构造函数

const state = this._modules.root.state  // 将 "根模块对象的 state" (即最外层store的state对象)赋予 state ,
复制代码

installModule(this, state, [], this._modules.root)

初始化根模块,并且递归注册子模块,并且收集所有模块的 getters

function installModule(store, rootState, path, module, hot) {
// hot 当动态改变 modules 或者热更新的时候为 true.const isRoot = !path.length // 判断是否是根模块const namespace = store._modules.getNamespace(path)  // 获取s使用的命名空间// register in namespace mapif (module.namespaced) { // 如果命名空间存在,就在store 对象中建立 namespace 到模块的映射store._modulesNamespaceMap[namespace] = module}// set stateif (!isRoot && !hot) { // 如果不是根模块以及 hot = false,这里我们是根模块,所以我们先放一放,跳到下一步const parentState = getNestedState(rootState, path.slice(0, -1))const moduleName = path[path.length - 1]store._withCommit(() => {Vue.set(parentState, moduleName, module.state)})}// 跳到下面先看看makeLocalContext,// 又跳!!!放心,,不用跳多远,也就是下一个 issue,,,抠鼻.gifconst local = module.context = makeLocalContext(store, namespace, path)  // 注册各个模块的 mutaations 方法到 store._mutations 中,每个type对应一个数组module.forEachMutation((mutation, key) => {const namespacedType = namespace + keyregisterMutation(store, namespacedType, mutation, local)})// 注册各个模块的 actions 到store._actionsmodule.forEachAction((action, key) => {const type = action.root ? key : namespace + keyconst handler = action.handler || actionregisterAction(store, type, handler, local)})// 注册各个模块的 getters 到store._wrappedGettersmodule.forEachGetter((getter, key) => {const namespacedType = namespace + keyregisterGetter(store, namespacedType, getter, local)})module.forEachChild((child, key) => { // 递归子模块installModule(store, rootState, path.concat(key), child, hot)})
}
复制代码

上面出现的有关函数由于排版以及篇幅原因,我放到了 我看Vue(三) 中。

总结下,installModule 都干了些神马:

  1. 获取命名空间
  2. 获取本模块的 context 属性,里面包含了本模块的 dispatch/commit/getter/state 等属性或方法
  3. 将 各个模块按照命名空间将 mutations/getters/actions 加入到全局的 _mutations /_wrappedGetters/_actions

接下来我们简单讲讲 在组件里面调用 dispatch、commit 的过程

dispatch/commit

commit(_type, _payload, _options) {// check object-style commit// 这里就是获取正确的 type / payload /optionsconst {type,payload,options} = unifyObjectStyle(_type, _payload, _options)const mutation = {type,payload}// 获取触发的type  对应的 mutationconst entry = this._mutations[type]if (!entry) {  // 如果不存在,给出警告if (process.env.NODE_ENV !== 'production') {console.error(`[vuex] unknown mutation type: ${type}`)}return}this._withCommit(() => {  // 由于entry是一个数组,所以逐个执行,并传入负载entry.forEach(function commitIterator(handler) {handler(payload)})})// 触发 订阅函数this._subscribers.forEach(sub => sub(mutation, this.state))if (process.env.NODE_ENV !== 'production' &&options && options.silent) {console.warn(`[vuex] mutation type: ${type}. Silent option has been removed. ` +'Use the filter functionality in the vue-devtools')}}dispatch(_type, _payload) {  // 基本和commit 一样
// xxx}复制代码

看完上面的代码,我有些疑问,这个 store.statestore.getter 是哪来的?

resetStoreVM(this, state)

function resetStoreVM(store, state, hot) {const oldVm = store._vm // 之前的 vue 实例// bind store public gettersstore.getters = {}  // 终于找到你 const wrappedGetters = store._wrappedGetters  // 前面说过的各个模块的 getters 集合const computed = {}forEachValue(wrappedGetters, (fn, key) => {// use computed to leverage its lazy-caching mechanismcomputed[key] = () => fn(store)    // 收集各个 getter,等会传入 computed ,以此做到响应式Object.defineProperty(store.getters, key, {get: () => store._vm[key],  // 因为利用了计算属性,所以各个 getter 就变成了 vue 实例的属性enumerable: true // for local getters})})// use a Vue instance to store the state tree// suppress warnings just in case the user has added// some funky global mixinsconst silent = Vue.config.silent  是否取消Vue 所有的日志与警告Vue.config.silent = true// 重新 new store._vm = new Vue({data: {$$state: state  // 这里虽然是 $$state,但是利用store.state时获取的就是它},computed})/* get state() {return this._vm._data.$$state}*/Vue.config.silent = silent// enable strict mode for new vmif (store.strict) {enableStrictMode(store)}if (oldVm) {if (hot) {// 解除旧vm的state的引用,以及销毁旧的Vue对象// dispatch changes in all subscribed watchers// to force getter re-evaluation for hot reloading.store._withCommit(() => {oldVm._data.$$state = null})}Vue.nextTick(() => oldVm.$destroy())}
}
复制代码

解决了 store.getter了,那么 store.state是如何来的呢? 还记不记得第一次让你跳的地方,没错就是 installModule

 if (!isRoot && !hot) { // 如果不是根模块以及 hot = false,这里我们是根模块,所以我们先放一放,跳到下一步const parentState = getNestedState(rootState, path.slice(0, -1))const moduleName = path[path.length - 1]store._withCommit(() => {Vue.set(parentState, moduleName, module.state)})}
复制代码

这里我们就来看 Vue.set(parentState, moduleName, module.state)。 它的作用是在父模块的state 属性上添加上本模块的state,还是按照一开始我们那种依赖关系来看:

这样我们就不难理解 getNestState 里面为什么可以如此获取 state

好了,vuex 核心大致的内容就是这些,后面在 我看Vuex(三) 中我会解释下其他一些函数的作用

原文地址: 我看Vuex(二)

如有不当,欢迎指出交流,谢谢 ^_^

转载于:https://juejin.im/post/5a8eb24e6fb9a0633c66209b

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Powered By Z-BlogPHP 1.7.3

Copyright www.122dh.com Rights Reserved.