Mutations

唯一真正改变 Vuex store 状态的方式就是通过提交一个 mutation。Vuex 的 muatation 跟事件非常像:每个 mutation 有一个 类型(字符串) 和 一个 处理器。处理器方法就是真正修改状态的地方,并且它会接收 state 作为第一个参数:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 修改状态
      state.count++
    }
  }
})

你不能直接调用一个 mutation 处理器,这里的做法更像是事件注册:『当一个 increment 类型的 mutation 触发时,调用这个处理器。』要调用一个 mutation,就得用调用 store.commit mutation 的类型。

store.commit('increment')

Commit with Payload

你可以给 store.commit 传入额外的参数,称之为 mutation 的 payload

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('INCREMENT', 10)

大多数情况下,payload 应该是一个对象,这样就可以包含多个字段了,然后记录 mutation 时也更方便描述:

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

对象风格的 Commit

另一种提交 mutation 的方式就是直接使用一个带有 type 属性的对象:

store.commit({
  type: 'increment',
  amount: 10
})

当使用对象风格的提交,整个对象会被传入作为 mutation 处理器的 payload,所以处理器跟之前一样:

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

Silent Commit

注意:一旦我们在 devtools 中实现了 mutation 过滤,那么这个功能很可能就会被移除。

默认情况下,每个提交的 mutation 会发送到插件(如 devtools)。不过有些情况,你不希望插件记录每一个状态变化。像是短时间内多次提交到 store 或者是轮询操作,并不都需要记录跟踪。那么你可以传递第三个参数给 store.commit,让该 mutation 在插件中 "安静"。

store.commit('increment', {
  amount: 1
}, { silent: true })

// 对象风格的提交
store.commit({
  type: 'increment',
  amount: 1
}, { silent: true })

Mutations 遵循 Vue 的响应式机制

Vuex 的 store 状态通过 Vue 作成响应式的,当我们修改状态的时候,组件侦测到状态变化后会自动更新。这也意味着,Vuex 的 mutation 要跟单纯的 Vue 一样遵循相同的响应式规则约束。

  1. 最好在初始化 store 的初始状态时,直接给出所有需要的字段。

  2. 当给一个对象添加新属性时,你要

    • 使用 Vue.set(obj, 'newProp', 123),或者 -

    • 用一个新对象替换。例如使用 stage-2 阶段的对象解构赋值语法,我们就能像下面这样编码:

      state.obj = { ...state.obj, newProp: 123 }
      

使用不变常量命名 Mutation

在各种不同的 Flux 实现中,使用不变常量来命名 Mutation 类型是常见的方式。这就可以让代码充分使用 linter 等工具,然后把所有常量放到一个文件中,让你的伙伴们看一眼就知道整个应用的 mutation:

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 我们使用 ES2015 计算属性名的特性
    // 使用一个常量作为方法名
    [SOME_MUTATION] (state) {
      // 修改状态
    }
  }
})

是否使用常量是见仁见智的 —— 在多人开发的大型项目中是有用的,如果你不喜欢,完全可以不用。

Mutation 一定是同步的

要记住一个重要的规则, mutation 处理器方法必须是同步的。为什么呢?看看下面的例子:

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

设想一下我们正在调试程序,然后查看 devtools 的 mutation 记录。对于每一个 mutation 记录, devtool 会捕捉状态的『前』、『后』快照,不过上面例子中,mutation 里的异步回调导致无法实现:回调在 mutation 提交后没有立刻执行,并且 devtool 无法知道这个回调何时执行 —— 本质上,所有在回调中执行的修改都是无法跟踪的!

在组件中提交 Mutation

你可以在组件中通过 this.$store.commit('xxx') 来提交 mutation,或者是使用 mapMutations 工具来映射组件的方法到 store.commit 调用(需要在根组件配置 store):

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment' // 映射 this.increment() 为 this.$store.commit('increment')
    ]),
    ...mapMutations({
      add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')
    })
  }
}

走进 Action

异步修改状态会让程序变得很难理解。例如,当你调用两个调用方法,异步修改状态,你怎么知道他们什么时候被调用,哪个回调先调用呢?这正是我们为什么要这两个概念区分开来的原因。在 Vuex 里,mutations 是同步的事务

store.commit('increment')
// "increate" mutation 引起的所有状态变化any state change that the "increment" mutation may cause
// 都应在这一时刻完成

想要处理异步操作,那我们来介绍下 Actions