天天看點

Vuex知識梳理Vuex知識梳理

Vuex知識梳理

每一個 Vuex 應用的核心就是 store(倉庫)。“store”基本上就是一個容器,它包含着你的應用中大部分的狀态 (state)。Vuex 和單純的全局對象有以下兩點不同:

  1. Vuex 的狀态存儲是響應式的。當 Vue 元件從 store 中讀取狀态的時候,若 store 中的狀态發生變化,那麼相應的元件也會相應地得到高效更新。
  2. 你不能直接改變 store 中的狀态。改變 store 中的狀态的唯一途徑就是顯式地送出 (commit) mutation。這樣使得我們可以友善地跟蹤每一個狀态的變化,進而讓我們能夠實作一些工具幫助我們更好地了解我們的應用。

State - 單一狀态樹

Vuex 用一個對象就包含了全部的應用層級狀态,它作為一個“唯一資料源 (SSOT)”而存在,這也意味着,每個應用将僅僅包含一個 store 執行個體。

Vue 元件從 store 執行個體中讀取狀态最簡單的方法就是在計算屬性中傳回某個狀态,當一個元件需要擷取多個狀态的時候,使用 mapState 輔助函數生成計算屬性。

Getter

當需要從 store 中的 state 中派生出一些狀态的時候,Vuex 允許我們在 store 中定義“getter”(可以認為是 store 的計算屬性)。就像計算屬性一樣,getter 的傳回值會根據它的依賴被緩存起來,且隻有當它的依賴值發生了改變才會被重新計算。

Getter 接受 state 作為其第一個參數。

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})
           

通路方式:

  1. 通過屬性通路:

    Getter 會暴露為 store.getters 對象,可以以屬性的形式通路。

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
           

Getter 也可以接受其他 getter 作為第二個參數:

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
           
store.getters.doneTodosCount // -> 1
           

注意,getter 在通過屬性通路時是作為 Vue 的響應式系統的一部分緩存其中的。

  1. 通過方法通路:

    也可以通過讓 getter 傳回一個函數,來實作給 getter 傳參。當對 store 裡的數組進行查詢時非常有用。

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
           
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
           

注意,getter 在通過方法通路時,每次都會去進行調用,而不會緩存結果。

mapGetters 輔助函數僅僅是将 store 中的 getter 映射到局部計算屬性:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用對象展開運算符将 getter 混入 computed 對象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}
           

如果需要将一個 getter 屬性另取一個名字,使用對象形式:

mapGetters({
  // 把 `this.doneCount` 映射為 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
})
           

Mutation

一條重要的原則就是要記住 mutation 必須是同步函數。

更改 Vuex 的 store 中的狀态的唯一方法是送出 mutation。 Vuex 中的 mutation 非常類似于事件:每個 mutation 都有一個字元串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是我們實際進行狀态更改的地方,并且它會接受 state 作為第一個參數,但不能直接調用一個 mutation handler,需要以相應的 type 調用 store.commit 方法:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 變更狀态
      state.count++
    }
  }
})
           
store.commit('increment')
           

也可以向 store.commit 傳入額外的參數,即 mutation 的 載荷(payload),在大多數情況下,載荷應該是一個對象,這樣可以包含多個字段并且記錄的 mutation 會更易讀:

// ...
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
           
store.commit('increment', {
  amount: 10
})
           

對象風格的送出方式,整個對象都作為載荷傳給 mutation 函數, handler 保持不變:

store.commit({
  type: 'increment',
  amount: 10
})
           

可以在元件中使用 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` 也支援載荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射為 `this.$store.commit('increment')`
    })
  }
}
           

Action

Action 類似于 mutation,不同在于:

  • Action 可以包含任意異步操作。
  • Action 送出的是 mutation,而不是直接變更狀态。

Action 函數接受一個與 store 執行個體具有相同方法和屬性的 context 對象,是以可以調用 context.commit 送出一個 mutation,或者通過 context.state 和 context.getters 來擷取 state 和 getters:

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

Action 通過 store.dispatch 方法觸發,可以在 action 内部執行異步操作,也同樣支援載荷方式和對象方式進行分發:

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}
           
store.dispatch('increment')
           
// 以載荷形式分發
store.dispatch('incrementAsync', {
  amount: 10
})

// 以對象形式分發
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})
           

在元件中使用 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` 也支援載荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射為 `this.$store.dispatch('increment')`
    })
  }
}