好在之前接觸過 flux,對於理解 vuex 還是很有幫助的。react 學到一半,后來因為太忙,就放棄了,現在也差不多都忘記了。不過感覺 vuex 還是跟 flux 還是有點區別的。
對於很多新手來說,只是閱讀文檔是不好消化,我的建議是看看 vuex 的實例,通過研究實例來學習vuex。這樣就會好理解多了。如果還是不能理解,最好辦法就是先把store 的四個屬性:state, getters, mutations, actions 記下來,然后再分析四個屬性的特點,什么地方會用到,是怎樣連接在一起的?通過這樣問自己問題來進行學習。
簡單來說,vuex 就是使用一個 store 對象來包含所有的應用層級狀態,也就是數據的來源。當然如果應用比較龐大,我們可以將 store 模塊化,也就是每個模塊都有自己的 store。分割方式見如下的代碼:
從上面的代碼我們也可以看出,一個 store 有四個屬性:state, getters, mutations, actions。下面我將從這四個屬性開始講。
1、State
先來講state。state 上存放的,說的簡單一些就是變量,也就是所謂的狀態。沒有使用 state 的時候,我們都是直接在 data 中進行初始化的,但是有了 state 之后,我們就把 data 上的數據轉移到 state 上去了。當一個組件需要獲取多個狀態時候,將這些狀態都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用 mapState
輔助函數幫助我們生成計算屬性,讓你少按幾次鍵:
其實就是把 state 上保存的變量轉移到計算屬性上。當映射的計算屬性的名稱與 state 的子節點名稱相同時,我們也可以給 mapState
傳一個字符串數組。
computed: mapState([ // 映射 this.count 為 store.state.count 'count' ])
為了更好地理解這個函數的作用,我們可以看看它的源代碼。
可以看到,mapstate 即可以接受對象,也可以接受數組。最終返回的是一個對象。並且 res[key] 的值都是來於 store 里的,紅色那條代碼就是。這樣就把兩個不相關的屬性連接起來了,這也是映射。其他幾個輔助函數也是類似的。
2、Getters
getters上簡單來說就是存放一些公共函數供組件調用。getters 會暴露為 store.getters
對象,也就是說可以通過 store.getters[屬性]來進行相應的調用。mapGetters 輔助函數僅僅是將 store 中的 getters 映射到局部計算屬性,其實也就是從 getters 中獲取對應的屬性,跟解構類似。具體如下圖
這樣我們就可以將 getters 中的 evenOrOdd 屬性值傳給對應組件中的 evenOrOdd 上。Getters 接受 state 作為其第一個參數,Getters 也可以接受其他 getters 作為第二個參數。
3、Mutations
mutations 與事件類似,更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。所以 mutations 上存放的一般就是我們要改變 state 的一些方法。
const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 變更狀態 state.count++ } } })
我們不能直接調用一個 mutation handler。這個選項更像是事件注冊:“當觸發一個類型為 increment
的 mutation 時,調用此函數。”要喚醒一個 mutation handler,你需要以相應的 type 調用 store.commit 方法:
store.commit('increment')
當 mutation 事件類型比較多的時候,我們可以使用常量替代 mutation 事件類型。同時把這些常量放在單獨的文件中可以讓我們的代碼合作者對整個 app 包含的 mutation 一目了然:
一條重要的原則就是要記住 mutation 必須是同步函數。
4、Actions
前面說了,mutation 像事件注冊,需要相應的觸發條件。而 Action 就那個管理觸發條件的。
Action 類似於 mutation,不同在於:Action 提交的是 mutation,而不是直接變更狀態。Action 可以包含任意異步操作。
actions: { increment (context) { context.commit('increment') } }
Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調用 context.commit
提交一個 mutation,或者通過 context.state
和 context.getters
來獲取 state 和 getters。
實踐中,我們會經常會用到 ES2015 的 參數解構 來簡化代碼(特別是我們需要調用 commit
很多次的時候):
actions: { increment ({ commit }) { commit('increment') } }
還記得我們前面說過 mutation 像事件類型嗎?因此需要我們給定某個動作來進行觸發。而這就是分發 action。Action 通過 store.dispatch方法觸發,dispatch 里面的是 actions 中的函數的名字:
store.dispatch('increment')
此外,我們還可以在我們可以在 action 內部執行異步操作:
actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } }
你在組件中使用 this.$store.dispatch('xxx')
分發 action,或者使用 mapActions
輔助函數將組件的 methods 映射為 store.dispatch
調用(需要先在根節點注入 store
):
import { mapActions } from 'vuex' export default { // ... methods: { ...mapActions([ 'increment' // 映射 this.increment() 為 this.$store.dispatch('increment') ]), ...mapActions({ add: 'increment' // 映射 this.add() 為 this.$store.dispatch('increment') }) } }
這句話意思其實是,當你使用了 mapActions, 你就不需要再次使用 this.$store.dispatch('xxx'),當你沒使用的話,你可以需要手動去分法。比如下面的代碼:
什么時候用this.$store.dispatch('xxx'),什么時候用 mapActions 大家要根據情況而定的。
最后,問大家一個問題,你知道什么時候有擴展符 (...) 嗎? 不知道你有沒有注意,有些有擴展符,有些沒有。
提示:有擴展符的,都是被包含在一個對象里了。
還有就是在有多個 store 的時候,注意即使是在不同 store 里, actions 里面的函數名字也是不能存在相同的,否則就是相當於那個函數調用兩遍。