Appearance
Vuex4.0 核心原理与实现解析
源码实现地址:Vuex4.0 源码实现
Vuex 概述
Vuex 是 Vue.js 中的状态管理工具,提供了一种集中式存储管理应用程序中所有组件状态的方法。其核心目的是:
- 实现组件之间的状态共享
- 快速响应数据变化
- 对复杂多交互行为进行封装和解耦
核心概念
| 概念 | 描述 |
|---|---|
| state | 单一状态树,将需要管理的状态抽离出来形成全局单一状态集合 |
| getters | 类似于 computed 属性,监听 state 变化返回计算值 |
| mutations | 修改状态值的函数,接收 state 对象作为参数(必须同步) |
| actions | 异步操作,触发 mutation 来更新状态 |
| module | 大型项目中划分功能模块,便于维护和管理 |
Vuex 执行流程
初始化阶段
- 通过
Vue.use(Store)安装时,调用createStore()创建 Store 实例 - Store 内部通过
provide将实例注入到根组件
- 通过
状态注册
js// 示例:创建 Store const store = createStore({ state: { count: 0 }, mutations: { increment(state) { state.count++; }, }, actions: { asyncIncrement({ commit }) { setTimeout(() => commit("increment"), 1000); }, }, });模块处理
- 递归安装所有模块,将子模块 state 挂载到根 state
- 通过命名空间对 getters、mutations、actions 进行分类存储
响应式处理
- 使用 Vue 的
reactive将根 state 转换为响应式数据
- 使用 Vue 的
状态更新
dispatch()触发 actionscommit()触发 mutations
组件使用
- 通过
useStore()获取 Store 实例 - 内部使用
inject从根组件查找 Store
- 通过
核心问题解析
为什么 mutations 不能异步?
- 状态追踪:每个 mutation 执行后对应一个新的状态变更,devtools 可以记录快照
- Time-travel 调试:异步操作会导致无法确定状态更新时机,破坏调试能力
- 设计原则:Mutation 必须是同步函数,确保状态变更可预测
Actions vs Mutations
| 特性 | Actions | Mutations |
|---|---|---|
| 异步支持 | ✅ 支持异步操作 | ❌ 仅支持同步 |
| 调用方式 | dispatch() | commit() |
| 状态修改 | 间接通过 mutation | 直接修改 state |
| 组合能力 | 可提交多个 mutation | 单一状态变更 |
为什么必须使用 commit()/dispatch()?
js
// Store 内部实现核心
class Store {
constructor(options) {
this._mutations = Object.create(null);
this._actions = Object.create(null);
// 收集 mutations 和 actions
// ...
}
commit(type, payload) {
const entry = this._mutations[type];
entry && entry(payload);
}
dispatch(type, payload) {
const entry = this._actions[type];
return entry(payload);
}
// 没有直接暴露 mutation 方法
}- 架构约束:Store 类不直接暴露 mutation 方法,只能通过 commit 触发
- 调试能力:统一入口便于 devtools 跟踪状态变更
- 代码维护:避免组件直接修改 state 导致难以追踪的变化
为什么不能直接修改 state?
- 可维护性:集中修改路径便于追踪状态变化来源
- 约束性:防止多组件随意修改导致的不可预测行为
- 响应式保障:确保 Vue 的响应式系统正确追踪状态依赖
为什么异步操作必须通过 dispatch()?
- 设计分离:
- commit() 使用同步方式调用 mutations
- dispatch() 使用 Promise.all 处理异步操作
- 错误处理:dispatch() 返回 Promise 便于异步错误捕获
- 执行上下文:actions 可获取完整 store 上下文(commit, state, getters)
js
// dispatch 异步处理实现
dispatch(type, payload) {
const entry = this._actions[type]
return Promise.all(entry.map(handler => handler(payload)))
}最佳实践
- 组件中通过
computed获取 state - 使用
mapState/mapGetters简化状态获取 - 复杂业务逻辑放在 actions 中处理
- 大型项目使用 modules 进行状态分区
- 始终通过 commit 修改状态,保持可追踪性
设计哲学:Vuex 通过强制规范状态修改路径,在灵活性和可维护性之间取得平衡,为复杂应用提供可预测的状态管理方案。
Vuex 与 Pinia 对比
- "Pinia 是 Vuex 的官方升级版,它简化了 API、提供了更好的 TypeScript 支持,并且移除了 mutations 的概念,让状态管理更加直观和简洁。"
1. 核心概念差异
Vuex:
- 基于 Flux 架构的严格单向数据流
- 四个核心概念:State、Getters、Mutations、Actions
- 必须通过 mutations 同步修改 state
Pinia:
- 简化的状态管理架构
- 三个核心概念:State、Getters、Actions
- Actions 可以直接修改 state(同步或异步)
2. 代码演示对比
Vuex 示例代码:
javascript
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
user: null,
count: 0
},
getters: {
isLoggedIn: state => !!state.user,
doubleCount: state => state.count * 2
},
mutations: {
SET_USER(state, user) {
state.user = user
},
INCREMENT(state) {
state.count++
}
},
actions: {
login({ commit }, credentials) {
return api.login(credentials).then(user => {
commit('SET_USER', user) // 必须通过 commit
})
},
incrementAsync({ commit }) {
setTimeout(() => {
commit('INCREMENT') // 必须通过 commit
}, 1000)
}
}
})
// 组件中使用
export default {
computed: {
...mapState(['user', 'count']),
...mapGetters(['isLoggedIn', 'doubleCount'])
},
methods: {
...mapActions(['login', 'incrementAsync']),
handleLogin() {
this.login(credentials).then(() => {
// 登录成功
})
}
}
}Pinia 示例代码:
javascript
// stores/userStore.js
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
state: () => ({
user: null,
count: 0,
}),
getters: {
isLoggedIn: (state) => !!state.user,
doubleCount: (state) => state.count * 2,
},
actions: {
async login(credentials) {
const user = await api.login(credentials);
this.user = user; // 直接修改 state
},
increment() {
this.count++; // 直接修改 state
},
async incrementAsync() {
setTimeout(() => {
this.count++; // 直接修改 state,无需 mutations
}, 1000);
},
},
});
// 组件中使用
import { useUserStore } from "@/stores/userStore";
export default {
setup() {
const userStore = useUserStore();
const handleLogin = async () => {
await userStore.login(credentials);
// 登录成功
};
return {
user: computed(() => userStore.user),
count: computed(() => userStore.count),
isLoggedIn: computed(() => userStore.isLoggedIn),
handleLogin,
increment: () => userStore.increment(),
};
},
};3. TypeScript 支持对比
Vuex 的 TypeScript 支持(相对复杂):
typescript
// 需要定义复杂的类型
interface State {
user: User | null;
count: number;
}
export default createStore<State>({
state: {
user: null,
count: 0,
},
// ... 其他配置
});Pinia 的 TypeScript 支持(一流的):
typescript
// 自动类型推断,几乎无需额外配置
export const useUserStore = defineStore("user", {
state: () => ({
user: null as User | null,
count: 0,
}),
// 完整的类型推断
});4. 模块化方式对比
Vuex 模块化:
javascript
// 需要嵌套模块
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
export default createStore({
modules: {
a: moduleA,
b: moduleB
}
})Pinia 模块化:
javascript
// 每个 store 都是独立的
// stores/userStore.js
export const useUserStore = defineStore('user', { ... })
// stores/productStore.js
export const useProductStore = defineStore('product', { ... })
// 按需引入使用
const userStore = useUserStore()
const productStore = useProductStore()"为什么要用 Pinia 替代 Vuex?:
- API 简化:移除了 mutations,让状态修改更直接
- 更好的 TS 支持:提供完整的类型推断,开发体验更好
- 模块化更自然:每个 store 都是独立的,避免命名冲突
- 组合式 API 友好:与 Vue 3 的组合式 API 风格一致
- 更轻量:体积更小,性能更好"