文章目录
在 Vuex 状态管理库中,action 和 mutation 是两个核心概念,它们协同工作但职责不同。理解它们的区别对于构建可维护的 Vue 应用至关重要。
一、核心区别总览
特性 | Mutation | Action |
---|---|---|
职责 | 修改 state 的唯一途径 | 处理业务逻辑和异步操作 |
调用方式 | commit |
dispatch |
同步/异步 | 必须是同步 | 可以包含异步操作 |
是否直接修改状态 | 直接修改 | 不直接修改,通过提交 mutation |
调试工具可见性 | 被 DevTools 记录 | 只有最终提交的 mutation 会被记录 |
使用场景 | 简单的状态变更 | 复杂逻辑、API 调用、多个 mutation |
二、Mutation:状态变更的最小单元
2.1 基本特征
Mutation 是唯一能够修改 Vuex state 的方式,每个 mutation 都有一个字符串类型的 type
和一个处理函数 handler
。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
// 类型为 'INCREMENT' 的 mutation
INCREMENT(state) {
state.count++
},
// 带 payload 的 mutation
ADD_COUNT(state, payload) {
state.count += payload.amount
}
}
})
2.2 关键特点
-
同步性:必须是同步函数
// 错误示例!不要在 mutation 中包含异步操作 mutations: { ASYNC_INCREMENT(state) { setTimeout(() => { state.count++ // 这会导致状态变更无法被追踪 }, 1000) } }
-
直接修改状态:在 mutation 函数内部直接修改 state 对象
-
可追踪性:所有 mutation 都会被 Vuex 记录,便于调试和时间旅行
-
原子性:每个 mutation 应该只完成一个特定的状态变更
2.3 提交方式
在组件中通过 commit
触发:
this.$store.commit('INCREMENT')
// 带 payload
this.$store.commit('ADD_COUNT', {
amount: 10 })
三、Action:业务逻辑的处理中心
3.1 基本特征
Action 类似于 mutation,但它不直接修改状态,而是:
- 提交 mutation 来修改状态
- 可以包含任意异步操作
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
SET_COUNT(state, value) {
state.count = value
}
},
actions: {
fetchCount({
commit }) {
return api.getCount().then(response => {
commit('SET_COUNT', response.data.count)
})
},
async incrementAsync({
commit, state }) {
await delay(1000)
commit('SET_COUNT', state.count + 1)
}
}
})
3.2 关键特点
-
异步操作支持:可以包含异步逻辑(API 调用、定时器等)
actions: { async login({ commit }, credentials) { try { const user = await api.login(credentials) commit('SET_USER', user) return user } catch (error) { commit('SET_ERROR', error.message) throw error } } }
-
间接修改状态:通过提交 mutation 来修改 state
-
组合性:可以组合多个 mutation 或调用其他 action
actions: { async initApp({ dispatch }) { await dispatch('fetchUser') await dispatch('loadPreferences') dispatch('startBackgroundSync') } }
-
业务逻辑容器:处理验证、错误处理等业务逻辑
3.3 分发方式
在组件中通过 dispatch
触发:
this.$store.dispatch('fetchCount')
// 带参数
this.$store.dispatch('login', {
username, password })
四、设计原则与最佳实践
4.1 职责划分原则
-
Mutation:
- 只负责简单的状态变更
- 保持同步和纯净(无副作用)
- 命名采用全大写(如
SET_USER
)
-
Action:
- 处理业务逻辑和异步操作
- 可以包含条件判断、错误处理
- 命名采用 camelCase(如
fetchUserData
)
4.2 常见模式
-
API 调用模式:
actions: { async fetchPosts({ commit }) { commit('SET_LOADING', true) try { const posts = await api.getPosts() commit('SET_POSTS', posts) commit('SET_ERROR', null) } catch (error) { commit('SET_ERROR', error.message) } finally { commit('SET_LOADING', false) } } }
-
多步操作模式:
actions: { async placeOrder({ commit, dispatch }, order) { commit('CLEAR_CART') await dispatch('validateOrder', order) const result = await dispatch('submitOrder', order) await dispatch('sendConfirmation', result.orderId) return result } }
-
数据预处理模式:
actions: { updateProfile({ commit }, rawData) { const processedData = { ...rawData, lastUpdated: new Date().toISOString() } commit('UPDATE_PROFILE', processedData) } }
五、为什么需要这样的设计?
5.1 架构优势
-
可预测的状态变更:
- 所有状态变更都通过 mutation 进行
- 变更记录可以被 DevTools 追踪
-
分离关注点:
- mutation 关注"如何改变状态"
- action 关注"什么导致了状态改变"
-
更好的可测试性:
- mutation 是纯函数,易于单元测试
- action 可以单独测试业务逻辑
5.2 调试体验
-
时间旅行调试:
- 因为 mutation 是同步的,DevTools 可以准确记录状态快照
- 可以回放 mutation 查看状态变化
-
Action 日志:
- 虽然 action 本身不被记录,但它们提交的 mutation 会被捕获
- 可以在 action 中添加日志辅助调试
actions: {
async fetchData({
commit }) {
console.log('开始获取数据')
const data = await api.getData()
console.log('数据获取成功', data)
commit('SET_DATA', data)
}
}
六、常见误区与纠正
6.1 误区一:在 action 中直接修改 state
错误做法:
actions: {
updateCount({
state }, value) {
state.count = value // 直接修改 state!
}
}
正确做法:
actions: {
updateCount({
commit }, value) {
commit('SET_COUNT', value) // 通过 mutation 修改
}
}
6.2 误区二:在 mutation 中执行异步操作
错误做法:
mutations: {
ASYNC_UPDATE(state) {
setTimeout(() => {
state.count++ // 这将导致状态变更无法被追踪
}, 1000)
}
}
正确做法:
actions: {
asyncUpdate({
commit }) {
setTimeout(() => {
commit('INCREMENT') // 在回调中提交 mutation
}, 1000)
}
}
6.3 误区三:过度使用 action
对于简单的同步操作,可以直接提交 mutation 而不需要 action:
// 不需要为这种简单操作创建 action
this.$store.commit('TOGGLE_SIDEBAR')
// 只有当需要业务逻辑时才使用 action
this.$store.dispatch('fetchUserProfile')
七、TypeScript 中的增强类型
在使用 TypeScript 时,可以更好地表达两者的区别:
interface State {
count: number;
user: User | null;
}
// Mutation 类型
interface Mutations {
INCREMENT(state: State): void;
SET_COUNT(state: State, payload: number): void;
SET_USER(state: State, payload: User | null): void;
}
// Action 上下文类型
interface AugmentedActionContext {
commit<K extends keyof Mutations>(
key: K,
payload?: Parameters<Mutations[K]>[1]
): ReturnType<Mutations[K]>;
state: State;
}
// Action 类型
interface Actions {
fetchUser(context: AugmentedActionContext, userId: string): Promise<void>;
incrementAsync(context: AugmentedActionContext, delay: number): Promise<void>;
}
八、总结:何时使用何种方式
使用 Mutation 当:
- 需要直接修改 state
- 操作是同步的
- 变更简单且无副作用
- 需要被 DevTools 精确追踪
使用 Action 当:
- 需要处理异步操作
- 需要组合多个状态变更
- 包含业务逻辑或副作用
- 需要调用 API 或其他服务
- 需要错误处理或条件逻辑
通过遵循这些原则,你可以构建出更清晰、更易维护的 Vuex 状态管理架构,使你的 Vue 应用程序更加健壮和可预测。