各位靓仔靓女,晚上好!我是今晚的讲师,咱们今天来聊聊 Vuex 4.0 的内部实现,以及它和 Composition API 如何“眉来眼去”的集成在一起。
别被“源码深度解析”吓到,咱们不搞那种让你看了就想睡觉的枯燥分析,争取用大白话把 Vuex 的核心扒个精光。
一、Vuex 4.0:不再是大哥大,而是瑞士军刀
Vuex,作为 Vue 的官方状态管理库,在 Vue 2 时代,它就像个大哥大,功能强大但有点笨重。到了 Vue 3 时代,它摇身一变,成了瑞士军刀,轻巧灵活,功能还更丰富了。
那么,Vuex 4.0 到底做了哪些改变呢?
- 拥抱 Composition API: 这是最大的变化。Vuex 4.0 提供了
useStore
hook,让你可以像使用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 的集成,可以说是天作之合。它们可以很好地协同工作,让你的代码更加简洁、易于维护。
-
更简洁的代码: 使用
useStore
hook,你可以直接在组件中使用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 有了更深入的理解,并能够在实际项目中更好地使用它。
记住,源码分析不是目的,目的是更好地理解框架的原理,从而写出更高效、更健壮的代码。
好了,今天的讲座就到这里,大家可以自由提问。如果暂时没有问题,不妨回去多敲几行代码,实践出真知嘛! 各位晚安!