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 纔會執行。

參考:

相關文章
相關標籤/搜索