vuex 基本入门和使用(四)-关于 action

vuex 基本入门和使用(四)-关于 action

vuex 版本为 ^2.3.1,按照我本身的理解来整理vuex。

关于 action

Action 相似于 mutation,不一样在于:vue

  • 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。git

这是 jsrun 的例子: https://jsrun.net/avqKp

备注:参数解构参考地址https://github.com/lukehoban/es6features#destructuring,参数解构能够将对象或者数组按照必定的规则解构出来直接使用。es6

分发 action

以前说过,mutation 必须同步执行,但 action 不须要,因此二者结合使用,可以实现一个可以异步执行的 mutation。github

形象地来讲就是异步执行的 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
})

异步分发样例:vuex

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 的操做方法。
  • 这里整个流程是:segmentfault

    • 保存购物车的物品 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 是一个数组,以下:promise

// 转换前
[
      // 这是没额外参数的(没载荷)
      '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 才会执行。

参考:

相关文章
相关标签/搜索