本质是内部生成一个新的 Vue 实例,用于 state 的响应式及 getter 的依赖性
初始化
- install:通过 Vue.mixin 去合并配置到组件上。之后组件在 beforeCreate 阶段从 this.$options.parent 获取 this.$store 对象(和 vueRouter 一样的方式)
store 实例化
- ModuleCollection() 函数收集:从数据结构上看,模块的设计就是个树型结构
- this.register 来处理模块的父子关系。最后都维护在
this._modules.root上 - 结构:
root={state, _children: {mA1: {state, _children: {mA11: {state, _children}}}}}
- this.register 来处理模块的父子关系。最后都维护在
class ModuleCollection {
/**
* 按 path 遍历从 root 下获取对应的父节点。并返回该父节点下的 _children 属性
* path 就是树形的一个分枝 ['m', 'm1', 'm11']
* 注:path 为空时,直接返回 this.root 对象
*/
get(path) {
return path.reduce((module, key) => {
return module.getChild(key);
}, this.root);
}
// 递归执行
register(path, rawModule) {
const newModule = new Module(rawModule);
if (path.length === 0) {
// 根模块
this.root = newModule;
} else {
/**
* 树形递归遍历操作
* 根据 path 找到对应的父节点,把子模块放入 parent._children 内
* this.get() 和 parent.addChild() 是一起配合的完成的
* path.slice(0, -1) 去除最后一个(本身)返回 新数组
* 因为先执行 path.concat(key) 然后再走到这一步
*/
// 第一层 parent -> this.root
const parent = this.get(path.slice(0, -1));
// path[path.length - 1] 获取最后一个元素,即是当前子模块
parent.addChild(path[path.length - 1], newModule);
}
// 模块嵌套:设置了 options.modules 选项
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
// [] -> ['ma', 'ma1', 'ma11']
this.register(path.concat(key), rawChildModule);
});
}
}
}
/**
* 描述单个模块的类
* _rawModule 模块的配置内容
* _children 维护所有的 子模块
* state 模块定义的 state
*/
class Module {
constructor(rawModule) {
this._rawModule = rawModule;
this._children = {};
this.state = rawModule.state;
}
addChild(key, module) {
this._children[key] = module;
}
getChild(key) {
return this._children[key];
}
}
- installModule() 安装:将模块中的
state/getters/mutations/actions作初始化,都挂到 this 上响应的属性下。并将子模块的 state 都挂到 rootState 且设为响应式Vue.set(parentState, moduleName, module.state)构建树形结构的 state (重要)const local = makeLocalContext(store, path)来维护本地上下文对象 (重要)
/**
* 创建本地的 context
* 代理 state/getters 使访问时,访问的是 local 的数据
*/
function makeLocalContext(store, path) {
const local = {
dispatch: store.dispatch,
commit: store.commit,
};
// 往 local 对象新增 getters/state 属性,并只能获取值
Object.defineProperties(local, {
getters: {
get: () => store.getters,
},
state: {
get: () => {
/**
* 从根节点的 state 然后遍历找到对应的子 state
* reduce 使用逻辑和之前的不太一样
*/
let state = store.state;
return path.length
? path.reduce((state, key) => state[key], state)
: state;
},
},
});
return local;
}
- mutations/actions 维护在
this._mutations/_actions={ keyName: [ fn1, fn2, nameSpace/fn3 ] }内。然后配合commit/dispatch(type)获取对应的 key 后执行 (重要)- 同名的都在一个数组内,之后执行时也是遍历触发
- getters 维护在
this.wrappedGetters{}内,之后在 store._vm 时会挂到computed属性上 (重要) - 注意:除了 state 是树形结构的,其他的都是平级的对象通过 keyName 区分
// 将模块中的 state/getters/mutations/actions 作初始化,都挂到 this 上
// 并将子模块的 state 都挂到 rootState 设为响应式
function installModule(store, rootState, path, module) {
const isRoot = !path.length;
// 根的 state 在 resetStoreVM 动态化
if (!isRoot) {
const parentState = rootState;
const moduleName = path[path.length - 1];
/**
* 按照 path 路径将 childModule.state 添加响应式
* 结构 root.state.moduleName.state
* 最终都是挂在了 this._vm._data.$$state 下的
*/
Vue.set(parentState, moduleName, module.state);
}
// !重要的步骤。构造本地上下文对象
const local = makeLocalContext(store, path);
/**
* 下面把 getter/mutaion/action 都添加到 rootStore 下
*/
if (module._rawModule.actions) {
forEachValue(module._rawModule.actions, (handler, type) => {
registerAction(store, type, handler, local);
});
}
if (module._rawModule.mutations) {
forEachValue(module._rawModule.mutations, (mutation, key) => {
registerMutation(store, key, mutation, local);
});
}
if (module._rawModule.getters) {
forEachValue(module._rawModule.getters, (getter, key) => {
registerGetter(store, key, getter, local);
});
}
// 有子模块,递归调用。最终是个树形结构
forEachValue(module._children, (child, key) => {
installModule(store, rootState, path.concat(key), child);
});
}
/**
* 设定的 函数 需要读取到 state 参数,所以先把 state 放到内部
* 同一个 type 的 _mutations 可以对应多个方法
* !即使配置了 namespace 其实也是存储在 _mutations 中,只是 key 是带着 namespace 前缀的
*/
function registerMutation(store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = []);
entry.push((payload) => {
handler.call(store, local.state, payload);
});
}
- 初始化 store._vm:vuex 的响应式是通过 new Vue() 实现的
- 配置的 state 设置在 data 里实现响应式
- getters 配置在 computed 内,并代理 this.$store.getter
// 核心 实现响应式
function resetStoreVM(store, state) {
// 使得可以使用 this.$store.getter.xxx 方式
store.getters = {};
const wrappedGetters = store._wrappedGetters;
// !使用 computed 实现可缓存性
const computed = {};
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = () => fn();
Object.defineProperty(store.getters, key, {
enumerable: true,
get: () => {
// 获取的是对应的 computed: {} -> computed[key]
return store._vm[key];
},
});
});
store._vm = new Vue({
data: {
$$state: state,
},
computed,
});
}
API
数据获取
- state 通过
store.state.a.b.xxx按照模块嵌套的树形结构获取 - getter 通过
store.getter.xxx获取。在 resetStoreVM() 设置了代理 - mutaions/actions 通过
store.commit/dispatch(keyName)执行
class Store {
/**
* 这里执行的是个 高阶函数
* this._mutations[type] 是个函数并已经传递了 state
* this._mutations[type](payload) 传递了 payload
*/
commit(type, payload) {
// console.log('# commit', this, type, payload);
// return this._mutations[type](payload);
const entry = this._mutations[type];
if (!entry) return;
entry.forEach((handler) => handler(payload));
}
/**
* action 执行的是异步函数,所有使用 Promise.all()
*/
dispatch(type, payload) {
// console.log('# dispatch', type);
// return this._actions[type](payload);
const entry = this._actions[type];
if (!entry) return;
return entry.length > 1
? Promise.all(entry.map((handler) => handler(payload)))
: entry[0](payload);
}
}
map 糖果语法
本质是从this.$store.state/mutations/actions上获取对应的内容,生成一个临时对象在合并到当前的组件实例下
- mapState 返回一个 object 它的 val 为配置的处理函数,并把
this.$store.state作为参数传入 - mapGetters 返回内部包裹了
this.$store.getter[key]的函数 - mapMutations 返回包裹
commit.apply(this.$store, [val].concat(args))的函数
/**
* 通过 封装函数 调用组件实例自身上的 this.$store
* 然后把这个 函数 合并到 computed/methods 中
*/
export const mapState = (states) => {
const res = {};
normalizeMap(states).forEach(({key, val}) => {
res[key] = function mappedState() {
let state = this.$store.state;
return typeof val === 'function' ? val.call(this, state) : state[val];
};
});
return res;
};
export const mapMutations = (mutations) => {
const res = {};
normalizeMap(mutations).forEach(({key, val}) => {
res[key] = function mappedMutation(...args) {
const commit = this.$store.commit;
// this.$store.commit('xxx', payload)
// [val].concat(args) -> ['xxx', payload]
commit.apply(this.$store, [val].concat(args));
};
});
return res;
};