type
status
date
slug
summary
tags
category
icon
password

class Store

我们在使用 Vuex 的时候,会实例化 Store 类,并且将一些 options 作为参数传入。
我们来逐行看一下 Store 构造函数中的 constructor 代码。
判断 store.js 开始申明的 Vue 变量、window 不为 undefined (说明在浏览器环境下)、window 上有 Vue 变量、如果全部符合就执行 install 方法进行自动安装。
这么做主要是为了防止在某些情况下避免自动安装,具体情况请看 issues #731
然后在非生产环境执行,运行一些断言函数。
判断当前 Vue 变量, 在创建 store 实例之前必须调用 Vue.use(Vuex)
判断支持 Promise 对象, 因为 vuexregisterAction 时会将不是 Promise 的方法包装成 Promise , store 实例的 dispatch 方法也使用了 Promise.all,这也是为什么 action 支持异步调用的原因。
判断 this 必须是 Store 的实例。
断言函数的实现非常简单。
将传入的 condition 在函数内取非,为 true 就抛出异常。
接下来是从 options 解构出 plugins strict
plugins: vuex 的插件,数组,会在后面循环调用。
strict: 是否是严格模式,后面判断如果是严格模式的会执行 enableStrictMode 方法,确保只能通过 mutation 操作 state
接下来就是一些初始参数的赋值。
使用 calldispatch committhis 绑定到当前的 Store 实例上。
将解构出的 strict 变量赋值给 this.strict ,会在实例中使用。

init module

接下来会调用 installModule 安装 modules
第一次调用将 thisstatethis._modules.root.state)、空数组、this._modules.rootroot module)作为参数传入。
installModule 代码:
首先先根据 path 判断是否是 root,刚开始传入的 path 为空数组, 所以是 isRoot = true, 随后调用 ModuleCollection 类的 getNamespace 方法 根据 path 获取命名空间,因为 this._modulesModuleCollection 类的实例。
接着判断 module.namespaced 是否为 true, namespaced 是在每个 module 的配置中设置的,如果为 true 就将 namespace 赋值为 keymodule 为值存到 construction_modulesNamespaceMap 变量上。
helper.js 我们会用 getModuleByNamespace 获取 _modulesNamespaceMap 下对应命名空间模块。
root module 并且没有 hot 热更新,初始化的时候并没有进入 if 判断,注册子模块的时候才会进入 调用 getNestedState 方法取出父 modulestate
path 是一个数组,按模块嵌套排列,path.slice(0, -1) 传入除去自身的数组,就是父级。
getNestedState 返回一个三元表达式,如果有 path.length 就调用 reduce 方法取出对应嵌套的 state ,没有返回直接传入的 state
然后调用 store_withCommit 方法:
_withCommit 中执行传入的 fn 之前会将 this._committing 置为 true ,执行 fn 函数后,将 committing 回复恢复之前的状态。 这里主要是为了保证修改 state 只能通过调用 _withCommit,会调用 enableStrictMode 去检测 state 是否以预期的方式改变,我们在使用 vuex 中,就是通过 mutation 去改变 state
调用 makeLocalContext 方法:
makeLocalContext 主要用来初始化 dispatchgettercommitstate,通过 defineProperties 劫持 gettersstate
声明 noNamespace 变量判断是否有命名空间,然后创建 local 对象,改对象有两个属性 dispatch commit,它们的值分别是 2 个三元表达式,如果是没有命名空间的,dispatch 就赋值为 store.dispatch,有命名空间就拼上再返回,commit 也是一样的道理。
然后通过 Object.defineProperties 劫持 local 对象的 gettersstate
劫持 getters 的时候也是一个三元表达式,没有命名空间就将 localgetters 代理到 store.getters 上,有的话就将 localgetters 代理到 makeLocalGetters 函数的返回上。
我们来看一下 makeLocalGetters 方法:
makeLocalGetters 接收 storenamespace 作为参数。 首先申明 gettersProxy 变量,申明 splitPos 变量为命名空间长度,随后遍历 store.getters , 匹配命名空间,失败就 return ,成功往下执行。
然后取出命名空间后的 gettertype,使用 definePropertygettersProxylocalType 添加 get 方法,劫持 gettersProxylocalTypeget 返回 store 上对应的 getter,简单来说就是做了一个有命名空间情况下的代理。
makeLocalContext 函数最后会将 local 返回。
makeLocalContext 返回保存到 localmodule.context
下面就是循环注册 mutationactiongetter
调用 module 类的 forEachMutationforEachActionforEachGetter,取出对应的 mutationsactionsgetters 和回调函数作为参数。
来看看 registerMutation 方法:
通过 type 取出 store._mutations 上对应的 mutation,没有就穿透赋值为空数组,然后将 wrappedMutationHandler 函数 pushentry 数组中,函数的参数也就是 mutation 时候的参数。
函数中调用 callhandler 函数 this 指向 store, 并将 local.statepayload 作为参数传入,这样 _mutations[types] 储存了所有的 mutation
来看看 registerMutation 方法:
通过 type 取出 store._actions 上对应的 action,没有就穿透赋值为空数组,然后将 wrappedActionHandler 函数 pushentry 数组中,函数中使用 callhandler 指向 store, call 的第二个参数是 dispatchcommitgetters 等包装后的对象,所以我们可以在 commit 的第一个参数中解构出需要的属性。
payload 也就是额外参数,cb 回调函数倒是不怎么用到。
然后通过简易的 isPromise 方法判断 res 是否为 Promise,只是简单判断了 then 是是否为一个函数。
如果不是的话,调用 Promise.resolve(res)res 包装成一个 Promise
之后就是根据 _devtoolHook 判断当前浏览器是否有 devtoolHook 插件,应该是通过 Promise.catch 抛出错误,让 devtoolHook 捕获。
来看看 registerGetter 方法:
开始判断如果有相同 getter 就抛出异常, 没有的话就以 typekeywrappedGettervalue 储存到 store._wrappedGetters 对象上,每一个 getter 都是一个 function
循环注册 mutation action getter 后,只剩下最后一段代码:
调用 Module 类的 forEachChild 方法,并且将回调函数传入。
forEachChild 方法也调用了 forEachValue 遍历 _childrenkey 循环调用传入的 fn
_children 是在 ModuleCollection 类中通过嵌套模块的递归注册建立父子关系的。
最后递归调用 installModule 完成所以嵌套模块的安装,到此 installModule 方法结束。

resetStoreVM

resetStoreVM 主要用来重置 Vue 实例,实现响应式的 state computed
我们接着来看 resetStoreVM 方法:
函数开始就取出 store._vm,初始值是 undefind,会在后面用到。
循环 wrappedGetters 处理所有 getter
storegetters 赋值为空对象, 取出保存所有注册 getter_wrappedGetters 对象,申明 computed 对象。
接着循环 wrappedGetters 对象,将对应的 key 以及 fn 保存到 computed,这里的 fn 就是注册 getterwrappedGetter 函数。
然后通过 defineProperty 劫持 store.getterskey,代理到 store._vm[key]
保存 Vue.config.silent 变量,设置Vue.config.silent = true,取消 Vue 所有的日志与警告。
然后生成一个新的 Vue 实例,将 statecomputed 作为参数传入,恢复 Vue.config.silent。因为将 store.getterskey 代理到 store._vm[key],所以我们可以通过访问 this.$store.getters.key 访问到 store._vm[key]
根据 store.strict 判断是否是严格模式,是的话调用 enableStrictMode 方法。
enableStrictModestore 作为参数,调用 store._vm.$watch 方法,也就是 Vue 实例的 $watch 方法,监测 this._data.$$state 的变化,就是生成新的 Vue 实例的时候传入的 state,判断不是生产模式,调用断言,如果 store._committingfalse, 抛出异常,所以我们在使用 vuex 的时候,只能通过 mutation 方式改变 store
oldVm 的注销:
如果有 oldVm, 并且是热更新模式,将 oldVm._data.$$state 置为 null, 接下来调用 oldVm$destroy 方法注销 oldVm 实例。
插件的调用:
循环传入的 plugin 数组,循环调用,并将 this 传入。
调用 devtoolPlugin 方法:
constructor 的末尾会判断 Vue.config.devtools 是否为真,调用 devtoolPlugin 方法,并将 this 作为参数传入,devtoolPlugin 实现请看 插件 devtool 部分。
至此 Store 类的 constructor 部分结束,我们往下来看看 Store 类中的方法。
代理 state:
state 设置 get,访问 Store 实例的 state 的时候代理带 this._vm._data.$$state
state 设置 set,不能直接修改 state, 非生产环境抛出异常,提示你使用 store.replaceState 方法修改 state

commit

修改 Vuexstore 只能通过 mutation,我们通过 commit 调用 mutation
commit 接收 3 个参数,_type 就是 mutationtype_payload 就是传入的参数,_options 参数会在下面调用,貌似没什么用处,只是用来判断是否 console.warn
接下来调用 unifyObjectStyle 方法:
接收 commit 的三个参数,判断 type 如果是一个对象,并且有 type 属性,将 options 赋值为 payloadpayload 赋值为 typetype 赋值为 type.type
因为 vuex 允许对象风格的提交方式:
处理成这样的形式:
然后从 unifyObjectStyle 结构出 typepayloadoptions,将包装 typepayload 成一个对象赋值给 mutation 变量,申明 entry 变量从储存所有 mutationthis._mutations 取出对应 typemutation,没有对应 mutationreturn,如果在非生产环境,顺便抛出个异常。
接着调用 this._withCommit 方法,并将回调函数传入,这里会循环对应的 mutation,将 payload 参数传入并调用 handler 函数,需要注意的是 mutation 只能是是同步函数。
接着循环 _subscribers
_subscribers 是一个数组,循环调用里面的函数,并将 mutation this.state 传入。
最后判断非生产环境,并且 options.silent 为真,就抛出异常,提示 Silent option 已经删除,应该是和 vue-devtools 有关。

dispatch

通过 store.dispatch 方法触发 Action:
dispatch 接收 2 个参数,action type_payload 参数。
commit 一样调用 unifyObjectStyle 方法处理对象形式的 dispatch,解构出 type payload,申明 action 对象包装 type payload,申明 entry 变量从 this._actions 中取出对应的 action,没有对应 actionreturn,如果在非生产环境,顺便抛出个异常。
接着循环 _actionSubscribers
_actionSubscribers 是一个数组,循环调用里面的函数,并将 action this.state 传入。
commit 不同的是,dispatch 最后会返回一个 Promiseentry 是注册 action 时储存 wrappedActionHandler 函数的数组,在注册 action 时会将其包装成 promise,所以在 action 中支持异步操作,这里判断 entry 长度,如果是多个调用 Promise.all 方法,单个直接取第 0 个调用。

subscribe

订阅 storemutation
subscribe 中 调用了 genericSubscribe 方法,并将回调和 this._subscribers 传入,返回一个函数可以停止订阅。 会在每个 mutation 完成后调用,通常用于插件,在 pluginsdevtool.jslogger.js 都使用了。

genericSubscribe

genericSubscribe 接收 fn 函数和一个 subs 数组作为参数,首先判断如果在 subs 没有 fn 函数,就往 subs 数组 push fn ,最后 return 一个 function,这个函数会取到当前函数在 subs 中的下标,然后使用 splicesubs 中删除,也就是说调用返回的函数可以停止订阅。

subscribeAction

订阅 storeaction
subscribeAction 中 调用了 genericSubscribe 方法,并将回调和 this._actionSubscribers 传入,返回一个函数可以停止订阅。

watch

响应式地侦听 fn 的返回值,当值改变时调用回调函数。
判断非生产环境并且 getter 不是一个 function 抛出异常,随后会 return 一个函数,调用返回的函数可以停止监听,this._watcherVMconstructor 赋值成了一个 Vue 实例,其实就是基于 Vue 实例的 $watch 方法。

replaceState

替换 store 的根状态。
调用 _withCommit 并传入回调函数,在回调函数中会用传入的 state 替换当前 _vm._data.$$state

registerModule

使用 store.registerModule 方法注册模块:
registerModule 方法接收 path 路径,rawModule 模块,options 配置作为参数。
首先判断 path 如果为字符串,就转成字符串数组, 在非生产环境断言,path 必须为一个数组,path.length 必须大于 0。
然后调用 this._modules.register 进行注册模块,installModule 进行模块安装,resetStoreVM 重设 Vue 实例。

unregisterModule

卸载一个动态模块:
调用 this._modules.unregister 进行模块注销,调用 _withCommit,将回调函数传入。
回调函数会调用 getNestedState 方法取出父 modulestate,然后调用 Vue.delete 删除对应子模块,resetStore 进行 store 的重置,其他部分与 registerModule 一致。

resetStore

接收 store 和 是否 hot 作为参数, 将 store_actions_mutations_wrappedGetters_modulesNamespaceMap 置为 null
调用 installModule 重新安装模块,调用 resetStoreVM 重设 Vue 实例。

hotUpdate

开发过程中热重载 mutationmoduleactiongetter:
接收一个新的 newOptions,调用 this._modules.update 更新模块,然后调用 resetStore 重置 store
余下的方法基本都在上文讲述过,到此 class Store 结束。
vuex 问题总结vuex 工具函数
张小手
张小手
一个普通的干饭人🍚