Vuex 的使用用例
const store = createStore({
state: {},
mutations: {},
actions: {},
getters: {},
modules: {
childModule1: {
namespaced: true,
state: {},
mutations: {},
actions: {},
getters: {},
modules: {
grandsonModule: {
namespaced: true,
state: {},
mutations: {},
actions: {},
getters: {},
},
},
},
childModule2: {
namespaced: true,
state: {},
mutations: {},
actions: {},
getters: {},
},
},
});
在使用 Vuex 的过程中,经常会用到多个模块。那么 Vuex 是怎么注册和处理这些模块的呢?
modules 的注册与收集
class Store {
constructor(options) {
const store = this;
// 收集模块
store._modules = new ModuleCollection(options);
}
}
ModuleCollection
该类主要是用来处理嵌套的 modules,将用户的模块转化为 Vuex 内部的树结构
class ModuleCollection {
constructor(rootModule) {
this.root = null;
this.register(rootModule, []);
}
register(rawModule, path) {
const newModule = new Module(rawModule);
if (path.length == 0) {
// 表示为一个根模块
this.root = rawModule;
} else {
// 获取path中除了最后一项的前面几项,从root开始获取child
// 因为最后一项是代表当前的module
const parent = path.slice(0, -1).reduce((module, cur) => {
return module.getChild(cur);
}, this.root);
// 将当前的module挂载到path路径对应的module上
parent.addChild(path[path.length - 1], newModule);
}
// 如果该模块含有子模块,注册其子模块
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
// 注册子模块,并将模块的路径(模块的key)拼接上去
this.register(rawChildModule, path.concat(key));
});
}
}
}
Module 的实现
class Module {
constructor(rawModule) {
this._raw = rawModule;
this._children = {};
this.state = rawModule.state;
this.namespaced = rawModule.namespaced;
}
getChild(key) {
return this._children(key);
}
addChild(key, module) {
this._children[key] = module;
}
forEachChild(fn) {
forEachValue(this._children, fn);
}
forEachGetter(fn) {
if (this._raw.getters) {
forEachValue(this._raw.getters.fn);
}
}
forEachMutation(fn) {
if (this._raw.mutations) {
forEachValue(this._raw.mutations, fn);
}
}
forEachAction(fn) {
if (this._raw.actions) {
forEachValue(this._raw.actions, fn);
}
}
}
经过处理,module最终会变成下面这样的数据结构
store: {
_modules: {
root: {
_raw: rootModule,
state: rootModule.state,
_children: {
childModule1: {
_raw: childModule1,
state: childModule1.state,
_children: {
grandsonModule: {
_raw: childModule2,
state: grandsonModule.state,
_children: {},
},
},
},
childModule2: {
_raw: childModule2,
state: childModule2.state,
_children: {},
},
},
}
}
}
处理modules的 getters, mutations, actions
将所有模块的getters, mutations, actions全部注册在store实例上
store._wrappedGetters = Object.create(null);
store._mutations = Object.create(null);
store._actions = Object.create(null);
// 定义状态
const state = store._modules.root.state; // 根状态
//
installModule(store, state, [], store._modules.root);
function getNestedState(state, path){
return path.reduce((curState, key) => curState[key], state)
}
function installModule(store, state, path, module){
const isRoot = !path.length;
if(!isRoot){
}
// {double: function(state){return state.count * 2}}
module.forEachGetter((getter, key)=>{
store._wrappedGetters[key] = () => {
return getter(getNestedState(store.state, path))
}
})
// mutation是基于发布订阅模式的,可能会有多个回调函数
// {add: [mutation, mutation, ...]}
module.forEachMutation((mutation, key) => {
const entry = store._mutations[key] || (store._mutations[key] = [])
entry.push((payload) => { // store.commit("add", payload)
mutation.call(store, getNestedState(store.state, path), payload)
})
})
// actions mutation和action的一个区别, action执行后返回一个是promise
module.forEachAction((action, key) => {
const entry = store._actions[key] || (store._actions[key] = [])
entry.push((payload) => {
let res = action.call(store, store, payload)
// 判断res是否为一个promise
if (!isPromise(res)) {
return Promise.resolve(res)
}
return res
})
})
// 如果有子模块,安装子模块
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child)
})
}
命名空间的处理
通过namespaced来标识是否开启命名空间
在 ModuleCollection 的类中添加一个获取命名空间的方法getNamespaced
// [a,c] => a/c
getNamespaced(path){
let module = this.root;
return path.reduce((nameSpaceStr, key)=>{
module = module.getChild(key); // 获取子模块
return nameSpaceStr + (module.namespaced ? key + "/" : "")
},"")
}
在处理getters, mutations, actions时候,将命名空间添加上
const namespaced = store._modules.getNamespaced(path)
// store.getters["some/nested/module/foo"]
module.forEachGetter((getter, key) => {
store._wrappedGetters[namespaced + key] = () => {
// ...
}
})
// store.commit("some/nested/module/foo", payload)
module.forEachMutation((mutation, key) => {
const entry = store._mutations[namespaced + key] || (store._mutations[namespaced + key] = [])
// ...
})
// store.dispatch("some/nested/module/foo", payload)
module.forEachAction((action, key) => {
const entry = store._actions[namespaced + key] || (store._actions[namespaced + key] = [])
// ...
})
resetStoreState
代理getter,并处理state的响应式
function resetStoreState(store, state){
// 用data包裹一层是为了修改的时候方便 store._state.date = 'xxx' 不会影响数据的响应式
store._state = reactive({data: state})
const wrappedGetters = store._wrappedGetters;
store.getters = {};
forEachValue(wrappedGetters, (getter, key) => {
Object.defineProperty(store.getters, key, {
get: getter,
enumerable: true
})
})
}
开启严格模式
在options中可以指定是否开始严格模式,开启之后,只能在commit时候修改state,其他的修改都是不合法,会报错。
严格模式实现基本思路
- 在mutation之前添加一个状态,_commiting = true
- 调用mutation => 更改状态, 监控这个状态,如果当前状态变化的时候,_commiting = true,同步修改
- 修改完毕后 _commiting = false
- 如果_commiting = false,状态发生了改变,则为非法修改
class Store{
this.strict = options.strict || false;
this._commiting = false;
resetStoreState(store, state)
_withCommit(fn){ // 切片模式编程
const commiting = this._commiting;
this._commiting = true;
// 只有在fn中修改状态才是合法的操作
fn();
this._commiting = commiting;
}
commit = (type, payload) => {
const entry = this._mutations[type] || [];
this._withCommit(()=>{
entry.forEach(handler => handler(payload))
})
}
dispatch = (type, payload) => {
const entry = this._actions[type] || []
return Promise.all(entry.map(handler => handler(payload)))
}
}
function resetStoreState(store, state){
if(store.strict){
enableStrictMode(store)
}
}
function enableStictMode(store){
watch(()=>store._state.data, ()=>{
console.assert(store._commiting, "不能在mutation之外修改state")
}, {deep: true, flush: 'sync'})
}
useStore
export const storeKey = 'store'
import {inject} from 'vue'
// createApp().use(store, 'my')
class Store{
install(app, injectKey) {
// 全局暴露一个 变量,暴露的是store的实例
app.provide(injectKey || storeKey, this)
// Vue2.x Vue.prototype.$store = this
app.config.globalProperties.$store = this; // 增添$store属性 使得可以直接在模板中使用 $store.state.count
}
}
// vue 内部已经将这个些api导出来了
// const store = useStore("my")
export function useStore(injectKey = null) {
return inject(injectKey !== null ? injectKey : storeKey)
}