继续,这次尝试逐行理解Store类的东西

然后关于这次,一些“工具函数”都集中在util.js的这种做法,已经够我学的了。这是个很受用的做法。

一些简单的东西

1
2
3
4
5
6
if (process.env.NODE_ENV !== 'production') {
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
assert(this instanceof Store, `Store must be called with the new operator.`)
} // 断言函数在util,如果不满足前面的情况,后面以error 排出来
// (实际上就是想看有没有在vue环境中和有没有正确安装)
1
2
3
4
const {
plugins = [],
strict = false
} = options // 定义两个变量,在传入的对象中把这两个东西拿出来,字面意思
1
2
3
4
5
6
7
8
9
10
11
// 这里可能是会把整个vuex所有的内容都会规整到这些中
// store internal state
this._committing = false // commit switch( -> mutations)
this._actions = Object.create(null) // actions
this._actionSubscribers = [] // 应该像在dva中看到的订阅,没用过也没弄清楚应该怎么用
this._mutations = Object.create(null) // mutations
this._wrappedGetters = Object.create(null) // getters
this._modules = new ModuleCollection(options) // 分模块得到一个类
this._modulesNamespaceMap = Object.create(null) // 命名空间,上一次有讲到
this._subscribers = [] // 所有订阅者
this._watcherVM = new Vue() //watcher??应该跟vue的watcher有关?

开始跳着理解

store 与 操作绑定

1
2
3
4
5
6
7
8
9
// bind commit and dispatch to self
const store = this // 把store 是指向 this 的
const { dispatch, commit } = this // 所以 { dispatch, commit } = store ?
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}

虽然有注释,就是把 commit, dispatch 和store绑定到this上,然后{ dispatch, commit } = store了。接着class中的dispatch指向刚刚对象中dispatch的方法,通过call用法改变作用域调用。我们用到的this.$store.dispatch应该来源这里,下面同理

ModuleCollection

这里感觉很有意思,因为我这是第一次关注vuex的代码,我也不清楚命名空间等这些东西是什么时候加进去的,但我猜测应该是vue2.5之后的内容因为之前没在文档里见过这些东西。

字面意思,模块收集器。由于现在复杂度的关系,这部分应该先处理一下。把从vue的vuex得到的options,直接传入这个类,然后再进入register方法,接下来的工作就是不断的尾递归(?)得到整棵树。因为现在还有命名空间什么的,所以还有关于命名空间的判断和结合。

然后还有一些其他方法,比如热更什么的。

installModule

使用到的位置

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

Store开始初始化的其中第一个方法。

这个方法,字面含义就是安装模块,猜测应该是把store相关的东西先配置下来。

这里有一个点(函数太长了不浪费篇幅了)

1
const isRoot = !path.length

判断是否是根是通过path的长度来做的,如果没长度就是/了,很有意思,对于还没什么经验的我受益匪浅。

这个函数,需要5个参数,store, rootState, path, module, hot。最后一个应该也跟更新有关系吧?

thisclass中,所以指向是class Store的;stateconst state = this._modules.root.state得到,而_modules就上面提到的定义了;因为还在根所以没有path,最后传入刚刚各种尾递归得到的树。

接下来注册命名空间,也就是判断这次的store有没有用到命名空间,有就分成一张张网,摊开处理。

接下来会遇到一个函数

1
2
3
4
5
function getNestedState (state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
} // 如果不是根的话会被分配到这里,看需不需要拼凑state,就“三光属性”

非根注册应该是文档的这里吧,这里提到了模块可以只是局部注册,我们一般都直接在main.js完事儿了。

然后就是三种操作的模块遍历注册,用到的方法存在module.js,实则还是用到了util.js的遍历方法(通过回调函数返回回去,再次赞叹)

而注册的方法registerMutation && registerAction && registerGetter 就是我们用到的那些例如store.commit(type, payload)的方法。

registerAction的方法比其他的复杂得多,它要求handler传入dispatch, commit, getter, state等。显然他命中注定要干一些“脏乱差”的工作。由此也可以得到为什么actions的方法,第一个参数(type)可以传入{dispatch, commit, state}…的东西了。

题外话:之前在某个论坛看到一个问题,问在vuex中的actions为什么可以取到state,不会造成什么乱七八糟的问题么?我也不知道,问题的答案先留着。不过因为可以得到state,所以我们可以通过现有的state来判断或者操作,这不是更方便了么?

resetStoreVM

当一切都准备好了之后,怼进实例。

实际上前面也有提到,vuex是被vue当做专属插件进行安装的,在Vue实例环境中就可以通过this.$store摸到vuex。然后vuex就可以通过数据的改变来重新得到新的组件或者新的去促进生成得到新的dom。然后在上一篇有讲到那一堆辅助函数,实际上就是控制或者约束操作,但实质就是Vue.$store.dispatch等等等。

先是从store拿到vm作为旧的vm以作备份(当然如果不存在就不存在备份了),然后让Vue把这段操作定义为静默操作。

接着生成通过new Vue生成新的vm(假如有旧的也备份了,不会影响),恢复取消静默操作。(意思就是偷偷替换了vm)

至此新的视图已经更新完成,没有出意外的话,存在的刚刚备份过的旧vm就可以干掉的,执行销毁

_withCommit()

通篇是没有提到这个函数的,到这里提一下是因为,至此这个函数用了两次,最后一次是发生在刚刚resetStoreVM的最后,如果存在热更的情况下那里。篇幅不长,拿出来看一下

1
2
3
4
5
6
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}

毕竟状态管理,如果谁都能理这个状态,还需要管理干什么。这里的理解可以套入reducer协助理解,state有且仅有reducer可以修改,而vuex的mutations也就是做这份工作的地方。然后_withCommit的话,是个代理来的。

还是刚刚备份旧vm那个样子,先备份当前状态,然后把该状态转为true,据说是如果不暂时改变状态,严格vuex会认为这是非法操作,是禁止的。

封印解除之后,执行一下回调(各种需要破例更新的数据),然后再固着。

为什么这里需要备份状态换回去,而且这里是boolean,非黑即白的。实际上如果这个所谓的开关,在执行这里之前就是关闭的,那道理通过。如果在这之前,开关本来开着,这样你执行一次这里,就又把开关关回去的话,会影响到其他地方的正常工作,这不是一个“合格的秘书”。

刚刚最后那里的操作

1
2
3
store._withCommit(() => {
oldVm._data.$$state = null
})

就是假如这里是hot的话,就把旧vm的状态改成null,然后跟这个vm有关的watcher和计算都会被强行触发更新。通过这样让页面不刷新然后刷新dom。

最后一个问题:为什么明明this指向的是本体(Store),初始化的时候需要const store = this

回答:JavaScript那么牛逼的this,墙头草属性的,不找个需要固定的位置固定下来肯定会有机会被带偏的。