|
1 | 1 | # Actions |
2 | 2 |
|
3 | | -Actions 是用于 dispatch mutations 的函数。Actions 可以是异步的,一个 action 可以 dispatch 多个 mutations. |
4 | | - |
5 | | -一个 action 描述了有什么事情应该发生,把本应该在组件中调用的逻辑细节抽象出来。当一个组件需要做某件事时,只需要调用一个 action —— 组件本身并不需要关心具体的后果:不需要提供回调函数也不需要期待返回值,因为 actions 的结果一定是 state 产生了变化,而 state 一旦变化,便会触发组件的 DOM 更新。 这样,组件便完全和 action 的具体逻辑解耦了。 |
6 | | - |
7 | | -因此,我们通常在 actions 中做 API 相关的请求。通过 actions 的封装,我们使得组件和 mutations 都不需要关心这些异步逻辑。 |
8 | | - |
9 | 3 | > Vuex actions 和 Flux 中的 "action creators" 是等同的概念,但是我觉得这个定义常让人感到困惑(比如分不清 actions 和 action creators)。 |
10 | 4 |
|
11 | | -### 简单的 Actions |
12 | | - |
13 | | -最简单的情况下,一个 action 即触发一个 mutation。Vuex 提供一个快捷的方式去定义这样的 actions: |
| 5 | +Actions 是用于分发 mutations 的函数。按照惯例,Vuex actions 的第一个参数是 store 实例,附加上可选的自定义参数。 |
14 | 6 |
|
15 | 7 | ``` js |
16 | | -const store = new Vuex.Store({ |
17 | | - state: { |
18 | | - count: 1 |
19 | | - }, |
20 | | - mutations: { |
21 | | - INCREMENT (state, x) { |
22 | | - state.count += x |
23 | | - } |
24 | | - }, |
25 | | - actions: { |
26 | | - // 快捷定义 |
27 | | - // 只要提供 mutation 名 |
28 | | - increment: 'INCREMENT' |
29 | | - } |
30 | | -}) |
| 8 | +// 最简单的 action |
| 9 | +function increment (store) { |
| 10 | + store.dispatch('INCREMENT') |
| 11 | +} |
| 12 | + |
| 13 | +// 带附加参数的 action |
| 14 | +// 使用 ES2015 参数解构 |
| 15 | +function incrementBy ({ dispatch }, amount) { |
| 16 | + dispatch('INCREMENT', amount) |
| 17 | +} |
31 | 18 | ``` |
32 | 19 |
|
33 | | -调用 action: |
| 20 | +乍一眼看上去感觉多此一举,我们直接分发 mutations 岂不更方便?实际上并非如此,还记得 **mutations 必须同步执行**这个限制么?Actions 就不受约束!我们可以在 action 内部执行**异步**操作: |
34 | 21 |
|
35 | 22 | ``` js |
36 | | -store.actions.increment(1) |
| 23 | +function incrementAsync ({ dispatch }) { |
| 24 | + setTimeout(() => { |
| 25 | + dispatch('INCREMENT') |
| 26 | + }, 1000) |
| 27 | +} |
37 | 28 | ``` |
38 | 29 |
|
39 | | -这相当于调用: |
| 30 | +来看一个更加实际的购物车示例,涉及到**调用异步 API** 和 **分发多重 mutations**: |
| 31 | + |
40 | 32 |
|
41 | 33 | ``` js |
42 | | -store.dispatch('INCREMENT', 1) |
| 34 | +function checkout ({ dispatch, state }, products) { |
| 35 | + // 把当前购物车的物品备份起来 |
| 36 | + const savedCartItems = [...state.cart.added] |
| 37 | + // 发出检出请求,然后乐观地清空购物车 |
| 38 | + dispatch(types.CHECKOUT_REQUEST) |
| 39 | + // 购物 API 接受一个成功回调和一个失败回调 |
| 40 | + shop.buyProducts( |
| 41 | + products, |
| 42 | + // 成功操作 |
| 43 | + () => dispatch(types.CHECKOUT_SUCCESS), |
| 44 | + // 失败操作 |
| 45 | + () => dispatch(types.CHECKOUT_FAILURE, savedCartItems) |
| 46 | + ) |
| 47 | +} |
43 | 48 | ``` |
44 | 49 |
|
45 | | -注意所有传递给 action 的参数同样会传递给 mutation handler. |
| 50 | +请谨记一点,必须通过分发 mutations 来处理调用异步 API 的结果,而不是依赖返回值或者是传递回调来处理结果。基本原则就是:**Actions 除了分发 mutations 应当尽量避免其他副作用**。 |
46 | 51 |
|
47 | | -### 正常 Actions |
| 52 | +### 在组件中调用 Actions |
48 | 53 |
|
49 | | -对于包含逻辑或是异步操作的 actions,则用函数来定义。Actions 函数获得的第一个参数永远是其所属的 store 实例: |
| 54 | +你可能发现了 action 函数必须依赖 store 实例才能执行。从技术上讲,我们可以在组件的方法内部调用 `action(this.$store)` 来触发一个 action,但这样写起来有失优雅。更好的做法是把 action 暴露到组件的方法中,便可以直接在模板中引用它。我们可以使用 `vuex.actions` 选项来这么做: |
50 | 55 |
|
51 | 56 | ``` js |
52 | | -const store = new Vuex.Store({ |
53 | | - state: { |
54 | | - count: 1 |
55 | | - }, |
56 | | - mutations: { |
57 | | - INCREMENT (state, x) { |
58 | | - state += x |
59 | | - } |
60 | | - }, |
61 | | - actions: { |
62 | | - incrementIfOdd: (store, x) => { |
63 | | - if ((store.state.count + 1) % 2 === 0) { |
64 | | - store.dispatch('INCREMENT', x) |
65 | | - } |
| 57 | +// 组件内部 |
| 58 | +import { incrementBy } from './actions' |
| 59 | + |
| 60 | +const vm = new Vue({ |
| 61 | + vuex: { |
| 62 | + getters: { ... }, // state getters |
| 63 | + actions: { |
| 64 | + incrementBy // ES6 同名对象字面量缩写 |
66 | 65 | } |
67 | 66 | } |
68 | 67 | }) |
69 | 68 | ``` |
70 | 69 |
|
71 | | -通常我们会用 ES6 的参数解构 (arguments destructuring) 语法来使得函数体更简洁: |
| 70 | +上述代码所做的就是把原生的 `incrementBy` action 绑定到组件的 store 实例中,暴露给组件一个 `vm.increamentBy` 实例方法。所有传递给 `vm.increamentBy` 的参数变量都会排列在 store 变量后面然后一起传递给原生的 action 函数,所以调用: |
72 | 71 |
|
73 | 72 | ``` js |
74 | | -// ... |
75 | | -actions: { |
76 | | - incrementIfOdd: ({ dispatch, state }, x) => { |
77 | | - if ((state.count + 1) % 2 === 0) { |
78 | | - dispatch('INCREMENT', x) |
79 | | - } |
80 | | - } |
81 | | -} |
| 73 | +vm.incrementBy(1) |
82 | 74 | ``` |
83 | 75 |
|
84 | | -同时,简单 actions 的快捷定义其实只是如下函数的语法糖: |
| 76 | +等价于: |
85 | 77 |
|
86 | 78 | ``` js |
87 | | -actions: { |
88 | | - increment: 'INCREMENT' |
89 | | -} |
90 | | -// ... 上面的定义等同于: |
91 | | -actions: { |
92 | | - increment: ({ dispatch }, ...payload) => { |
93 | | - dispatch('INCREMENT', ...payload) |
94 | | - } |
95 | | -} |
| 79 | +incrementBy(vm.$store, 1) |
96 | 80 | ``` |
97 | 81 |
|
98 | | -### 异步 Actions |
| 82 | +虽然多写了一些代码,但是组件的模板中调用 action 更加省力了: |
| 83 | + |
| 84 | +``` html |
| 85 | +<button v-on:click="incrementBy(1)">increment by one</button> |
| 86 | +``` |
99 | 87 |
|
100 | | -异步 actions 同样使用函数定义: |
| 88 | +还可以给 action 取别名: |
101 | 89 |
|
102 | 90 | ``` js |
103 | | -// ... |
104 | | -actions: { |
105 | | - incrementAsync: ({ dispatch }, x) => { |
106 | | - setTimeout(() => { |
107 | | - dispatch('INCREMENT', x) |
108 | | - }, 1000) |
| 91 | +// 组件内部 |
| 92 | +import { incrementBy } from './actions' |
| 93 | + |
| 94 | +const vm = new Vue({ |
| 95 | + vuex: { |
| 96 | + getters: { ... }, |
| 97 | + actions: { |
| 98 | + plus: incrementBy // 取别名 |
| 99 | + } |
109 | 100 | } |
110 | | -} |
| 101 | +}) |
111 | 102 | ``` |
112 | 103 |
|
113 | | -举个更实在的例子,比如一个购物车。当用户结账时,我们可能需要在 checkout 这一个 action 中触发多个不同的 mutations:一个在开始检查购物车时触发,一个在成功后触发,还有一个在失败时触发。 |
| 104 | +这样 action 就会被绑定为 `vm.plus` 而不是 `vm.increamentBy` 了。 |
| 105 | + |
| 106 | +### 内联 Actions |
| 107 | + |
| 108 | +如果一个 action 只跟一个组件相关,可以采用简写语法把它定义成一行: |
114 | 109 |
|
115 | 110 | ``` js |
116 | | -// ... |
117 | | -actions: { |
118 | | - checkout: ({ dispatch, state }, products) => { |
119 | | - // 保存结账前的购物车内容 |
120 | | - const savedCartItems = [...state.cart.added] |
121 | | - // 发出结账的请求,并且清空购物车 |
122 | | - dispatch(types.CHECKOUT_REQUEST) |
123 | | - // 假设我们的后台 API 接受一个成功回调和一个错误回调 |
124 | | - shop.buyProducts( |
125 | | - products, |
126 | | - // 结账成功 |
127 | | - () => dispatch(types.CHECKOUT_SUCCESS), |
128 | | - // 结账失败,将购物车恢复到结账之前的状态 |
129 | | - () => dispatch(types.CHECKOUT_FAILURE, savedCartItems) |
130 | | - ) |
| 111 | +const vm = new Vue({ |
| 112 | + vuex: { |
| 113 | + getters: { ... }, |
| 114 | + actions: { |
| 115 | + plus: ({ dispatch }) => dispatch('INCREMENT') |
| 116 | + } |
131 | 117 | } |
132 | | -} |
| 118 | +}) |
133 | 119 | ``` |
134 | 120 |
|
135 | | -这里有相对复杂的异步逻辑,但是购物车的组件依然只需要简单地调用 `store.actions.checkout(products)` 即可. |
| 121 | +### 绑定所有 Actions |
| 122 | + |
| 123 | +如果你想简单地把所有引入的 actions 都绑定到组件中: |
| 124 | + |
| 125 | +``` js |
| 126 | +import * as actions from './actions' |
| 127 | + |
| 128 | +const vm = new Vue({ |
| 129 | + vuex: { |
| 130 | + getters: { ... }, |
| 131 | + actions // 绑定所有 actions |
| 132 | + } |
| 133 | +}) |
| 134 | +``` |
0 commit comments