2

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.statecontext.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 才会执行。

参考:


线上猛如虎
2.2k 声望178 粉丝

你们都有梦想的,是吧.怀抱着梦想并且正朝着梦想努力的人,寻找着梦想的人,我想为这些人加油呐喊!