上一篇文章说的是如何阅读框架源代码,收到了“如果更详细一点就好了”的反馈,不如就以 Vuex 为切入点进行一次实践吧,不矫揉不造作,说走咱就走~~
一、前提
本文假定你已经对 Vue 的使用上有一定的概念,不要求轻车熟路(使用过 Vuex 当然是最好的),但至少要了解基本的事件绑定方式,以及 Mixin 的用法,官方文档从此去
二、Vuex 解决了什么问题
官方的说法:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
这里首先要搞清楚什么是状态,状态就是数据,也就是说: Vuex 提供了一套 Vue 应用统一的数据源管理模式,除了定义数据源,还定义了数据的管理模式
这其中,Store 所包含的两个核心部分 State 和 Actions 分别代表了数据源,和数据的管理(操作)模式,同时作为一个全局的 VM,其有效的协调了 Vue 各组件间的通信
三、Vuex 的设计思想
如果读 Vue 文档的时候足够留心,兴许你能在插件一节找到蛛丝马迹:
插件的功能包括,通过全局 mixin 方法添加一些组件选项,如:vuex
也就是说,Vuex 不过是 Vue 的一个插件,通过 Mixin 的方式给每个组件注入一个 $store 对象,由于每个组件的 $store 指向的是同一个 store 对象(后面通过详读代码可以知道,这个 $store 其实是一个 VM 对象),所以 store 是全局的,这就印证了之前在我们为什么需要 Vuex中的一个结论,Vuex 类似于一个事件总线
四、详读代码
通过 Mixin 注入 Store
从入口文件 index.js 开始,代码不多,可以直接贴出来
export default {
Store,
install,
version: '__VERSION__',
mapState,
mapMutations,
mapGetters,
mapActions
}
如果你一眼就看出这里的关键是 install,那么你应该领略到读源码先了解设计思想的独特魅力了,没错,作为 Vue 的 Plugin,install 方法就是入口
循着 install 方法进入 store.js,还是符合预期,这个方法主要干得是事情就是 mixin
export function install (_Vue) {
...
Vue = _Vue
applyMixin(Vue)
}
// auto install in dist mode
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
并且还有一个小细节,浏览器环境下并且 Vue 不为空的时候,引入 Vuex 之后是会自动注册的
具体来看看 mixin.js 这个文件,划重点(注意看注释):
// 通过钩子 init / beforeCreate 执行 vuexInit
const usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1
Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })
// 组件初始化的时候注入 $store
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
Store 对象
Vuex 的最佳实践中,一般这样使用(带着目标去阅读,效果更佳):
// create store
const store = new Vuex.Store({
actions: {
...
},
modules: {
...
}
})
import App from './comps/app.vue'
new Vue(Vue.util.extend({ el: '#root', store }, App))
我们需要新建一个 Store,在创建 Vue 实例的时候,作为参数传入,在上一节的 vuexInit 函数中,是从 this.$options 中取出 store 赋值给组件的 $store 的,如此,便能无缝联系上了
接下来的重点,就是 Store 这个类了,还是 store.js 这个文件,怀着入参为 ations 和 modules 的预期,来读 constructor 方法,倒是有一个语句是用来处理 modules 的
this._modules = new ModuleCollection(options)
但真的是寻寻觅觅寻不到从 options 中取出 actions 进行处理的方法,当然后面仔细阅读了 ModuleCollection 中的代码之后,才找到了答案,actions 参数也是在这里面提取的。毕竟让我纠结迷茫了良久,如果是我来写的话,我可能不会这么写,方法的命名需要有语义性,而且一个方法也应当只做一件事情
原则上为了尽快理清主流程,有些细节需要暂时略过(所以语义化的命名、合理的函数拆分,对阅读者来说是多么的重要),假设已经知道前面的步骤已经从 options 中读到了 actions 和 modules,那么下一个核心节点就是:
installModule(this, state, [], this._modules.root)
这一步再进行分解(注意看注释)
// 注册 mutation
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 注册 action
module.forEachAction((action, key) => {
const namespacedType = namespace + key
registerAction(store, namespacedType, action, local)
})
// 注册 getter (computed)
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 遍历子模块
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
出于篇幅以及希望阅读的同学亲自实践的目的,具体的注册方式这里不再展开
进入下一个重要环节 resetStoreVM,创建 VM,实现数据监听(注意看注释)
function resetStoreVM (store, state, hot) {
// bind store public getters
// getters 其实就是 computed
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// 创建一个 Vue 实例,作为 Store 的 VM
store._vm = new Vue({
data: {
$$state: state
},
computed
})
...
}
五、小结
至此,Vuex 的主流程代码基本上算是走了一遍,看似神奇,可是代码量并不大,还是那句话,希望阅读的同学能够按照这个套路自己走一遍
网友评论