Mutation 同步更新

Mutation 是 Vuex 中更改状态的唯一方式。每个 mutation 都有一个字符串类型和一个回调函数,回调函数接收 state 作为参数。

基本用法

定义 Mutation

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  }
})

提交 Mutation

this.$store.commit('increment')

提交载荷

单个参数

mutations: {
  increment(state, amount) {
    state.count += amount
  }
}

this.$store.commit('increment', 10)

对象载荷

mutations: {
  updateUserInfo(state, payload) {
    state.user.name = payload.name
    state.user.email = payload.email
  }
}

this.$store.commit('updateUserInfo', {
  name: '张三',
  email: 'zhangsan@example.com'
})

对象风格提交

this.$store.commit({
  type: 'increment',
  amount: 10
})
mutations: {
  increment(state, payload) {
    state.count += payload.amount
  }
}

使用常量

使用常量替代 mutation 类型名,便于维护:

export const MUTATION_TYPES = {
  SET_USER: 'SET_USER',
  SET_TOKEN: 'SET_TOKEN',
  CLEAR_USER: 'CLEAR_USER'
}
import { MUTATION_TYPES } from './mutation-types'

const store = new Vuex.Store({
  mutations: {
    [MUTATION_TYPES.SET_USER](state, user) {
      state.user = user
    },
    [MUTATION_TYPES.SET_TOKEN](state, token) {
      state.token = token
    },
    [MUTATION_TYPES.CLEAR_USER](state) {
      state.user = null
      state.token = null
    }
  }
})

this.$store.commit(MUTATION_TYPES.SET_USER, user)

mapMutations 辅助函数

import { mapMutations } from 'vuex'

export default {
  methods: {
    ...mapMutations(['increment', 'decrement']),
    ...mapMutations({
      add: 'increment',
      subtract: 'decrement'
    })
  }
}
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="add(10)">+10</button>

Mutation 必须是同步的

Mutation 必须是同步函数

mutations: {
  increment(state) {
    setTimeout(() => {
      state.count++
    }, 1000)
  }
}

这会导致无法追踪状态变化,调试工具无法正确记录。

异步操作应该放在 Action 中。

实战示例

用户管理

mutations: {
  SET_USER(state, user) {
    state.user = user
    state.isLoggedIn = !!user
  },
  
  SET_TOKEN(state, token) {
    state.token = token
    if (token) {
      localStorage.setItem('token', token)
    } else {
      localStorage.removeItem('token')
    }
  },
  
  CLEAR_USER(state) {
    state.user = null
    state.token = null
    state.isLoggedIn = false
    localStorage.removeItem('token')
  },
  
  UPDATE_USER_PROFILE(state, profile) {
    state.user = { ...state.user, ...profile }
  }
}

购物车

mutations: {
  ADD_TO_CART(state, product) {
    const existingItem = state.cart.items.find(item => item.id === product.id)
    
    if (existingItem) {
      existingItem.quantity++
    } else {
      state.cart.items.push({
        ...product,
        quantity: 1
      })
    }
    
    state.cart.total += product.price
    state.cart.count++
  },
  
  REMOVE_FROM_CART(state, productId) {
    const index = state.cart.items.findIndex(item => item.id === productId)
    
    if (index > -1) {
      const item = state.cart.items[index]
      state.cart.total -= item.price * item.quantity
      state.cart.count -= item.quantity
      state.cart.items.splice(index, 1)
    }
  },
  
  UPDATE_QUANTITY(state, { productId, quantity }) {
    const item = state.cart.items.find(item => item.id === productId)
    
    if (item) {
      const diff = quantity - item.quantity
      item.quantity = quantity
      state.cart.total += item.price * diff
      state.cart.count += diff
    }
  },
  
  CLEAR_CART(state) {
    state.cart.items = []
    state.cart.total = 0
    state.cart.count = 0
  }
}

列表操作

mutations: {
  SET_ITEMS(state, items) {
    state.items = items
  },
  
  ADD_ITEM(state, item) {
    state.items.push(item)
  },
  
  UPDATE_ITEM(state, { id, data }) {
    const index = state.items.findIndex(item => item.id === id)
    if (index > -1) {
      state.items.splice(index, 1, { ...state.items[index], ...data })
    }
  },
  
  DELETE_ITEM(state, id) {
    const index = state.items.findIndex(item => item.id === id)
    if (index > -1) {
      state.items.splice(index, 1)
    }
  },
  
  SET_PAGE(state, page) {
    state.pagination.page = page
  }
}

表单状态

mutations: {
  SET_FORM_DATA(state, { field, value }) {
    state.form[field] = value
  },
  
  SET_FORM_ERRORS(state, errors) {
    state.formErrors = errors
  },
  
  CLEAR_FORM_ERRORS(state) {
    state.formErrors = {}
  },
  
  RESET_FORM(state) {
    state.form = { ...state.initialForm }
    state.formErrors = {}
  }
}

UI 状态

mutations: {
  SET_LOADING(state, { key, value }) {
    state.loading[key] = value
  },
  
  SET_MODAL(state, { name, visible }) {
    state.modals[name] = visible
  },
  
  SET_THEME(state, theme) {
    state.theme = theme
    document.documentElement.setAttribute('data-theme', theme)
  },
  
  TOGGLE_SIDEBAR(state) {
    state.sidebarCollapsed = !state.sidebarCollapsed
  }
}

Mutation 设计原则

单一职责

每个 mutation 只做一件事:

mutations: {
  SET_USER(state, user) {
    state.user = user
  },
  SET_TOKEN(state, token) {
    state.token = token
  }
}

命名规范

使用大写蛇形命名:

SET_USER
UPDATE_CART
DELETE_ITEM
CLEAR_FORM

避免复杂逻辑

Mutation 应该简单直接:

mutations: {
  SET_USER(state, user) {
    state.user = user
  }
}

复杂逻辑放在 Action 中:

actions: {
  async login({ commit }, credentials) {
    const response = await api.login(credentials)
    commit('SET_USER', response.user)
    commit('SET_TOKEN', response.token)
  }
}

小结

Mutation 要点:

  1. 同步更新:Mutation 必须是同步函数
  2. 提交方式:commit 或 mapMutations
  3. 载荷传递:支持单个参数或对象
  4. 常量命名:使用常量提高可维护性
  5. 单一职责:每个 mutation 只做一件事