使用vue已经有半年有余, 在各类正式非正式项目中用过, 开始专一于业务比较多, 用到如今也碰见很多由于理解不深致使的问题. 有问题就有找缘由的勇气, 因此带着问题搞一波.html
因此来整理了一下使用过程当中不注意或者不规范, 或者简化写法的奇技淫巧, 会结合文档的说明和实际的问题来看看源码, 问题:vue
看的源码版本为vuex2.3.1git
咱们使用vuex多是相似:github
import Vue from 'vue' import Vuex from 'vuex' import plugins from './plugins' Vue.use(Vuex) export default new Vuex.Store({ state: { todo: ["todo1", "todo2"] }, mutations: { mutationName(state, payload) { state.xxx = payload.xxx } }, actions: { actionName({ commit, dispatch }, payload) { commit(mutationName, payload) } }, modules: { catagories: { state: {}, mutations: {} } }, plugins })
使用vuex的方法为使用Vue.use
来installvuex
, 并new一个Store实例, 咱们来看一下vuex核心对象.vuex
line6: 本地vue变量, 在install时会被赋值, 以后会经过vue是否为undefined
来判断是否installapi
line10~14: 判断vuex是否被正确使用
line16~26: 获取options, state
能够和vue的component的data
同样为函数return一个对象, 会在这段代码中被parse
line28~36: store对象内部变量初始化
line39~46: 绑定commit和dispatch方法到自身
line54: 装载动做
line58: 装载响应动做
line61: 调用插件数组
this._committing = false
是否合法更新state的标识, 对象有方法_withCommit
是惟一能够改动_committing
的方法, 只有对象内部使用_withCommit
更新状态才是合法的, 在strict
模式下非法更新state会抛出异常.数据结构
this._modules = new ModuleCollection(options)
modules的cache, 直接把store的参数所有扔给了ModuleCollection
新建一个modules对象.app
点击跳转ModuleCollection对象来看分析.函数
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
其他的变量是新建了空的变量, 以后会在install模块的时候赋值.
在line39~46, 对dispatch和commit方法进行绑定, 使dispatch方法能够调用在Store对象上注册过的._actions
和._mutations
的方法.
dispatch方法在line108, 先兼容了参数的写法, 取到参数, 而后判断Store对象的_.actions
属性是否注册过, 若是注册过多个, 将会依次调用. 也就是若是type重复了也是会调用屡次的, 这个地方若是出错debug会很是困难. 暂时没有理解vuex此处设计的意图.
commit方法稍微多一点, 大致思路是同样的, 只是直接执行没有返回值, dispatch会返回执行结果. 另外在line95进行了subscriber的操做, 咱们暂且不知道subscriber的做用. 稍后再看.
首先来看参数:
function installModule (store, rootState, path, module, hot) // 调用 installModule(this, state, [], this._modules.root)
line255 根据path得到namespace, 作法是读取path的每一个模块, 若是namespaced为true则拼接, 例如path为['catagories', 'price', 'detail']
, 其中price
的namespaced为false, 其他为true, 那么得到的namespace为catagories/detail/
.
line258~260 把namespaced为true的module注册到_modulesNamespaceMap
.
line271的makeLocalContext
函数整理了namespace和type的关系. 在以后的三个module.forEachXxx
中, 都调用了registerXxx
, 最后的参数都是makeLocalContext
的返回值. 咱们来分析一下makeLocalContext
的做用:
被注册到全局的mutation/actiongetter实际的type相似于namespace1/namespace2/type
的形式, 而咱们在namespaced为true的module中调用的type只是:type
. 因此在namespace[true]的action中调用的全部dispatch
, commit
, getter
, state
都会被加上 path.join("/") + "/" 的type来调用到正确的方法.
根据注册的type, 我还获得了一个偏门结论: 能够经过设置type为namespace1/namespace2/type
来调用其余namespace的type(待测试), 由于他们是这样被注册的.
经过比较, install child module的时候是改了第三第四个参数: path
=> path.concat(key)
, module
=> module.getChild(key)
.
主要区别只是在line264~268, 与ModuleCollection的递归注册子module行为相似, 递归的path参数流程上只是多了一步把当前loop产生的对象挂到父节点上. 作法也是同样的, 把module名字(path)做为key, 套在父级state上. 也就是结构为:
state: { ...currentState, moduleName: { ...subState }, module2Name: { ...anotherSubState } }
在以前注册Mutation的时候vuex也是经过这个方法来试mutation得到嵌套过的state做为arguments[0]的.
store对象把传入的options放入了各个变量进行储存, 并提供了commit, dispatch等方法来调用和处理他们:
._modules
这里存放raw的modules, 未经处理的, 以module名字做为key的方式递归子module.
.state
这里也是以module名字做为key的方式递归储存传入的state
这里的entry指._actions
, ._mutations
, ._getters
. 他们的储存方式并无递归储存key, 而是平级的, 用/
来分割namespace来分辨type, 并在注册时把当前的entry绑定对应的state(经过getNestedState
方法).
问题: 若是在不一样module注册了相同type的mutation, 会发生什么?
回答: 会依次在本身的state中执行, 不会影响对方state, 可是会形成错误执行. (待测试). 因此应该在大的项目中尽可能使用namespaced[true]的方式, 而不是命名的方式.(可是也是能够利用/
来串namespace的, 因此本身type命名避免/
)
._modulesNamespaceMap
根据namespace为key来存放子module
这里会新建一个Vue实例并赋值给Store对象的._vm
属性, 把整个vuex的状态放进去. 并判断严格模式, 若是为严格模式会在非法改变状态的时候抛出异常.
这样整个构建动做已经完成了, 那么这个._vm
在何时用的, 请看下面的章节.
line64 state的getter方法, 会获取._vm
的vue实例的state. 因此咱们在vue代码中this.$store.state.xxx
获取到的东西就是这个vue的实例的数据.
line68 当直接set Store的state时报错, 只能经过设置._vm
来进行.
剩余的方法的是vuex的进阶用法, 是能够在使用时对vuex状态进行操做的方法, 详见文档
咱们来看下ModuleCollection
的构造方法.
调用了register
方法, 把参数的path设为根目录, runtime设为false.
register
方法一开始(l30)就判断了除state
外的属性的值是否为函数, 若不是则抛出异常.
line33 把module参数(仍是初始的options, 就是{state:{...}, mutations:{...}, actions: {...}}
这个)和runtime = false 来构建了Module
对象(稍后咱们看Module对象的构造)
line35 把ModuleCollection
的root私有变量设为了刚才使用初始options新建的Module
对象.
line42 若是初始options有modules这个属性, 就开始递归注册modules.
上面是register
的第一个参数path
为空, 也就是root节点的时候的流程, 在最后一部分(line42)根据是否当前注册的module含有modules属性来递归注册, 这部分咱们来看一下register的path参数的行为会把数据存成什么结构. 以概览部分的例子的参数为例(modules含有一个key为catagories
)来走一遍代码流程. (开始~)
被做为子module传入register
方法的参数应该为: path
(['catagories']), rawModule
(state: {},mutations: {}), runtime
(false).
注意到的是, 若是catagories
有同级module, 被传入的path
也是一个元素的数组, 也就是path的意思应该相似于从跟到当前module的层级, 对于兄弟节点是无感的.
这里的runtime
还没有明白用途, 多是在别处调用的. 注册流程应该runtime都为false.
一路看下来, 也是new了一个Module
对象, 可是没有走到line35把new出的对象放到root
变量里, 而是在line37~38去寻找当前module的父节点并把本身做为child, append到父节点上.
这里又脑补了一下数据结构: path.slice(0, -1)
是获取被pop()一下的path, path[path.lengt - 1]
是获取当前path的最后一个元素, 也就是当前正在被register的module的key. 因此以前对于path的数据结构判断是正确的.
这里的appendChild
和getChild
很明显是Module
对象的方法了, 咱们再继续看Module
对象的结构.
Module
对象最后来看Module
对象的构造~
接受2个参数, 一个rawModule
, 一个runtime
, 第一个参数是刚才相对于key为catagories
的value, 也就是相似{state: xxx, mutations: xxx, actions: xxx}
的options.
Module
的构造函数只是把参数拆分, 放入了本身的私有变量, 其中state
也接受函数, 并执行函数parse成对象存入私有变量. 其余变量都是原封不动储存的, 因此vuex给他起名为 rawModule 吧. 剩下那些方法都是顾名思义的, 语法上也简单, 没什么好看的.
那么这样Store对象的._modules
属性的数据结构已经很清楚了. 相似于(脑内):
{ // (ModuleCOllection实例) root: { // (Module实例) _rawModule: { state: {...}, mutations: {...}, // ...(全是options直接传入) }, state: {}, // 进行过parse的state, 若是是function会调用并赋值 _children: { catagories: { // (Module实例) }, anotherModule: { // (Module实例), 递归 } } } }
总结一点, 就是这里贮存的数据都是"raw"的.
全部的helper都用了两个wrap方法, 先来看下这两个方法的做用.
normalizeNamespace
由于helper是都接受两种传参方式:mapState(namespace, map)
/ mapState(map)
, 若是第一个参数为map时这个函数把namespace设为空字符串 , 而且检查namespace的最后一个字符是否是/
, 若是不是的话加上.
normalizeMap
咱们map的内容也接受两种语法:
[ "state1", "state2" ]
或者是
{ state1: state => state.state1, state2: state => state.state2 }
这个wrap函数会把两种形式都normalize为含有key
和val
属性的数组, 便于统一处理. 也就是上面个两个形式会转化为:
// Array like [{ key: "state1", val: "state1" }, { key: "state2", val: "state2" }] // Object like [{ key: "state1", val: state => state.state1 }, { key: "state2", val: state => state.state2 }]
这里作了2个处理:
makeLocalContext
的返回值)mapAction的val语法只接受字符串的, 因此先把val前借namespace, 变为: namespace/val
, 这样能符合在Store里注册的entry名.
而后检查了一下namespace是否被注册过, 也就是防碰撞, 而后把val做为type, 并把剩余参数带着dispatch Store里的action.
参考: