前言
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它借鉴了Flux、redux的基本思想,将共享的数据抽离到全局,同时利用Vue.js的响应式机制来进行高效的状态管理与更新。想要掌握了解基础知识可以查阅Vuex官网,本篇主要是对vuex 4.x版本的源码进行研究分析。
Vuex 源码架构
Vuex 源码架构主要由几个核心的类和方法组成:Store 类、 ModuleCollection 类、ModuleWrapper类、installModule 方法、makeLocalContext 方法
初始化装载与注入
Store 类是 vuex 中核心类之一,初始化创建通过 new Store()
,创建一个返回 store 实例的createStore
函数实现:
export function createStore<S>(options: StoreOptions<S>) {
return new Store<S>(options);
}
复制代码
通过 Vue3 的 provide/inject
api 实现 useStore
hook 和组件的注册(app.use(store)[store中间件挂载app上]时需要调用的方法):
import { App, inject } from "vue";
const injectKey = "store";
export function useStore<S>(): Store<S> {
return inject(injectKey) as any;
}
class Store<S = any> {
...
install(app: App) {
app.provide(injectKey, this);
}
...
}
复制代码
Store 类对象构造
Store 类作为 vuex 中的核心类,它通过定义一系列的方法和属性实现:
-
属性
-
_moduleCollection:模块集合对象
-
_modulesNamespaceMap:模块和命名空间映射
-
dispatch:访问actions异步方法的函数类型的属性
-
commit:访问mutations 方法的函数类型的属性
-
state:一个提供所有组件渲染的渲染数据【指响应式数据】的对象或函数【一般为对象】
-
_state:state 响应式数据的备份
-
-
方法
- commit:一个可以访问mutations对象中方法的方法
- dispatch: 一个可以访问actions对象中方法的方法
- reactiveState:把根模块中的state 变成响应式state的方法
- install:app.use(store)[store中间件挂载app上]时需要调用的方法
数据初始化、module树构造
根据new构造传入的options或默认值,初始化内部数据。其调用 new Store(options)
时传入的options对象,用于构造 ModuleCollection 类,下面看看其功能。
type Dispatch = (type: string, payload?: any) => any;
type Commit = (type: string, payload?: any) => any;
class Store<S = any> {
moduleCollection: ModuleCollection<S>;
mutations: Record<string, any> = {};
actions: Record<string, any> = {};
commit: Commit;
getters: Record<string, any> = {};
dispatch: Dispatch;
constructor(options: StoreOptions<S>) {
this.moduleCollection = new ModuleCollection<S>(options);
...
}
...
}
复制代码
ModuleCollection 主要将传入的options对象整个构造为一个 ModuleCollection 对象,实现原理可以查看下面 ModuleCollection 模块。详细实现可以查看源文件module-collection.js。
dispatch 与 commit 的实现
Store 类中 封装替换原型中的dispatch和commit方法,将this指向当前store对象。dispatch和commit方法具体实现如下:
class Store<S = any> {
...
mutations: Record<string, any> = {};
actions: Record<string, any> = {};
commit: Commit;
dispatch: Dispatch;
constructor(options: StoreOptions<S>) {
const store = this;
const { dispatch_: dispatch, commit_: commit } = this;
this.commit = function bundCommit(type: string, payload: any) {
commit.call(store, type, payload);
};
this.dispatch = function bundDispatch(type: string, payload: any) {
dispatch.call(store, type, payload);
};
...
}
commit_(type: string, payload: any) {
if (!this.mutations[type]) {
return console.error(`[vuex] unknown mutation type: ${type}`)
}
this.mutations[type](payload);
}
dispatch_(type: string, payload: any) {
if (!this.actions[type]) {
return console.error(`[vuex] unknown action type: ${type}`)
}
this.actions[type](payload);
}
}
复制代码
dispatch 的功能是触发并传递一些参数(payload)给对应 type 的 action。因为其支持2种调用方法,所以在 dispatch 中,先进行参数的适配处理,然后判断action type是否存在,若存在就逐个执行(注:上面代码中的this._actions[type]
以及 下面的 this._mutations[type]
均是处理过的函数集合,具体内容留到后面进行分析)。
ModuleWrapper 类实现
ModuleWrapper 类是封装和管理某一个模块的类,ModuleWrapper 类成员有:
- 属性
- _children:保存当前模块下子模块
- _rawModule:保存当前模块的属性
- state:保存当前模块的state的属性
- namespace:判断当前模块是否有命名空间的属性
- actionContext:一个可以向action、mutations 中的方法参数传递state、commit、dispatch 值的对象,此对象类型为ActionContext
- 方法
- addChild:添加子模块
- getChild:获取子模块
- forEachChild:遍历添加所有子模块到根 store 的方法
- forEachGetter:遍历添加所有子模块 getter 到根 store 的方法
- forEachMutation:遍历添加所有子模块 mutation 到根 store 的方法
- forEachAction:遍历添加所有子模块 action 到根 store 的方法
初始化 state、rawModule
class ModuleWrapper<S, R> {
children: Record<string, ModuleWrapper<any, R>> = {};
rawModule: Module<any, R>;
state: S;
namespaced: boolean;
constructor(_rawModule: Module<any, R>) {
this.rawModule = _rawModule;
this.state = _rawModule.state || Object.create(null);
this.namespaced = _rawModule.namespaced || false;
}
// 添加子模块
addChild(key: string, moduleWrapper: ModuleWrapper<any, R>) {
this.children[key] = moduleWrapper;
}
// 获取子模块
getChild(key: string) {
return this.children[key];
}
...
}
复制代码
mutations、actions 以及 getters 注册实现方法
// 注册对应模块的getters,供installModule 模块调用
forEachGetter(fn: GetterToKey<R>) {
if (this.rawModule.getters) {
console.log("object :>> ", this.rawModule.getters);
Util.forEachValue(this.rawModule.getters, fn);
}
}
// 注册对应模块的mutation,供installModule 模块调用
forEachMutation(fn: MutationToKey<R>) {
if (this.rawModule.mutations) {
Util.forEachValue(this.rawModule.mutations, fn);
}
}
// 注册对应模块的action,供installModule 模块调用
forEachAction(fn: ActionToKey<R>) {
if (this.rawModule.actions) {
Util.forEachValue(this.rawModule.actions, fn);
}
}
class Util {
static forEachValue(obj: any, fn: Function) {
Object.keys(obj).forEach((key) => {
fn(obj[key], key);
});
}
}
复制代码
子 module 注册实现方法
注册完了模块的actions、mutations以及getters后,提供installModule 子模块注册时递归调用自身,为子组件注册其state,actions、mutations以及getters等。
forEachChild(fn: ChildModuleWrapperToKey<R>) {
Object.keys(this.children).forEach((key) => {
fn(this.children[key], key);
});
}
复制代码
ModuleCollection 模块实现
ModuleCollection 主要用来封装和管理所有模块的类,其将传入的 options 对象整个构造为一个module对象,并循环调用 this.register([key], rawModule)
为其中的 modules 属性进行模块注册,使其都成为 module 对象,最后 options 对象被构造成一个完整的组件树。
其包含的属性和方法如下:
- 属性
- root:根模块属性
- 方法
- register:注册根模块和子模块的方法
- getNameSpace:循环递归获取命名空间方法
- getChild:获取子模块方法
初始化注册根模块
class ModuleCollection<R> {
root!: ModuleWrapper<any, R>;
constructor(rawRootModule: Module<any, R>) {
this.register([], rawRootModule);
}
...
}
复制代码
模块注册方法实现
register(path: string[], rawModule: Module<any, R>) {
const newModule = new ModuleWrapper<any, R>(rawModule);
if (!path.length) {
this.root = newModule;
} else {
const parentModule = this.get(path.slice(0, -1));
parentModule.addChild(path[path.length - 1], newModule);
}
if (rawModule.modules) {
const sonModules = rawModule.modules;
Object.keys(sonModules).forEach((key) =>
this.register(path.concat(key), sonModules[key])
);
}
}
复制代码
通过闯进来的 options 生成新模块,通过 path 判断是否是根模块,如果是根模块则将模块直接挂在 ModuleCollection 的 root 属性上,如果是不根模块通过 get 获取父级模块,并把模块添加到父模块上。
获取父模块 和 获取命名空间方法实现
get(path: string[]) {
const module = this.root;
return path.reduce((moduleWrapper: ModuleWrapper<any, R>, key: string) => {
return moduleWrapper.getChild(key);
}, module);
}
getNamespace(path: string[]) {
let moduleWrapper = this.root;
return path.reduce(function (namespace, key) {
moduleWrapper = moduleWrapper.getChild(key);
return namespace + (moduleWrapper.namespaced ? key + "/" : "");
}, "");
}
复制代码
module 安装(installModule 方法实现)
在上面 Store 类构造中,绑定 dispatch 和 commit 方法之后,需要执行模块的安装(installModule)。installModule 方法为模块注册方法。其主要是初始化根模块,递归注册所有子模块,收集此内所有模块getter、mutations、actions方法 ,就是把根模块个子模块state 对象中的数据和mutations、action、getters 对象中方法,全部收集到store对象中。
- installModule 主要完成:
- 判断所有模块中是否有重复的命名空间
- 收集当前模块的state,并保存到父级模块的state 中
- 调用makeLocalContext 方法,创建ActionContext 类型的对象
- 注册当前模块mutations 到store
- 注册当前模块 actions 到store
- 注册当前模块getter到store
- 迭代当前模块下的所有子模块时,并完成子模块mutations、actions、getters到store的注册。
初始化rootState
function installModule<R>(
store: Store<R>,
rootState_: R,
path: string[],
module: ModuleWrapper<any, R>
) {
const isRoot = !path.length;
const namespace = store.moduleCollection.getNamespace(path);
if (!isRoot) {
const parentState: Record<string, any> = getParentState(
rootState_,
path.slice(0, -1)
);
parentState[path[path.length - 1]] = module.state;
}
...
}
复制代码
判断是否是根目录,以及是否设置了命名空间,通过getParentState,(源码中为 getNestedState)方法拿到该 module 父级的 state,拿到其所在的 moduleName ,并将其 state 设置到父级state对象的 moduleName 属性中,由此实现该模块的state注册(首次执行这里,因为是根目录注册,所以并不会执行该条件中的方法)。通过getParentState 方法代码很简单,分析 path 拿到 state,如下。
function getParentState<R>(rootState: R, path: string[]) {
return path.reduce((state, key) => {
return (state as any)[key];
}, rootState);
}
复制代码
mutations、actions以及getters注册
初始化完成 rootState 之后,循环注册我们在options中配置的action以及mutation等。
下面分析代码逻辑:
// 注册对应模块的getters,供state读取使用
module.forEachGetter(function (getter: any, key: string) {
const namespaceType = namespace + key;
Object.defineProperty(store.getters, namespaceType, {
get: () => {
return getter(module.state);
},
});
});
// 注册对应模块的mutation,供state修改使用
module.forEachMutation(function (mutation: Mutation<R>, key: string) {
const namespaceType = namespace + key;
store.mutations[namespaceType] = function (payload: any) {
mutation.call(store, module.state, payload);
};
});
// 注册对应模块的action,供数据操作、提交mutation等异步操作使用
const actionContext = makeLocalContext(store, namespace);
module.forEachAction(function (action: Action<any, R>, key: string) {
const namespaceType = namespace + key;
store.actions[namespaceType] = function (payload: any) {
action.call(
{ commit: actionContext.commit, dispatch: store.dispatch },
payload
);
};
});
复制代码
子 module 安装
注册完了根组件的actions、mutations以及getters后,递归调用自身,为子组件注册其state,actions、mutations以及getters等。
module.forEachChild(function (child, key) {
installModule(store, rootState_, path.concat(key), child);
});
复制代码
8. makeLocalContext 方法实现
makeLocalContext 方法用来生成模块 ActionContext 类型的对象,对象属性主要包括dispatch、commit、state 三部分方法返回对象主要向 action、mutations 中的方法参数传递state、commit、dispatch 值。
function makeLocalContext<R>(store: Store<R>, namespace: string) {
const noNamespace = !namespace;
const actionContext: ActionContext<any, R> = {
commit: noNamespace
? store.commit
: function (type, payload) {
type = namespace + type;
store.commit(type, payload);
},
};
return actionContext;
}
复制代码
makeLocalContext 将action type 进行拦截,如果有命名空间则拼上空间名。
写在最后
本篇主要是对vuex4.0 源码的学习总结,demo 使用 typescript 编写,源代码仓库可以查看vuex-source-demo。如果本篇对你有所帮助,欢迎点赞收藏,顺便给个 star ~~。ps:【想要快速搭建自己的前端静态博客,欢迎查阅Vuepress 快速搭建博客--一款你值得拥有的博客主题。】