Vuex入門(5)—— 為什麽要用Action管理異步操作
Action 類似於 mutation,不同在於:
1.Action 提交的是 mutation,而不是直接變更狀態。
2.Action 可以包含任意異步操作。
官方給的定義我沒什麽意見,事實上我通過mutation異步操作,好像跟用action管理也沒什麽區別。關於為什麽要用Action管理異步操作,我會通過一個簡單的例子和一個復雜的例子來進行說明,事實上,如果初學者沒有考慮到實際場景的復雜情況,會覺得Action根本沒有一點軟用,這個時候就要把問題想得復雜一些了,然後才能看到Action的作用。
先來看一個簡單的例子,也是我對如果不用Action進行異步操作的一些初步探索。
第一步:我非常作死的重寫了mutation狀態管理器中對狀態操作的一些寫法,我使用了異步操作代替了之前的操作。
// mutation.js const increment = (state) => { setTimeout(() => { state.count++ }, 1000) } const decrement = (state) => { setTimeout(() => { state.count-- }, 2000) state.count-- } export {increment, decrement}
第二步:試驗一下能否成功
<template> <div> <button @click="decrement">-</button> <span>{{count}}</span> <button @click="increment">+</button> </div> </template> <script> import { mapState, mapMutations} from ‘vuex‘ export default { computed: { ...mapState([‘count‘]) }, methods: { ...mapMutations([‘increment‘, ‘decrement‘]), } } </script> <style> </style>
第三步:發現除了每次操作加減時候有一秒的延時,不管你怎麽操作,結果都是正確的,是符合社會主義核心價值觀的。
第四步:用Action處理異步操作(先得把之前作死改掉的mutation的代碼改回來)
// 正常的mutation const increment = (state) => { state.count++ } const decrement = (state) => { state.count-- } export {increment, decrement}
// action.js處理一些異步操作 // Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象 let incrementAsync = (content) => { setTimeout(() => { content.commit(‘increment‘) }, 1000) } let decrementAsync = (content) => { setTimeout(() => { content.commit(‘decrement‘) }, 1000) } export {incrementAsync, decrementAsync}
<template> <div> <button @click="decrementAsync">-</button> <span>{{count}}</span> <button @click="incrementAsync">+</button> </div> </template> <script> import { mapState, mapMutations, mapActions } from ‘vuex‘ export default { computed: { ...mapState([‘count‘]) }, methods: { ...mapMutations([‘increment‘, ‘decrement‘]), ...mapActions([‘incrementAsync‘, ‘decrementAsync‘]) //這裏用了輔助函數,不了解的可以看這個系列的第二篇文章 } } </script> <style> </style>
第五步:測試一下效果,你會感覺跟沒有用action直接用mutation的結果一毛一樣。再看一下官網說明。
所以,官網說的這句話有問題?
確實有問題,因為在mutation中執行異步操作並不會報錯,也能正確更改狀態,所以並不是所謂的“必須同步執行”這麽苛刻,只能說你最好不要這麽做。(這裏說一句題外話,我初學vuex的時候,大概去年,在mutaition中一旦有異步操作,控制臺立馬就會有警告,不知道是記錯了還是現在刪除了這個設定)
上面的例子可能太過簡單,以至於Action看起來都發揮不了什麽作用。
下面來看一個復雜的例子,這個例子有助於理解為什麽要用Action管理異步操作
需求如下
state中存儲了一個狀態,我們還是復用剛才的count
現在有兩個異步操作,他們都能改變count的值
第二個異步操作的條件依賴第一個異步操作的結果,比如第一個異步操作執行了count ++ ,count :0 = >1,第二個異步操作會先判斷當前count的值,if(count === 1) { do something...} else { do something... }
當異步操作涉及互相依賴的情況的時候,我們肯定希望被依賴的操作執行完成之後,再執行依賴項,這樣能保證程序執行得到正確的結果,但異步操作,如接口訪問這種,往往是不能確定執行完成的時間的,
通常你在接口A中得到一個值,a
接口B需要使用這個值結合B接口返回的值進行一些判斷操作,if(a&&b){ ... }
這個時候如果B接口執行完畢了,A接口的值還沒過來的話,就可能得到錯誤的結果。 a => undefined
所以這裏牽扯到了一個異步操作的順序執行問題,既然是異步操作問題,基本都會用到ES6的promise函數去解決,有興趣的可以用看一下我的文章——關於promise的一些使用和原理。
下面我們重寫一下代碼
// Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象 let incrementAsync = (content) => { return new Promise((resolve, reject) => { setTimeout(() => { content.commit(‘increment‘) resolve() }, 1000) }) } let decrementAsync = (content) => { return new Promise((resolve, reject) => { setTimeout(() => { content.commit(‘decrement‘) resolve() }, 1000) }) } export {incrementAsync, decrementAsync} })
<template> <div> <button @click="dec">-</button> <span>{{count}}</span> <button @click="add">+</button> </div> </template> <script> import { mapState, mapMutations, mapActions } from ‘vuex‘ export default { computed: { ...mapState([‘count‘]) }, methods: { ...mapMutations([‘increment‘, ‘decrement‘]), ...mapActions([‘incrementAsync‘, ‘decrementAsync‘]), add () { this.incrementAsync().then(() => { this.increment() }) }, dec () { this.decrementAsync().then(() => { // do something }) } } } </script> <style> </style>
關於Action,細枝末節的東西並不想多講,比如action中的‘載荷’,mapAction輔助函數,promise()函數使用等等,都是之前講過的,寫這篇文章的目的還是想重申一下為什麽要用Action管理異步操作,有些人可能覺得如果一種代碼可以用另一種看起來可以實現的方式實現的話,就不用去管“規範”的問題了,反而覺得這看起來更像一個“黑客”的做法,從而刻意的追求程序的“自由性”。
我只能說,你可以這麽做,但請你善待幫你維護代碼的人,因為你的“自由開發”可能讓別人完全搞不懂你在寫什麽,只想罵你是個傻X。
Vuex入門(5)—— 為什麽要用Action管理異步操作