1. 程式人生 > >Vuex的五個核心屬性的理解

Vuex的五個核心屬性的理解

Vuex的五個核心概念

本文參考自Vue文件,說的非常詳細,建議看文件。

Vuex是什麼?

VueX 是一個專門為 Vue.js 應用設計的狀態管理架構,統一管理和維護各個vue元件的可變化狀態(你可以理解成 vue 元件裡的某些 data )。

Vue有五個核心概念,state, getters, mutations, actions, modules。本文將對這個五個核心概念進行梳理。

總結

state => 基本資料 getters => 從基本資料派生的資料 mutations => 提交更改資料的方法,同步! actions => 像一個裝飾器,包裹mutations,使之可以非同步。 modules => 模組化Vuex

State

state即Vuex中的基本資料!

單一狀態樹

Vuex使用單一狀態樹,即用一個物件就包含了全部的狀態資料。state作為構造器選項,定義了所有我們需要的基本狀態引數。

在Vue元件中獲得Vuex屬性

  • 我們可以通過Vue的Computed獲得Vuex的state,如下:
const store = new Vuex.Store({
    state: {
        count:0
    }
})
const app = new Vue({
    //..
    store,
    computed: {
        count: function(){
            return this.$store.state.count
        }
    },
    //..
})

每當 store.state.count 變化的時候, 都會重新求取計算屬性,並且觸發更新相關聯的 DOM。

mapState輔助函式

當一個元件需要獲取多個狀態時候,將這些狀態都宣告為計算屬性會有些重複和冗餘。為了解決這個問題,我們可以使用 mapState 輔助函式幫助我們生成計算屬性,讓你少按幾次鍵。

// 在單獨構建的版本中輔助函式為 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭頭函式可使程式碼更簡練
    count: state => state.count,

    // 傳字串引數 'count' 等同於 `state => state.count`
    countAlias: 'count',

    // 為了能夠使用 `this` 獲取區域性狀態,必須使用常規函式
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

當對映的計算屬性的名稱與 state 的子節點名稱相同時,我們也可以給 mapState 傳一個字串陣列。

computed: mapState([
  // 對映 this.count 為 store.state.count
  'count'
])

物件展開運算子

mapState 函式返回的是一個物件。我們如何將它與區域性計算屬性混合使用呢?通常,我們需要使用一個工具函式將多個物件合併為一個,以使我們可以將最終物件傳給 computed 屬性。但是自從有了物件展開運算子,我們可以極大地簡化寫法:

computed: {
  localComputed () //本地計算屬性
  //使用物件展開運算子將此物件混入到外部物件中
  ...mapState({
    //..
  })
}
  • 物件運算子... 展開運算子(spread operator)允許一個表示式在某處展開。展開運算子在多個引數(用於函式呼叫)或多個元素(用於陣列字面量)或者多個變數(用於解構賦值)的地方可以使用。

    展開運算子不能用在物件當中,因為目前展開運算子只能在可遍歷物件(iterables)可用。iterables的實現是依靠[Symbol.iterator]函式,而目前只有Array,Set,String內建[Symbol.iterator]方法,而Object尚未內建該方法,因此無法使用展開運算子。不過ES7草案當中已經加入了物件展開運算子特性。

    例子:

    function test(a,b,c) {
        console.log(a);
        console.log(b);
        console.log(c);
    }
    var args = [0,1,2];
    test(...args);  // 0  1  2
  • ES7草案中的物件展開運算子 ES6中還不支援對物件的展開運算子,但是ES7中將支援。物件展開運算子符可以讓我們更快捷地操作物件,如下例子:
    let {x,y,...z}={x:1,y:2,a:3,b:4};
    x; //1
    y; //2
    z; //{a:3,b:4}

元件仍然保有區域性狀態

使用 Vuex 並不意味著你需要將所有的狀態放入 Vuex。雖然將所有的狀態放到 Vuex 會使狀態變化更顯式和易除錯,但也會使程式碼變得冗長和不直觀。

如果有些狀態嚴格屬於單個元件,最好還是作為元件的區域性狀態。你應該根據你的應用開發需要進行權衡和確定。

getters

即從store的state中派生出的狀態。

getters接收state作為其第一個引數,接受其他 getters 作為第二個引數,如不需要,第二個引數可以省略如下例子:

const store = new Vuex.Store({
    state: {
        count:0
    },
    getters: {
        // 單個引數
        countDouble: function(state){
            return state.count * 2
        },
        // 兩個引數
        countDoubleAndDouble: function(state, getters) {
            return getters.countDouble * 2
        }
    }
})

與state一樣,我們也可以通過Vue的Computed獲得Vuex的getters。

const app = new Vue({
    //..
    store,
    computed: {
        count: function(){
            return this.$store.state.count
        },
        countDouble: function(){
            return this.$store.getters.countDouble
        },
        countDoubleAndDouble: function(){
            return this.$store.getters.countDoubleAndDouble
        }
    },
    //..
})

mapGetters 輔助函式

mapGetters 輔助函式僅僅是將 store 中的 getters 對映到區域性計算屬性,與state類似

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用物件展開運算子將 getters 混入 computed 物件中
    ...mapGetters([
      'countDouble',
      'CountDoubleAndDouble',
      //..
    ])
  }
}

如果你想將一個 getter 屬性另取一個名字,使用物件形式:

mapGetters({
  // 對映 this.double 為 store.getters.countDouble
  double: 'countDouble'
})

mutations

提交mutation是更改Vuex中的store中的狀態的唯一方法。

mutation必須是同步的,如果要非同步需要使用action。

每個 mutation 都有一個字串的 事件型別 (type) 和 一個 回撥函式 (handler)。這個回撥函式就是我們實際進行狀態更改的地方,並且它會接受 state 作為第一個引數,提交載荷作為第二個引數。(提交荷載在大多數情況下應該是一個物件),提交荷載也可以省略的。

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    //無提交荷載
    increment(state) {
        state.count++
    }
    //提交荷載
    incrementN(state, obj) {
      state.count += obj.n
    }
  }
})

你不能直接呼叫一個 mutation handler。這個選項更像是事件註冊:“當觸發一個型別為 increment 的 mutation 時,呼叫此函式。”要喚醒一個 mutation handler,你需要以相應的 type 呼叫 store.commit 方法:

//無提交荷載
store.commit('increment')
//提交荷載
store.commit('incrementN', {
    n: 100
    })

物件風格的提交方式

我們也可以使用這樣包含 type 屬性的物件的提交方式。

store.commit({
  type: 'incrementN',
  n: 10
})

Mutations 需遵守 Vue 的響應規則

  • 最好提前在你的 store 中初始化好所有所需屬性。
  • 當需要在物件上新增新屬性時,你應該
    • 使用 Vue.set(obj, 'newProp', 123), 或者
    • 以新物件替換老物件。例如,利用物件展開運算子我們可以這樣寫state.obj = {...state.obj, newProp: 123 }

mapMutations 輔助函式

與其他輔助函式類似,你可以在元件中使用 this.$store.commit(‘xxx’) 提交 mutation,或者使用 mapMutations 輔助函式將元件中的 methods 對映為 store.commit 呼叫(需要在根節點注入 store)。

import { mapMutations } from 'vuex'

export default {
  //..
  methods: {
    ...mapMutations([
      'increment' // 對映 this.increment() 為 this.$store.commit('increment')
    ]),
    ...mapMutations({
      add: 'increment' // 對映 this.add() 為 this.$store.commit('increment')
    })
  }
}

actions

Action 類似於 mutation,不同在於:

  • Action 提交的是 mutation,而不是直接變更狀態。
  • Action 可以包含任意非同步操作。

我們用如下例子來結束actions:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      setInterval(function(){
        context.commit('increment')
      }, 1000)
    }
  }
})

注意:Action 函式接受一個與 store 例項具有相同方法和屬性的 context 物件,因此你可以呼叫 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。

分發actions

Action 通過 store.dispatch 方法觸發:

store.dispatch('increment')

其他與mutations類似的地方

Actions 支援同樣的載荷方式和物件方式進行分發:

// 以載荷形式分發
store.dispatch('incrementN', {
  n: 10
})

// 以物件形式分發
store.dispatch({
  type: 'incrementN',
  n: 10
})

mapActions輔助函式

你在元件中使用 this.$store.dispatch('xxx') 分發 action,或者使用 mapActions 輔助函式將元件的 methods 對映為 store.dispatch 呼叫(需要先在根節點注入 store):

import { mapActions } from 'vuex'

export default {
  //..
  methods: {
    ...mapActions([
      'incrementN' //對映 this.incrementN() 為 this.$store.dispatch('incrementN')
    ]),
    ...mapActions({
      add: 'incrementN' //對映 this.add() 為 this.$store.dispatch('incrementN')
    })
  }
}

Modules

使用單一狀態樹,導致應用的所有狀態集中到一個很大的物件。但是,當應用變得很大時,store 物件會變得臃腫不堪。

為了解決以上問題,Vuex 允許我們將 store 分割到模組(module)。每個模組擁有自己的 state、mutation、action、getters、甚至是巢狀子模組——從上至下進行類似的分割:

模組的區域性狀態

對於模組內部的 mutationgetter,接收的第一個引數是模組的區域性狀態,對於模組內部的 getter,根節點狀態會作為第三個引數:

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // state 模組的區域性狀態
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    },
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

同樣,對於模組內部的 action,context.state 是區域性狀態,根節點的狀態是 context.rootState:

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum (context) {
      if ((context.state.count + context.rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}