各位观众老爷,晚上好!今天咱们来聊聊 Vuex 的老底儿,扒一扒 Store
类的初始化过程,看看 Vuex 是怎么把 state
、getters
、mutations
、actions
这些宝贝疙瘩安排得井井有条的。 咱们尽量用大白话,加上点小幽默,保证大家听得懂、记得住。
开场白:Vuex 的本质是什么?
在深入源码之前,咱们先来明确一个概念:Vuex 其实就是一个状态管理容器,它把应用的状态集中管理起来,并且提供了一套规则来修改状态。可以把 Vuex 看作一个全局的“数据库”,专门用来存放和管理 Vue 应用的数据。
Store 类:Vuex 的心脏
Store
类是 Vuex 的核心,所有的状态管理逻辑都围绕着它展开。我们创建 Vuex 实例的时候,实际上就是在创建一个 Store
类的实例。
初始化过程:一步一个脚印
接下来,咱们就一步一个脚印,来剖析 Store
类的初始化过程。为了方便理解,咱们以 Vue 3 版本的 Vuex 为例,但 Vue 2 版本的思路也是大同小异,稍微改改就能用。
- 构造函数:Store 的诞生
首先,我们来看 Store
类的构造函数:
class Store {
constructor(options = {}) {
const {
state = {},
getters = {},
mutations = {},
actions = {},
plugins = [],
strict = false
} = options;
// 1. 严格模式
this._committing = false;
this.strict = strict;
// 2. 获取 state
this._state = typeof state === 'function' ? state() : state;
// 3. 初始化 mutations
this._mutations = Object.create(null);
this._actions = Object.create(null);
this._wrappedGetters = Object.create(null);
// 4. 模块化
this._modules = new ModuleCollection(options);
// 5. 安装模块
installModule(this, this.state, [], this._modules.root);
// 6. 初始化 getters
resetStoreVM(this, this._state);
// 7. 插件
plugins.forEach(plugin => plugin(this));
// 8. 订阅
const devtools = getDevtoolsGlobalHook();
devtools && devtools.emit('vuex:init', this);
devtools && devtools.on('vuex:travel-to-state', state => {
this.replaceState(state);
});
}
}
这个构造函数接收一个 options
对象,这个对象包含了 state
、getters
、mutations
、actions
等配置项。 让我们逐行解读一下:
-
strict
: 严格模式开关。开启严格模式后,只有通过 mutation 才能修改 state,否则会抛出错误。这有助于我们在开发过程中避免意外的状态修改。 -
this._state
:state
是 Vuex 存储数据的核心。如果state
是一个函数,就调用它来获取初始状态;否则,直接使用传入的state
对象。 这样做的好处是,如果state
是一个函数,每次创建Store
实例时,都会得到一个新的state
对象,避免多个组件共享同一个state
对象导致数据污染。 -
this._mutations
、this._actions
、this._wrappedGetters
: 这三个对象分别用于存储mutations
、actions
和getters
。Object.create(null)
创建一个没有原型的空对象,避免原型链上的属性干扰。 -
this._modules
: 用于处理模块化的情况。ModuleCollection
类负责将模块化的options
对象转换成一个树形结构,方便后续的安装。 -
installModule
: 这个函数是整个初始化过程的核心。它递归地安装模块,注册mutations
、actions
和getters
。 -
resetStoreVM
: 这个函数负责创建 Vue 实例,并将state
和getters
挂载到 Vue 实例上。这样,我们就可以在组件中通过this.$store.state
和this.$store.getters
来访问状态和计算属性了。 -
plugins
: Vuex 允许我们使用插件来扩展功能。plugins
数组中的每个插件都会被调用,并且传入Store
实例作为参数。 -
devtools
: 如果安装了 Vue Devtools,Vuex 会通过devtools
对象与 Devtools 进行通信,方便我们调试 Vuex 应用。
installModule
:模块安装的关键
接下来,我们重点看一下 installModule
函数,它是注册 mutations
、actions
和 getters
的关键。
function installModule(store, rootState, path, module, hot) {
const isRoot = !path.length;
const namespaced = store._modules.getNamespace(path);
// 1. 注册 mutation
if (module.mutations) {
Object.entries(module.mutations).forEach(([key, mutation]) => {
const namespacedType = namespaced + key;
registerMutation(store, namespacedType, mutation, module);
});
}
// 2. 注册 action
if (module.actions) {
Object.entries(module.actions).forEach(([key, action]) => {
const type = action.root ? key : namespaced + key;
const handler = action.handler || action;
registerAction(store, type, handler, module);
});
}
// 3. 注册 getter
if (module.getters) {
Object.entries(module.getters).forEach(([key, getter]) => {
const namespacedType = namespaced + key;
registerGetter(store, namespacedType, getter, module);
});
}
// 4. 递归安装子模块
module.forEachChild((child, key) => {
const childState = child.state;
// 使用 Vue.set 或 store.registerModule注册子模块
store.state[key] = childState;
installModule(store, rootState, path.concat(key), child, hot);
});
}
这个函数接收四个参数:
store
:Store
实例。rootState
:根状态对象。path
:当前模块的路径,是一个数组,表示从根模块到当前模块的路径。module
:当前模块对象。hot
:是否热更新
installModule
函数的主要工作如下:
-
获取命名空间:
store._modules.getNamespace(path)
用于获取当前模块的命名空间。命名空间可以避免不同模块之间的mutations
、actions
和getters
发生冲突。 -
注册
mutations
: 遍历module.mutations
对象,将每个mutation
注册到store._mutations
对象中。注册的时候,会加上命名空间,避免冲突。 -
注册
actions
: 遍历module.actions
对象,将每个action
注册到store._actions
对象中。注册的时候,也会加上命名空间,避免冲突。如果action
对象中定义了root: true
,则表示该action
属于根级别的action
,不需要加上命名空间。 -
注册
getters
: 遍历module.getters
对象,将每个getter
注册到store._wrappedGetters
对象中。注册的时候,也会加上命名空间,避免冲突。 -
递归安装子模块: 遍历
module.children
对象,递归调用installModule
函数来安装子模块。
registerMutation
、registerAction
、registerGetter
:注册三剑客
接下来,我们看一下 registerMutation
、registerAction
和 registerGetter
这三个函数,它们分别负责注册 mutations
、actions
和 getters
。
function registerMutation(store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = []);
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload);
});
}
function registerAction(store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = []);
entry.push(function wrappedActionHandler(payload) {
let res = handler.call(store, {
commit: store.commit,
dispatch: store.dispatch,
state: local.state,
getters: local.getters,
rootState: store.state,
rootGetters: store.getters
}, payload);
if (!isPromise(res)) {
res = Promise.resolve(res);
}
return res;
});
}
function registerGetter(store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
console.error(`[vuex] duplicate getter key: ${type}`);
return;
}
store._wrappedGetters[type] = function wrappedGetter(store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
);
}
}
这三个函数的作用很简单,就是将 mutations
、actions
和 getters
存储到 store
对象的对应属性中。
-
registerMutation
: 将mutation
函数包装成一个函数,然后存储到store._mutations
对象中。包装后的函数会调用原始的mutation
函数,并且传入state
和payload
作为参数。 -
registerAction
: 将action
函数包装成一个函数,然后存储到store._actions
对象中。包装后的函数会调用原始的action
函数,并且传入一个包含commit
、dispatch
、state
、getters
、rootState
和rootGetters
属性的对象作为参数。action
必须返回一个 Promise。 -
registerGetter
: 将getter
函数包装成一个函数,然后存储到store._wrappedGetters
对象中。包装后的函数会调用原始的getter
函数,并且传入state
、getters
、rootState
和rootGetters
作为参数。
resetStoreVM
:创建 Vue 实例
最后,我们来看一下 resetStoreVM
函数,它负责创建 Vue 实例,并将 state
和 getters
挂载到 Vue 实例上。
import { computed, reactive } from 'vue'
function resetStoreVM(store, state) {
const wrappedGetters = store._wrappedGetters
const computedGetters = {}
store.getters = {}
// 将getter转换为computed属性
Object.keys(wrappedGetters).forEach(key => {
const fn = wrappedGetters[key]
computedGetters[key] = computed(() => fn(store))
Object.defineProperty(store.getters, key, {
get: () => computedGetters[key].value,
enumerable: true // for local getters
})
})
// 使用 Vue 提供的 reactive API 使 state 具有响应式
store._state = reactive(state)
}
这个函数的主要工作如下:
-
处理 getters: 循环注册的
getters
,利用computed
函数将getter转换为可计算属性,并且挂载到store.getters
对象上。 -
使 state 具有响应式: 使用 Vue 提供的
reactive
API 将state
对象转换成一个响应式对象。这样,当state
对象发生变化时,所有依赖于state
的组件都会自动更新。
总结:Store 初始化的全貌
到这里,我们已经把 Store
类的初始化过程扒了个底朝天。现在,让我们来回顾一下整个过程:
- 创建
Store
实例时,会接收一个options
对象,这个对象包含了state
、getters
、mutations
、actions
等配置项。 Store
类会根据options
对象,初始化state
、mutations
、actions
和getters
。installModule
函数负责递归地安装模块,注册mutations
、actions
和getters
。registerMutation
、registerAction
和registerGetter
这三个函数分别负责注册mutations
、actions
和getters
。resetStoreVM
函数负责创建 Vue 实例,并将state
和getters
挂载到 Vue 实例上。
为了更清晰地展示这个过程,我们可以用一个表格来总结一下:
步骤 | 描述 | 涉及的函数 |
---|---|---|
1. 构造函数 | 接收 options 对象,初始化 strict 、_state 、_mutations 、_actions 、_wrappedGetters 和 _modules 。 |
Store constructor |
2. 安装模块 | 递归地安装模块,注册 mutations 、actions 和 getters 。 |
installModule |
3. 注册三剑客 | 分别负责注册 mutations 、actions 和 getters 。 |
registerMutation 、registerAction 、registerGetter |
4. 创建 Vue 实例 | 创建 Vue 实例,并将 state 和 getters 挂载到 Vue 实例上。 |
resetStoreVM |
举个栗子:代码演示
为了让大家更好地理解 Store
类的初始化过程,我们来举个栗子:
import { createStore } from 'vuex'
const store = createStore({
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
})
export default store
在这个例子中,我们创建了一个简单的 Vuex 实例,它包含了一个 state
、一个 getter
、一个 mutation
和一个 action
。
当我们创建 store
实例时,Vuex 会按照上面介绍的步骤,将 state
、getter
、mutation
和 action
注册到 store
对象中。
然后,我们就可以在组件中使用 this.$store.state.count
来访问 state
,使用 this.$store.getters.doubleCount
来访问 getter
,使用 this.$store.commit('increment')
来提交 mutation
,使用 this.$store.dispatch('incrementAsync')
来分发 action
。
总结:Vuex 的魅力
通过上面的分析,我们可以看到,Vuex 的初始化过程虽然有点复杂,但是它的设计思想非常清晰。它将状态管理的核心逻辑封装在 Store
类中,并且提供了一套规则来修改状态,这使得我们可以更好地管理 Vue 应用的状态,避免状态混乱和难以维护的问题。
Vuex 的魅力在于它的简单易用、可扩展性和可维护性。它不仅可以帮助我们更好地管理 Vue 应用的状态,还可以提高开发效率,降低维护成本。
好了,今天的讲座就到这里。希望大家通过今天的学习,能够对 Vuex 的 Store
类的初始化过程有一个更深入的理解。下次再见!