|
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 的第一个参数是 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 |
| -} |
111 |
| -``` |
| 101 | +}) |
| 102 | + |
| 103 | +这样 action 就会被绑定为 `vm.plus` 而不是 `vm.increamentBy` 了。 |
112 | 104 |
|
113 |
| -举个更实在的例子,比如一个购物车。当用户结账时,我们可能需要在 checkout 这一个 action 中触发多个不同的 mutations:一个在开始检查购物车时触发,一个在成功后触发,还有一个在失败时触发。 |
| 105 | +### 内联 Actions |
| 106 | + |
| 107 | +如果一个 action 只跟一个组件相关,可以采用简写语法把它定义成一行: |
114 | 108 |
|
115 | 109 | ``` 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 |
| - ) |
| 110 | +const vm = new Vue({ |
| 111 | + vuex: { |
| 112 | + getters: { ... }, |
| 113 | + actions: { |
| 114 | + plus: ({ dispatch }) => dispatch('INCREMENT') |
| 115 | + } |
131 | 116 | }
|
132 |
| -} |
| 117 | +}) |
133 | 118 | ```
|
134 | 119 |
|
135 |
| -这里有相对复杂的异步逻辑,但是购物车的组件依然只需要简单地调用 `store.actions.checkout(products)` 即可. |
| 120 | +### 绑定所有 Actions |
| 121 | + |
| 122 | +如果你想简单地把所有引入的 actions 都绑定到组件中: |
| 123 | + |
| 124 | +``` js |
| 125 | +import * as actions from './actions' |
| 126 | +
|
| 127 | +const vm = new Vue({ |
| 128 | + vuex: { |
| 129 | + getters: { ... }, |
| 130 | + actions // 绑定所有 actions |
| 131 | + } |
| 132 | +}) |
| 133 | +``` |
0 commit comments