|
@@ -1,135 +1,134 @@
|
|
# Actions
|
|
# Actions
|
|
|
|
|
|
-Actions 是用于 dispatch mutations 的函数。Actions 可以是异步的,一个 action 可以 dispatch 多个 mutations.
|
|
|
|
-
|
|
|
|
-一个 action 描述了有什么事情应该发生,把本应该在组件中调用的逻辑细节抽象出来。当一个组件需要做某件事时,只需要调用一个 action —— 组件本身并不需要关心具体的后果:不需要提供回调函数也不需要期待返回值,因为 actions 的结果一定是 state 产生了变化,而 state 一旦变化,便会触发组件的 DOM 更新。 这样,组件便完全和 action 的具体逻辑解耦了。
|
|
|
|
-
|
|
|
|
-因此,我们通常在 actions 中做 API 相关的请求。通过 actions 的封装,我们使得组件和 mutations 都不需要关心这些异步逻辑。
|
|
|
|
-
|
|
|
|
> Vuex actions 和 Flux 中的 "action creators" 是等同的概念,但是我觉得这个定义常让人感到困惑(比如分不清 actions 和 action creators)。
|
|
> Vuex actions 和 Flux 中的 "action creators" 是等同的概念,但是我觉得这个定义常让人感到困惑(比如分不清 actions 和 action creators)。
|
|
|
|
|
|
-### 简单的 Actions
|
|
|
|
-
|
|
|
|
-最简单的情况下,一个 action 即触发一个 mutation。Vuex 提供一个快捷的方式去定义这样的 actions:
|
|
|
|
|
|
+Actions 是用于分发 mutations 的函数。按照惯例,Vuex actions 的第一个参数是 store 实例,附加上可选的自定义参数。
|
|
|
|
|
|
``` js
|
|
``` js
|
|
-const store = new Vuex.Store({
|
|
|
|
- state: {
|
|
|
|
- count: 1
|
|
|
|
- },
|
|
|
|
- mutations: {
|
|
|
|
- INCREMENT (state, x) {
|
|
|
|
- state.count += x
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- actions: {
|
|
|
|
- // 快捷定义
|
|
|
|
- // 只要提供 mutation 名
|
|
|
|
- increment: 'INCREMENT'
|
|
|
|
- }
|
|
|
|
-})
|
|
|
|
|
|
+// 最简单的 action
|
|
|
|
+function increment (store) {
|
|
|
|
+ store.dispatch('INCREMENT')
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 带附加参数的 action
|
|
|
|
+// 使用 ES2015 参数解构
|
|
|
|
+function incrementBy ({ dispatch }, amount) {
|
|
|
|
+ dispatch('INCREMENT', amount)
|
|
|
|
+}
|
|
```
|
|
```
|
|
|
|
|
|
-调用 action:
|
|
|
|
|
|
+乍一眼看上去感觉多此一举,我们直接分发 mutations 岂不更方便?实际上并非如此,还记得 **mutations 必须同步执行**这个限制么?Actions 就不受约束!我们可以在 action 内部执行**异步**操作:
|
|
|
|
|
|
``` js
|
|
``` js
|
|
-store.actions.increment(1)
|
|
|
|
|
|
+function incrementAsync ({ dispatch }) {
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ dispatch('INCREMENT')
|
|
|
|
+ }, 1000)
|
|
|
|
+}
|
|
```
|
|
```
|
|
|
|
|
|
-这相当于调用:
|
|
|
|
|
|
+来看一个更加实际的购物车示例,涉及到**调用异步 API** 和 **分发多重 mutations**:
|
|
|
|
+
|
|
|
|
|
|
``` js
|
|
``` js
|
|
-store.dispatch('INCREMENT', 1)
|
|
|
|
|
|
+function checkout ({ dispatch, state }, products) {
|
|
|
|
+ // 把当前购物车的物品备份起来
|
|
|
|
+ const savedCartItems = [...state.cart.added]
|
|
|
|
+ // 发出检出请求,然后乐观地清空购物车
|
|
|
|
+ dispatch(types.CHECKOUT_REQUEST)
|
|
|
|
+ // 购物 API 接受一个成功回调和一个失败回调
|
|
|
|
+ shop.buyProducts(
|
|
|
|
+ products,
|
|
|
|
+ // 成功操作
|
|
|
|
+ () => dispatch(types.CHECKOUT_SUCCESS),
|
|
|
|
+ // 失败操作
|
|
|
|
+ () => dispatch(types.CHECKOUT_FAILURE, savedCartItems)
|
|
|
|
+ )
|
|
|
|
+}
|
|
```
|
|
```
|
|
|
|
|
|
-注意所有传递给 action 的参数同样会传递给 mutation handler.
|
|
|
|
|
|
+请谨记一点,必须通过分发 mutations 来处理调用异步 API 的结果,而不是依赖返回值或者是传递回调来处理结果。基本原则就是:**Actions 除了分发 mutations 应当尽量避免其他副作用**。
|
|
|
|
|
|
-### 正常 Actions
|
|
|
|
|
|
+### 在组件中调用 Actions
|
|
|
|
|
|
-对于包含逻辑或是异步操作的 actions,则用函数来定义。Actions 函数获得的第一个参数永远是其所属的 store 实例:
|
|
|
|
|
|
+你可能发现了 action 函数必须依赖 store 实例才能执行。从技术上讲,我们可以在组件的方法内部调用 `action(this.$store)` 来触发一个 action,但这样写起来有失优雅。更好的做法是把 action 暴露到组件的方法中,便可以直接在模板中引用它。我们可以使用 `vuex.actions` 选项来这么做:
|
|
|
|
|
|
``` js
|
|
``` js
|
|
-const store = new Vuex.Store({
|
|
|
|
- state: {
|
|
|
|
- count: 1
|
|
|
|
- },
|
|
|
|
- mutations: {
|
|
|
|
- INCREMENT (state, x) {
|
|
|
|
- state += x
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- actions: {
|
|
|
|
- incrementIfOdd: (store, x) => {
|
|
|
|
- if ((store.state.count + 1) % 2 === 0) {
|
|
|
|
- store.dispatch('INCREMENT', x)
|
|
|
|
- }
|
|
|
|
|
|
+// 组件内部
|
|
|
|
+import { incrementBy } from './actions'
|
|
|
|
+
|
|
|
|
+const vm = new Vue({
|
|
|
|
+ vuex: {
|
|
|
|
+ getters: { ... }, // state getters
|
|
|
|
+ actions: {
|
|
|
|
+ incrementBy // ES6 同名对象字面量缩写
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
```
|
|
```
|
|
|
|
|
|
-通常我们会用 ES6 的参数解构 (arguments destructuring) 语法来使得函数体更简洁:
|
|
|
|
|
|
+上述代码所做的就是把原生的 `incrementBy` action 绑定到组件的 store 实例中,暴露给组件一个 `vm.increamentBy` 实例方法。所有传递给 `vm.increamentBy` 的参数变量都会排列在 store 变量后面然后一起传递给原生的 action 函数,所以调用:
|
|
|
|
|
|
``` js
|
|
``` js
|
|
-// ...
|
|
|
|
-actions: {
|
|
|
|
- incrementIfOdd: ({ dispatch, state }, x) => {
|
|
|
|
- if ((state.count + 1) % 2 === 0) {
|
|
|
|
- dispatch('INCREMENT', x)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+vm.incrementBy(1)
|
|
```
|
|
```
|
|
|
|
|
|
-同时,简单 actions 的快捷定义其实只是如下函数的语法糖:
|
|
|
|
|
|
+等价于:
|
|
|
|
|
|
``` js
|
|
``` js
|
|
-actions: {
|
|
|
|
- increment: 'INCREMENT'
|
|
|
|
-}
|
|
|
|
-// ... 上面的定义等同于:
|
|
|
|
-actions: {
|
|
|
|
- increment: ({ dispatch }, ...payload) => {
|
|
|
|
- dispatch('INCREMENT', ...payload)
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+incrementBy(vm.$store, 1)
|
|
```
|
|
```
|
|
|
|
|
|
-### 异步 Actions
|
|
|
|
|
|
+虽然多写了一些代码,但是组件的模板中调用 action 更加省力了:
|
|
|
|
+
|
|
|
|
+``` html
|
|
|
|
+<button v-on:click="incrementBy(1)">increment by one</button>
|
|
|
|
+```
|
|
|
|
|
|
-异步 actions 同样使用函数定义:
|
|
|
|
|
|
+还可以给 action 取别名:
|
|
|
|
|
|
``` js
|
|
``` js
|
|
-// ...
|
|
|
|
-actions: {
|
|
|
|
- incrementAsync: ({ dispatch }, x) => {
|
|
|
|
- setTimeout(() => {
|
|
|
|
- dispatch('INCREMENT', x)
|
|
|
|
- }, 1000)
|
|
|
|
|
|
+// 组件内部
|
|
|
|
+import { incrementBy } from './actions'
|
|
|
|
+
|
|
|
|
+const vm = new Vue({
|
|
|
|
+ vuex: {
|
|
|
|
+ getters: { ... },
|
|
|
|
+ actions: {
|
|
|
|
+ plus: incrementBy // 取别名
|
|
|
|
+ }
|
|
}
|
|
}
|
|
-}
|
|
|
|
|
|
+})
|
|
```
|
|
```
|
|
|
|
|
|
-举个更实在的例子,比如一个购物车。当用户结账时,我们可能需要在 checkout 这一个 action 中触发多个不同的 mutations:一个在开始检查购物车时触发,一个在成功后触发,还有一个在失败时触发。
|
|
|
|
|
|
+这样 action 就会被绑定为 `vm.plus` 而不是 `vm.increamentBy` 了。
|
|
|
|
+
|
|
|
|
+### 内联 Actions
|
|
|
|
+
|
|
|
|
+如果一个 action 只跟一个组件相关,可以采用简写语法把它定义成一行:
|
|
|
|
|
|
``` js
|
|
``` js
|
|
-// ...
|
|
|
|
-actions: {
|
|
|
|
- checkout: ({ dispatch, state }, products) => {
|
|
|
|
- // 保存结账前的购物车内容
|
|
|
|
- const savedCartItems = [...state.cart.added]
|
|
|
|
- // 发出结账的请求,并且清空购物车
|
|
|
|
- dispatch(types.CHECKOUT_REQUEST)
|
|
|
|
- // 假设我们的后台 API 接受一个成功回调和一个错误回调
|
|
|
|
- shop.buyProducts(
|
|
|
|
- products,
|
|
|
|
- // 结账成功
|
|
|
|
- () => dispatch(types.CHECKOUT_SUCCESS),
|
|
|
|
- // 结账失败,将购物车恢复到结账之前的状态
|
|
|
|
- () => dispatch(types.CHECKOUT_FAILURE, savedCartItems)
|
|
|
|
- )
|
|
|
|
|
|
+const vm = new Vue({
|
|
|
|
+ vuex: {
|
|
|
|
+ getters: { ... },
|
|
|
|
+ actions: {
|
|
|
|
+ plus: ({ dispatch }) => dispatch('INCREMENT')
|
|
|
|
+ }
|
|
}
|
|
}
|
|
-}
|
|
|
|
|
|
+})
|
|
```
|
|
```
|
|
|
|
|
|
-这里有相对复杂的异步逻辑,但是购物车的组件依然只需要简单地调用 `store.actions.checkout(products)` 即可.
|
|
|
|
|
|
+### 绑定所有 Actions
|
|
|
|
+
|
|
|
|
+如果你想简单地把所有引入的 actions 都绑定到组件中:
|
|
|
|
+
|
|
|
|
+``` js
|
|
|
|
+import * as actions from './actions'
|
|
|
|
+
|
|
|
|
+const vm = new Vue({
|
|
|
|
+ vuex: {
|
|
|
|
+ getters: { ... },
|
|
|
|
+ actions // 绑定所有 actions
|
|
|
|
+ }
|
|
|
|
+})
|
|
|
|
+```
|