Vuex核心思想之Mutation、action
一、Mutation——更改store的状态的唯一方法
在Vue中修改存储状态的方法仅限于发布mutation,在Vue中mutations相当于事件
Each mutation is accompanied by a string event type and a callback handler.
回调函数用于完成状态操作的任务位置,并且该函数接收state作为输入的第一个参数
const store = new Vuex.store({
state: {
count: 1
},
mutations: {
increment (state) {
//变更状态
state.count++
}
}
})
注意: 请注意,您不应直接调用mutations和increment;它们更适合作为事件注册使用。
要唤醒一个mutation handler,请您按照相应的type类型并调用store.commit方法。
注意: 请注意,您不应直接调用mutations和increment;它们更适合作为事件注册使用。
要唤醒一个mutation handler,请您按照相应的type类型并调用store.commit方法。
store.commit('increment') //之后打印store.state.count === 2
提交载荷(Payload)
你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload):
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10) //此时 count == 11
通常情况下,在设计系统时应考虑负载作为一个对象。这样做不仅有助于支持存储多个字段,并且能够让mutations变得更容易阅读。
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
store.commit({
type: 'increment',
amount: 10
})
当采用对象风格的提交方式时(或称基于对象的方式),该对象将作为主要载体被传递给 mutation 函数(或称mutation操作),因此 handler 保持不变:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
Mutation 需遵守 Vue 的响应规则
由于VUE store中的状态采用响应式布局,在变更状态下会自动更新相关的Vue组件。这进一步要求VUE中的mutation必须遵循与使用Vue相同的最佳实践。
最好提前在你的 store 中初始化好所有所需属性。
当需要在对象上添加新属性时,你应该
使用 Vue.set(obj, 'newProp', 123), 或者
以新对象替换老对象。例如,利用对象展开运算符我们可以这样写:
state.obj = { ...state.obj, newProp: 123 }
使用常量替代 Mutation 事件类型
采用将 constant 替代 event mutation 类型的做法,在 Flux 的各种实现中是一种常见策略。这不仅能够使得 linting 工具发挥效用,并且将这些 constant 存储于独立文件中能够让团队成员清楚了解整个 application 包含的所有 event 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) {
// mutate state
}
}
})
Mutation 必须是同步函数
这条关键要点就是要求 mutation 必须与同步函数保持一致。为什么要这样做?请参考下面的例子。
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
假设我们现在正在调试一个应用程序,并且观察devtools中的Mutation日志记录。每当一条Mutation发生时都会被记录下来,并且为了捕捉到每个状态的变化情况, devtools需要捕获到前一状态和后一状态的具体快照信息。然而, 在上述示例中由于异步函数中的Callback触发使得这一过程无法实现:因为在Mutation触发的那一瞬间Callback还没有被调用,因此开发工具无法得知Callback何时开始执行,实质上任何在Callback内部执行的行为都无法被追踪回
在组件中提交 Mutation
您可以在组件中调用 this.$store.commit('xxx') 来提交 mutation,此外还可以通过 mapMutations 函数将组件中的 methods 映射至 store.commit 的调用(建议在根节点预先注入 store)。
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
到了此阶段,对状态管理的结构大概是这样的

二、action
Action 类似于 mutation,不同在于:
- 该Action 通过提交 request 来实现 mutation 而非直接修改 state。
- 该Action 允许执行任意异步操作。
让我们来注册一个简单的 action:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action 接收了一个与 Store 实例共享方法与属性的 Context 对象。因此你可以使用 context.commit 方法提交 mutation;或者你可以利用 context.state 和 context.getters 获取 state 以及 getters。当我们随后讨论 Modules 的时候,请注意这个 Context 对象并不是 Store 实例本身的原因。
实践中,我们常常利用数据解构来简化代码流程(尤其是我们在频繁调用 commit 的情况下):
actions: {
increment ({ commit }) {
commit('increment')
}
}
分发 Action
Action 通过 store.dispatch 方法触发:
store.dispatch('increment')
乍一看似乎多此一举, 我们直接分发 mutation 显得有些不够灵活. 其实不然, 你是否还记得 mutation 必须同步执行这一限制呢? Action 自由度极大! 在action内部可进行异步操作:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
Actions 支持同样的载荷方式和对象方式进行分发:
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
在组件中分发 Action
you can use this.$store.dispatch('xxx') to trigger actions within components or employ the mapActions helper function to remap component methods onto the store's dispatch mechanism. you must ensure that the root node has the store injected beforehand.
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
组合 Action
大多数 Action 都是异步设计的;那么我们又该如何判断一个 action 的完成时间?更重要的是,在处理更为复杂的异步流程时应该如何将多个 action 组合成一个整体流程?
第一步,请您了解该库中的store.dispatch方法的作用:当某个动作被触发时,并通过其对应的处理函数生成一个Promise。同时该方法自身也会生成并返回一个Promise。
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
现在你可以:
store.dispatch('actionA').then(() => {
// ...
})
在另外一个 action 中也可以:
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
最后,如果我们利用 async / await,我们可以如下组合 action:
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
在一个store.dispatch机制里,在各个不同的模块里能够同时启动多个action函数,并且只有当这些启动完成后才能处理返回的Promise。
疑问
- Mutation 为什么必须是同步函数?
