vuex@3.x实现分析
本文最后更新于:2022年5月31日 早上
初始化阶段
安装 Vuex
在组件beforeCreate
的钩子中将 vuex 的Store
实例挂载在实例this.$store
上;
function vuexInit() {
const options = this.$options;
// store injection
if (options.store) {
this.$store = typeof options.store === "function" ? options.store() : options.store;
} else if (options.parent && options.parent.$store) {
// 如果是子组件中执行,也会指向最顶层的$store
this.$store = options.parent.$store;
}
}
实例化 Store
// 调用new Store()
export class Store {
constructor(options = {}) {
// options是vue实例化的options
const { plugins = [], strict = false } = options;
// store internal state
this._committing = false;
this._actions = Object.create(null);
this._actionSubscribers = [];
this._mutations = Object.create(null);
this._wrappedGetters = Object.create(null);
this._modules = new ModuleCollection(options); /* ⭐️初始化模块 */
this._modulesNamespaceMap = Object.create(null);
this._subscribers = [];
this._watcherVM = new Vue(); /*用一个新的vue实例作为vuexWatcher*/
// bind commit and dispatch to self
const store = this;
const { dispatch, commit } = this;
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);
};
// 根store就是_module.root
const state = this._modules.root.state;
/* ⭐️安装模块 */
installModule(this, state, [], this._modules.root);
// 初始化 store vm, 负责提供相应式
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state);
// 应用plugin
plugins.forEach((plugin) => plugin(this));
if (Vue.config.devtools) {
// devtool
devtoolPlugin(this);
}
}
}
初始化模块
从根module
开始,按照层级关系递归实例化所有的子module
为一整个树形结构的对象
export default class ModuleCollection {
constructor(rawRootModule) {
// 为根store模块开始执行register方法
this.register([], rawRootModule, false);
}
// 根据path去查找模块
get(path) {
return path.reduce((module, key) => {
return module.getChild(key);
/**
* getChild (key) {
* return this._children[key]
* }
*/
}, this.root);
}
register(path, rawModule, runtime = true) {
/// ...
const newModule = new Module(rawModule, runtime);
// 生成单个的module实例
if (path.length === 0) {
// path长度是0说明是根module
this.root = newModule;
} else {
// 否则添加为上级的子模块
const parent = this.get(path.slice(0, -1));
parent.addChild(path[path.length - 1], newModule);
/**
* addChild (key, module) {
* this._children[key] = module
* }
*/
}
// 为当前模块中的所有子模块递归调用register
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime);
});
}
}
}
安装模块
function installModule(store, rootState, path, module, hot) {
// 判断是否根模块
const isRoot = !path.length;
// 判断有没有命名空间
const namespace = store._modules.getNamespace(path);
// return namespace + (module.namespaced ? key + '/' : '')
// 注册到namespace map
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module;
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1));
const moduleName = path[path.length - 1];
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state);
});
}
const local = (module.context = makeLocalContext(store, namespace, path));
// 创建一个local上下文
// 对于有namespace的dispatch和commit会拼接上namespace
// 对于有namespace的getter有特殊处理,不是直接返回而是用Object.defineProperty设置了getter
/**
* function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {...}),
commit: noNamespace ? store.commit : (_type, _payload, _options) => {...})
}
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
*/
// 注册 各种方法
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key;
registerMutation(store, namespacedType, mutation, local);
});
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key;
const handler = action.handler || action;
registerAction(store, type, handler, local);
});
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);
});
}
实例化store.vm
- 建立
getter
和state
之间的联系 - 使用一个vue实例来存储
state
,访问了store._vm[key]
,也就是computed[key]
computed
的特性可以缓存并且state
变化的时候才会重新计算- 当
store.state
发生变化的时候,下一次再访问store.getters
的时候会重新计算。
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
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实例来存储state,访问state就是访问computed[key],
// 实现state改变的时候getter才改变,并且被缓存
store._vm = new Vue({
data: {
$$state: state
},
computed
})
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
// 严格模式会添加一个watch监测state的改变是否来自非vuex接口
// function enableStrictMode (store) {
// store._vm.$watch(function () { return this._data.$$state }, () => {
// if (process.env.NODE_ENV !== 'production') {
// assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
// }
// }, { deep: true, sync: true })
// }
if (oldVm) {
Vue.nextTick(() => oldVm.$destroy())
}
}
归纳
- 在
beforeCreate
钩子中混入vuexInit()
方法 - 通过递归判断是否有父组件,绑定
this.$store
指向到实例化的Store
- 从根
module
按照父子层级计算出完整的module
树 - 从根
module
开始梳理有namespace
的模块,会在他路径前拼接上namespace
,并保存下一个_modulesNamespaceMap
- 遍历模块中的所有子 modules,递归执行 installModule 方法,计算各自
state``getter``commit``dispatch
的访问路径 - 按照各个API的入口,将自定义的回调函数加入入口数组
getter
的特性会使用一个vue实例和computed
属性实现,只有getter依赖的state变化后getter的结果也会变化
vuex@3.x实现分析
http://yoursite.com/2022/03/14/[源码]vue2-vuex/