Mutation规则 #

核心规则 #

Vuex 的 Mutation 有几条必须遵守的规则:

text
Mutation 规则
├── 必须是同步函数
├── 必须通过 commit 调用
├── 第一个参数是 state
└── 不应直接修改 state(在组件中)

规则一:必须是同步函数 #

为什么? #

Mutation 必须是同步函数,这是 Vuex 设计的核心原则。原因如下:

javascript
// 问题:异步 mutation
mutations: {
  INCREMENT(state) {
    setTimeout(() => {
      state.count++
    }, 1000)
  }
}

// 当 commit 完成时,状态实际上还没有改变
this.$store.commit('INCREMENT')
console.log(this.$store.state.count)  // 还是旧值

正确做法 #

javascript
// Mutation 保持同步
mutations: {
  INCREMENT(state) {
    state.count++
  }
}

// 异步操作放在 Action 中
actions: {
  async incrementAsync({ commit }) {
    await delay(1000)
    commit('INCREMENT')
  }
}

调试问题 #

异步 mutation 会导致调试困难:

text
DevTools 记录:
┌─────────────────────────────────────────┐
│ Mutation: INCREMENT                      │
│ 时间: 10:00:00                          │
│ 状态变化: count 0 → 1                   │
└─────────────────────────────────────────┘

如果 mutation 是异步的:
- DevTools 记录的是 commit 时间
- 实际状态变化发生在之后
- 时间旅行调试会出错

规则二:必须通过 commit 调用 #

错误示例 #

javascript
// 错误:直接调用 mutation
this.$store.mutations.INCREMENT(this.$store.state)

// 错误:直接修改 state
this.$store.state.count = 10

正确做法 #

javascript
// 正确:通过 commit
this.$store.commit('INCREMENT')

// 正确:通过 Action
this.$store.dispatch('increment')

严格模式 #

开启严格模式可以防止非法修改:

javascript
const store = createStore({
  strict: true,  // 开发环境开启
  
  state: { /* ... */ },
  mutations: { /* ... */ }
})

// 直接修改会抛出错误
this.$store.state.count = 10  // Error!

规则三:响应式更新 #

添加新属性 #

javascript
// 问题:直接添加属性不会触发响应式
mutations: {
  ADD_PROPERTY(state) {
    state.user.newProp = 'value'  // 不会触发更新
  }
}

// 解决方案一:使用 Vue.set
import { set } from 'vue'

mutations: {
  ADD_PROPERTY(state) {
    set(state.user, 'newProp', 'value')
  }
}

// 解决方案二:对象展开
mutations: {
  ADD_PROPERTY(state) {
    state.user = { ...state.user, newProp: 'value' }
  }
}

数组操作 #

javascript
// 问题:通过索引修改
mutations: {
  UPDATE_ITEM(state, { index, value }) {
    state.items[index] = value  // 不会触发更新
  }
}

// 解决方案一:使用 splice
mutations: {
  UPDATE_ITEM(state, { index, value }) {
    state.items.splice(index, 1, value)
  }
}

// 解决方案二:创建新数组
mutations: {
  UPDATE_ITEM(state, { index, value }) {
    state.items = state.items.map((item, i) => 
      i === index ? value : item
    )
  }
}

删除属性 #

javascript
// 问题:直接删除
mutations: {
  REMOVE_PROPERTY(state) {
    delete state.user.oldProp  // 不会触发更新
  }
}

// 解决方案:使用 Vue.delete 或对象解构
import { del } from 'vue'

mutations: {
  REMOVE_PROPERTY(state) {
    del(state.user, 'oldProp')
    
    // 或
    const { oldProp, ...rest } = state.user
    state.user = rest
  }
}

规则四:命名规范 #

使用常量 #

javascript
// mutation-types.js
export const SET_USER = 'SET_USER'
export const SET_LOADING = 'SET_LOADING'
export const ADD_TO_CART = 'ADD_TO_CART'

// store.js
import * as types from './mutation-types'

mutations: {
  [types.SET_USER](state, user) {
    state.user = user
  },
  
  [types.SET_LOADING](state, loading) {
    state.loading = loading
  },
  
  [types.ADD_TO_CART](state, item) {
    state.cart.push(item)
  }
}

命名约定 #

javascript
// 推荐:使用动词+名词
mutations: {
  SET_USER,
  SET_LOADING,
  ADD_ITEM,
  REMOVE_ITEM,
  UPDATE_TODO,
  CLEAR_CART
}

// 不推荐:模糊命名
mutations: {
  user,    // 不清楚是设置还是获取
  loading,
  item
}

规则五:单一职责 #

每个 Mutation 只做一件事 #

javascript
// 推荐:单一职责
mutations: {
  SET_USER(state, user) {
    state.user = user
  },
  
  SET_LOADING(state, loading) {
    state.loading = loading
  },
  
  SET_ERROR(state, error) {
    state.error = error
  }
}

// 不推荐:一个 mutation 做多件事
mutations: {
  FETCH_USER_SUCCESS(state, { user, loading, error }) {
    state.user = user
    state.loading = loading
    state.error = error
  }
}

在 Action 中组合 #

javascript
// Action 可以组合多个 mutation
actions: {
  async fetchUser({ commit }, userId) {
    commit('SET_LOADING', true)
    commit('SET_ERROR', null)
    
    try {
      const user = await api.fetchUser(userId)
      commit('SET_USER', user)
    } catch (error) {
      commit('SET_ERROR', error.message)
    } finally {
      commit('SET_LOADING', false)
    }
  }
}

开发环境配置 #

开启严格模式 #

javascript
const store = createStore({
  // 只在开发环境开启
  strict: process.env.NODE_ENV !== 'production',
  
  state: { /* ... */ },
  mutations: { /* ... */ }
})

性能考虑 #

严格模式会深度观察状态树,可能影响性能:

javascript
// 生产环境关闭严格模式
if (process.env.NODE_ENV === 'production') {
  store.strict = false
}

常见错误 #

1. 在 Mutation 中进行异步操作 #

javascript
// 错误
mutations: {
  FETCH_USER(state) {
    fetch('/api/user').then(user => {
      state.user = user  // 异步修改
    })
  }
}

// 正确
actions: {
  async fetchUser({ commit }) {
    const user = await fetch('/api/user')
    commit('SET_USER', user)
  }
}

2. 在组件中直接修改 state #

javascript
// 错误
this.$store.state.user.name = 'John'

// 正确
this.$store.commit('SET_USER_NAME', 'John')

3. Mutation 返回值 #

javascript
// 错误:依赖 mutation 返回值
const result = this.$store.commit('INCREMENT')

// Mutation 不应该有返回值
mutations: {
  INCREMENT(state) {
    state.count++
    return state.count  // 不要这样做
  }
}

总结 #

Mutation 规则总结:

规则 说明 原因
同步函数 必须同步执行 便于调试和追踪
commit 调用 必须通过 commit 统一入口,便于管理
响应式更新 正确添加/删除属性 确保视图更新
命名规范 使用常量和清晰命名 提高可维护性
单一职责 每个 mutation 只做一件事 便于测试和调试

继续学习 Action基础,了解如何处理异步操作。

最后更新:2026-03-28