vuex 基本入门和使用(四)-关于 action
vuex 版本为^2.3.1
,按照我自己的理解来整理vuex。
关于 action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
我的理解就是,mutation是一把刀,action 是一个人,这个人可以同步耍刀,也可以异步耍刀,但是刀只能同步劈或者切或者砍。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
// context 对象的使用跟 store 对象的使用类似
increment (context) {
// 直接可以 commit(原来是this.$store.commit)
context.commit('increment')
}
// 在 es2015下,可以使用参数解构的写法
increment ({ commit }) { //直接解构出 commit 来进行 mutation的提交
commit('increment')
}
}
})
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters。
这是 jsrun 的例子:https://jsrun.net/avqKp
备注:参数解构参考地址https://github.com/lukehoban/es6features#destructuring,参数解构可以将对象或者数组按照一定的规则解构出来直接使用。
分发 action
之前说过,mutation 必须同步执行,但 action 不需要,所以两者结合使用,能够实现一个能够异步执行的 mutation。
形象地来说就是异步执行的 action 去操作同步执行的 mutation。
// 初始化 action
actions: {
// 异步操作
incrementAsync ({ commit }) {
setTimeout(() => {
// 异步 commit
commit('increment')
}, 1000)
}
}
// 一般形式分发 action
store.dispatch('increment')
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
// 传入包含 type 属性的对象(类似 mutation)
type: 'incrementAsync',
amount: 10
})
异步分发样例:
actions: {
// 解构 context 对象里面的 commit 和 state 来使用
checkout ({ commit, state }, products) {
// 把当前购物车的物品备份起来
const savedCartItems = [...state.cart.added]
// 清空购物车
commit(types.CHECKOUT_REQUEST)
// 购物 API 接受一个成功回调和一个失败回调
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失败操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
- 这里有一个 actions 的对象(actions 都是以对象组成的),里面有一个 checkout 的操作方法。
-
这里整个流程是:
- 保存购物车的物品 savedCartItems
- 清空购物车
- 提交结账请求给后端结账服务器(这是异步的请求,通过回调确认结账是否成功),如果成功则做成功购买状态变更,否则做失败购物状态变更,并且重新添加购物车内容
这个例子里面其实主要是说明可以异步操作,其他的逻辑可以暂时不用理会。
在组件中分发 Action
你在组件中使用 this.$store.dispatch('xxx')
分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch
调用(需要先在根节点注入 store):
这个就很类似之前的mapMutations了
首先:normalizeMap会将actions格式化为一个数组:
function normalizeMap (map) {
// 判断是否数组,并且最终返回也是一个数组
return Array.isArray(map)
// 是数组就直接 map 循环
? map.map(key => ({ key, val: key }))
// 是对象就将 key拿出来,然后再进行 map 循环
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
例如传入的actions 是一个数组,如下:
// 转换前
[
// 这是没额外参数的(没载荷)
'increment',
// 这是有额外参数的(有载荷)
'incrementBy'
]
// 那么被normalizeMap转换后:
// 即转换为{ key, val: key })
[
{
key, // key 是increment
val: key // val是increment
},
// 这里虽然说有额外参数传入,但是这个参数并没有在转换中处理
{
key, // key 是incrementBy
val: key // val是incrementBy
},
//.....
]
例如传入的actions 是一个对象,如下:
// 转换前
{
add: 'increment'
}
// 那么被normalizeMap转换后:
// 即转换为{ key, val: key })
{
key, // key 是addAlias
val: map[key] // val 是对象的 key 属性的值,就是 'increment'
}
然后看回去 vuex 的源代码关于mapActions的部分:
var mapActions = normalizeNamespace(function (namespace, actions) {
var res = {};
normalizeMap(actions).forEach(function (ref) {
var key = ref.key;
var val = ref.val;
res[key] = function mappedAction () {
// 也是跟 mapmutation类似,获取载荷参数
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
// 保存当前 vuex 的 dispatch 方便后面处理
var dispatch = this.$store.dispatch;
// 省略命名空间部分
return typeof val === 'function'
// 是函数就直接执行
? val.apply(this, [dispatch].concat(args))
// 不是函数就用 dispatch 执行
: dispatch.apply(this.$store, [val].concat(args))
};
});
return res
});
dispatch 是 action 执行的固定语法,跟 mutation 的 commit 类似
那么回归到实际转换效果,如下:
// 需要引入mapActions才可以使用
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
// 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
'increment',
// 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
'incrementBy'
]),
// 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
...mapActions({
add: 'increment'
})
}
}
对比着 mutation 来看,就非常好理解了。
这是 jsrun 的例子:https://jsrun.net/jwqKp
组合 Action
Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?
首先,你需要明白 store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch
仍旧返回 Promise:
换言之,就是store.dispatch
能够处理 promise,并且也会返回 promise,所以能够在异步中处理逻辑。
// 初始化 actions
actions: {
actionA ({ commit }) {
// 返回一个 promise 对象
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve() // 跟一般 promise 的使用差别不大
}, 1000)
})
}
}
// 使用 actions
store.dispatch('actionA').then(() => { // 可以使用 then 了
// ...
})
// 在 actionB 里面分发 actionA
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
总的来说就是 action 支持返回一个 promise 来做处理,这样就可以很好的使用 promise 来进行异步操作了。
如果我们利用 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())
}
}
ES2017 标准引入了 async 函数,async 函数会让异步代码更加直观,如果不用可以不管。
需要注意的是,一个 store.dispatch
在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
参考:
- Vuex 2.0 源码分析(下)_慕课手记
- 这篇文章描述更为清楚,可以先看这篇:https://segmentfault.com/a/1190000011668825
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。