各位靓仔靓女,晚上好!我是今晚的讲师,咱们今天来聊聊 Vuex 4.0 的内部实现,以及它和 Composition API 如何“眉来眼去”的集成在一起。
别被“源码深度解析”吓到,咱们不搞那种让你看了就想睡觉的枯燥分析,争取用大白话把 Vuex 的核心扒个精光。
一、Vuex 4.0:不再是大哥大,而是瑞士军刀
Vuex,作为 Vue 的官方状态管理库,在 Vue 2 时代,它就像个大哥大,功能强大但有点笨重。到了 Vue 3 时代,它摇身一变,成了瑞士军刀,轻巧灵活,功能还更丰富了。
那么,Vuex 4.0 到底做了哪些改变呢?
- 拥抱 Composition API: 这是最大的变化。Vuex 4.0 提供了
useStorehook,让你可以像使用ref和reactive一样轻松地访问和修改状态。 - TypeScript 支持: 告别
any地狱,类型检查让你的代码更健壮。 - 模块化增强: 模块化更加灵活,可以动态注册和卸载模块。
- 更小的体积: 性能优化,让你的应用跑得更快。
二、Store 的内部实现:揭开神秘面纱
Store 是 Vuex 的核心,所有的状态、 mutations、actions 和 getters 都存储在其中。 让我们一起看看它的内部构造。
// Vuex 4.0 的 Store 类
class Store<S> {
constructor(options: StoreOptions<S>) {
const {
state,
mutations,
actions,
getters,
modules,
plugins,
strict = false
} = options;
// 1. 状态:如果是函数,则执行获取初始状态
this._state = reactive({
data: typeof state === 'function' ? (state as Function)() : state
});
// 2. 存储 mutations, actions 和 getters
this._mutations = Object.create(null);
this._actions = Object.create(null);
this._wrappedGetters = Object.create(null);
// 3. 模块收集与注册
this._modules = new ModuleCollection(options);
installModule(this, this._state, [], this._modules.root);
// 4. 注册 plugins
if (plugins) {
plugins.forEach(plugin => plugin(this));
}
// 5. 严格模式
this.strict = strict;
// 6. 如果是开发环境,则开启严格模式的监听
if (process.env.NODE_ENV !== 'production' && strict) {
enableStrictMode(this);
}
// 7. 初始化完成标志
this._committing = false;
this._subscribers = [];
this._watcherVM = new Vue(); // Vue2 实现的观察者,用于订阅和触发事件
}
// 获取 state
get state(): S {
return this._state.data;
}
// 提交 mutation
commit = (type: string, payload?: any, options?: CommitOptions) => {
// ... 省略 commit 的实现细节,后面会详细讲解
}
// 触发 action
dispatch = (type: string, payload?: any) => {
// ... 省略 dispatch 的实现细节,后面会详细讲解
}
// 注册 mutation
registerMutation(type: string, handler: Function, local: boolean = false) {
// ... 省略注册 mutation 的实现细节
}
// 注册 action
registerAction(type: string, handler: Function, local: boolean = false) {
// ... 省略注册 action 的实现细节
}
// 注册 getter
registerGetter(type: string, getter: Function, local: boolean = false) {
// ... 省略注册 getter 的实现细节
}
// 替换 state
replaceState(state: S) {
// ... 省略替换 state 的实现细节
}
// 订阅 mutation
subscribe(fn: Function): Function {
// ... 省略订阅 mutation 的实现细节
}
}
export function createStore<S>(options: StoreOptions<S>): Store<S> {
return new Store<S>(options);
}
我们来逐行解读一下:
constructor(options: StoreOptions<S>):Store类的构造函数,接收一个options对象,包含了state、mutations、actions、getters、modules、plugins和strict等配置。this._state = reactive({ data: ... }): 使用 Vue 3 的reactive函数将state包装成一个响应式对象。这意味着当state的值发生变化时,所有依赖它的组件都会自动更新。如果state是一个函数,则调用该函数返回初始状态。this._mutations = Object.create(null)、this._actions = Object.create(null)、this._wrappedGetters = Object.create(null): 创建三个对象,分别用于存储mutations、actions和getters。使用Object.create(null)可以创建一个没有原型链的对象,避免不必要的属性查找。this._modules = new ModuleCollection(options)、installModule(this, this._state, [], this._modules.root): 这是 Vuex 模块化的核心。ModuleCollection用于收集所有的模块,并构建一个模块树。installModule函数则负责将模块的状态、mutations、actions 和 getters 注册到Store实例中。plugins.forEach(plugin => plugin(this)): 注册插件。插件可以用来扩展 Vuex 的功能,例如日志记录、数据持久化等。this.strict = strict: 是否开启严格模式。在严格模式下,只能通过mutation来修改state,否则会抛出错误。enableStrictMode(this): 开启严格模式的监听。this.commit = (type: string, payload?: any, options?: CommitOptions) => { ... }:commit函数用于提交mutation。它会查找对应的mutation,并执行它。this.dispatch = (type: string, payload?: any) => { ... }:dispatch函数用于触发action。它会查找对应的action,并执行它。
三、commit 和 dispatch:状态管理的发动机
commit 和 dispatch 是 Vuex 中最重要的两个函数,它们是状态管理的发动机。
-
commit:同步修改状态commit用于提交mutation,mutation是唯一允许修改state的方式。commit = (type: string, payload?: any, options?: CommitOptions) => { // 1. 查找对应的 mutation const mutation = this._mutations[type]; if (!mutation) { if (process.env.NODE_ENV !== 'production') { console.error(`[vuex] unknown mutation type: ${type}`); } return; } // 2. 执行 mutation try { this._committing = true; // 标记当前正在提交 mutation mutation.forEach(handler => handler(this._state.data, payload)); } catch (e) { throw e; } finally { this._committing = false; // 标记 mutation 提交完成 } // 3. 触发订阅者 this._subscribers.forEach(sub => sub({ type, payload }, this.state)); }- 查找
mutation: 根据type查找对应的mutation。 - 执行
mutation: 遍历所有的mutation处理函数,并依次执行它们。在执行mutation期间,会设置this._committing = true,防止在mutation中直接修改state。 - 触发订阅者: 遍历所有的订阅者,并依次执行它们。订阅者可以用来监听
mutation的提交,例如记录日志、更新 UI 等。
- 查找
-
dispatch:异步触发 actiondispatch用于触发action,action可以包含任意的异步操作。dispatch = (type: string, payload?: any) => { // 1. 查找对应的 action const action = this._actions[type]; if (!action) { if (process.env.NODE_ENV !== 'production') { console.error(`[vuex] unknown action type: ${type}`); } return Promise.reject(new Error(`unknown action type: ${type}`)); } // 2. 执行 action try { return action.length > 1 ? Promise.all(action.map(handler => handler({ dispatch: this.dispatch, commit: this.commit, state: this.state, getters: this.getters, rootState: this.state, rootGetters: this.getters }, payload))) : action[0]({ dispatch: this.dispatch, commit: this.commit, state: this.state, getters: this.getters, rootState: this.state, rootGetters: this.getters }, payload) } catch (e) { return Promise.reject(e); } }- 查找
action: 根据type查找对应的action。 - 执行
action: 执行action处理函数,并将context对象和payload作为参数传递给它。context对象包含了dispatch、commit、state、getters、rootState和rootGetters等属性。action可以通过context.commit提交mutation,也可以通过context.dispatch触发其他的action。
- 查找
四、ModuleCollection 和 installModule:模块化的基石
Vuex 的模块化允许你将 store 分割成多个模块,每个模块拥有自己的 state、mutations、actions 和 getters。
-
ModuleCollection:收集模块ModuleCollection用于收集所有的模块,并构建一个模块树。class ModuleCollection { constructor(rawRootModule: RawModule, runtime: boolean) { // register root module (Vuex.Store options) this.register([rawRootModule], runtime) } get(path: string[]): Module { return path.reduce((module, key) => { return module.getChild(key) }, this.root) } getNamespace(path: string[]): string { let module = this.root return path.reduce((namespace, key) => { module = module.getChild(key) return namespace + (module.namespaced ? key + '/' : '') }, '') } update(rawRootModule: RawModule) { update(this.root, rawRootModule) } register(path: string[], rawModule: RawModule, runtime: boolean = true) { const parent = this.get(path.slice(0, -1)) const newModule = new Module(rawModule, runtime) parent.addChild(path[path.length - 1], newModule) // recursively register all submodules if (rawModule.modules) { forEachValue(rawModule.modules, (rawChildModule, key) => { this.register(path.concat(key), rawChildModule, runtime) }) } } unregister(path: string[]) { const parent = this.get(path.slice(0, -1)) const key = path[path.length - 1] if (!parent.getChild(key).runtime) return parent.removeChild(key) } }register(path: string[], rawModule: RawModule, runtime: boolean = true): 注册一个模块。它会创建一个Module实例,并将其添加到模块树中。如果模块包含子模块,则递归注册所有的子模块。get(path: string[]): Module: 根据路径获取模块。getNamespace(path: string[]): string: 根据路径获取模块的命名空间。
-
installModule:安装模块installModule函数负责将模块的状态、mutations、actions 和 getters 注册到Store实例中。function installModule(store: Store<any>, rootState: any, path: string[], module: Module, hot: boolean = false) { const isRoot = !path.length const namespace = store._modules.getNamespace(path) // register in namespace map if (module.namespaced) { if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') { console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module at ${path.join('/')}`) } 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(() => { parentState[moduleName] = module.state }) } const local = module.context = makeLocalContext(store, namespace, path) 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) }) // install submodules module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) }) // watch state if hot if (hot && module.hotUpdate) { module.hotUpdate(store, local) } }- 获取模块的命名空间: 使用
store._modules.getNamespace(path)获取模块的命名空间。 - 设置模块的状态: 将模块的状态添加到父模块的状态中。
- 创建模块的本地上下文: 使用
makeLocalContext函数创建模块的本地上下文。本地上下文包含了dispatch、commit、state、getters和rootState等属性,但是它们只作用于当前模块。 - 注册
mutation、action和getter: 遍历模块的mutation、action和getter,并将它们注册到Store实例中。 - 递归安装子模块: 递归安装所有的子模块。
- 获取模块的命名空间: 使用
五、useStore:拥抱 Composition API
Vuex 4.0 提供了 useStore hook,让你可以像使用 ref 和 reactive 一样轻松地访问和修改状态。
import { inject } from 'vue'
import { storeKey } from './injectKey'
export function useStore(key?: string): Store<any> {
return inject(key !== null && key !== void 0 ? key : storeKey)
}
useStore 函数实际上就是一个 inject 函数,它从 Vue 的依赖注入系统中获取 Store 实例。
使用 useStore 非常简单:
<template>
<div>
<h1>{{ count }}</h1>
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'
const store = useStore()
const count = computed(() => store.state.count)
const increment = () => {
store.commit('increment')
}
</script>
const store = useStore(): 获取Store实例。const count = computed(() => store.state.count): 使用computed函数创建一个计算属性,用于监听state.count的变化。const increment = () => { store.commit('increment') }: 使用store.commit提交mutation,修改state.count的值。
六、Vuex 4.0 与 Composition API 的集成:天作之合
Vuex 4.0 与 Composition API 的集成,可以说是天作之合。它们可以很好地协同工作,让你的代码更加简洁、易于维护。
-
更简洁的代码: 使用
useStorehook,你可以直接在组件中使用state、mutations和actions,无需再使用mapState、mapMutations和mapActions等辅助函数。 -
更好的类型检查: Vuex 4.0 提供了 TypeScript 支持,你可以使用类型检查来确保你的代码的正确性。
-
更灵活的组合: 使用 Composition API,你可以将不同的状态管理逻辑组合在一起,创建更复杂的功能。
七、实战演练:一个简单的计数器
让我们用 Vuex 4.0 和 Composition API 来实现一个简单的计数器。
// store.ts
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
},
actions: {
incrementAsync(context) {
setTimeout(() => {
context.commit('increment')
}, 1000)
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})
// Counter.vue
<template>
<div>
<h1>{{ count }}</h1>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'
const store = useStore()
const count = computed(() => store.state.count)
const doubleCount = computed(() => store.getters.doubleCount)
const increment = () => {
store.commit('increment')
}
const decrement = () => {
store.commit('decrement')
}
const incrementAsync = () => {
store.dispatch('incrementAsync')
}
</script>
这个例子很简单,但是它可以让你看到 Vuex 4.0 和 Composition API 的强大之处。
八、总结
今天,我们深入了解了 Vuex 4.0 的内部实现,以及它和 Composition API 如何集成在一起。希望通过这次讲座,你对 Vuex 有了更深入的理解,并能够在实际项目中更好地使用它。
记住,源码分析不是目的,目的是更好地理解框架的原理,从而写出更高效、更健壮的代码。
好了,今天的讲座就到这里,大家可以自由提问。如果暂时没有问题,不妨回去多敲几行代码,实践出真知嘛! 各位晚安!