vuex 筆記


Vuex 筆記

一個簡單的狀態管理

單一數據源:

const sourceOfTruth = {}
const vmA = new Vue({
  data: sourceOfTruth
})
const vmB = new Vue({
  data: sourceOfTruth
})

每當 sourceOfTruth 發生變化, vmAvmB 都會自動更新它們的視圖. 子組件可以通過 this.$root.$data 訪問數據. 現在我們有了單一的數據源, 但是調試會很困難. 因為無論何時數據源發生變化都會改變程序, 但是沒有任何跡象表明變化發生.

store pattern

為了解決上述問題, 我們可以引入 store pattern:

var store = {
  debug: true,
  state: {
    message: 'Hello!'
  },
  setMessageAction (newValue) {
    this.debug && console.log('setMessageAction triggered with', newValue)
    this.state.message = newValue
  },
  clearMessageAction () {
    this.debug && console.log('clearMessageAction triggered')
    this.state.message = 'action B triggered'
  }
}

所有的數據改變都發生 store 內. 這種集中的狀態管理模式使我們很容易記錄變化發生, 如何發生.
除了單一的數據源外, 每個 vue 實例或組件也可以有其私有狀態:

var vmA = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})
var vmB = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})

state

使用Vuex

// 如果 Vuex 不是全局的, 那么確保調用 Vue.use(Vuex) 使 Vuex 生效.

const store = new Vuex.Store({
  // 數據源
  state: {
    count: 0
  },
  // 數據操作
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

// 觸發數據變化操作
store.commit('increment')

console.log(store.state.count) // -> 1

State

store 自動注入到子組件中

通常我們通過計算屬性來訪問 store 中的數據, 這樣就能感知到數據發生變化.
根組件的 store 屬性會注入到其所有的子組件中. (通過 Vue.use(Vuex) 生效)

const Counter = {
    template: `<div>{{ count }}</div>`,
    computed: {
        count() {
            // 子組件通過 this.$store 訪問父組件的 store
            return this.$store.state.count
        }
    }
}

new Vue({
    // 父對象中的 store 自動注入到子組件
    store,
    componets: {
        Counter
    }
})

mapState

如果 store 中有許多數據需要訪問, 每個數據都需要定義一個計算屬性會非常麻煩. Vuex 提供了 mapState 來簡化計算屬性的定義.

import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // es6 箭頭函數更加簡潔
    count: state => state.count,

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

    // 為了訪問組件的 `this`, 必須使用普通的函數
    // 箭頭函數會綁定 `this` 到 `mapState` 的參數這個對象
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

如果計算屬性和 store 中數據是一一對應的, 可以使用更簡單的字符串數組:

computed: mapState([
  // map this.count to store.state.count
  'count'
])

es6 的擴展操作符

使用 mapState 返回一個對象, 如果組件還有私有的計算屬性, 通常我們可以使用 _.extend({localComputed}, mapState(...)) 這種方式合並對象已得到最終的 computed. 使用 es6 的擴展操作符可以簡化:

computed: {
    localComputed(){ /* ... */},
    // 通過擴展操作符擴展 computed 對象
    ...mapState({
        // ...
    })
}

Getters

通常計算屬性是基於一段 store 數據的代碼, 比如過濾一個列表並計數:

computed: {
    doneTodoCount() {
        return this.$store.state.todos.filter(todo => todo.done).length
    }
}

如果我們需要復用這段代碼, 基本就是重構提取出一個函數, 但是這樣還不是很理想.

Vuexstore 中提供了 getters:

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)
    },
    doneTodosCount: (state, getters) => {
        return getters.doneTodos.length
    }
  }
})

// 通過 `store.getters` 訪問
store.getters.doneTodosCount

上面的計算屬性就可以改成:

computed: {
    doneTodoCount() {
        return this.$store.getters.doneTodoCount
    }
}

mapGetters

statemapState 類似, getters 也有 mapGetters 來簡化計算屬性的定義

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
    // mix the getters into computed with object spread operator
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

使用對象可以自定義對應關系

mapGetters({
  // map this.doneCount to store.getters.doneTodosCount
  doneCount: 'doneTodosCount'
})

Mutations

Vuex 中的 state 只能通過 mutations 來改變. mutations 很像事件, 都有一個類型處理函數. 處理函數是真正改變 state 的地方, 並以 state 作為第一個參數.

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 改變 state
      state.count++
    }
  }
})

就是事件一樣, 我們不能直接調用處理函數, 而是要通過 store.commit(type) 來觸發 mutation 處理函數.

store.commit('increment')

帶 playload commit

我們可以將處理函數的參數放到第二個參數 playload 中:

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

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

對象風格 commit

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

靜默模式

默認情況下, 每一次 commit 都會發送到插件 (比如: devtools) 中. 可能你會希望某些 commit 不被記錄. 這時候可以傳遞第三個參數以設置為靜默模式:

store.commit('increment', {
  amount: 1
}, { silent: true })

// 對象風格 commit
store.commit({
  type: 'increment',
  amount: 1
}, { silent: true })

Mutations 要遵守 Vue 的響應式規則

即:

  1. 提前初始化所有的狀態值
  2. 添加新的屬性到對象時, 你應該:
    • 使用 Vue.set(obj, 'newProp', 123)
    • 直接替換新的對象: state.obj = {...state.obj, newProp: 123}

使用常量為 Mutations 命名

使用常量為 Mutations 命名是各種 Flux 實現常用的模式. 將所有常量放到一個文件中, 我們能看到整個程序有什么情況數據會發生變化.

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // es6 特性 computed property name
    // 屬性名稱運行時確定
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

Mutations 必須是同步的

異步 mutations 調用違反了所有的狀態變化必須在 store 中進行的規定. 比如:

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

當上例中狀態變化時, someMutation 已經結束了. 這時候如果有其他狀態變化的操作發生, devtools 記錄下來的狀態變化就是錯誤的.

mapMutations

我們可以通過 this.$store.commit('xxx') 在組件中調用 mutations, 一般我們將這些調用分裝到 methods 中, 同時 Vuex 也提供了 mapMutations 函數簡化 methods 定義:

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment' // 映射 this.increment() 到 this.$store.commit('increment')
    ]),
    ...mapMutations({
      add: 'increment' // map this.add() to this.$store.commit('increment')
    })
  }
}

Actions

異步的 mutations 使程序的狀態變化難以追蹤. 為了解決異步操作, Vuex 引入了 actions.
actionsmutations 非常像, 它們的不同之處在於:

  • actions 不改變 state, 而是 commit mutations
  • actions 可以包含任意的異步操作
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

actions 接收一個 context 對象作為參數, context 可以訪問 commit, getters, state, 但是它不是 store 對象.
通常, 我們會使用 es6 的參數結構語法來簡化代碼:

actions: {
    increment({commit}) {
        commit('increment')
    }
}

Dispatching Actions

actions 通過 store.dispatch 來觸發:

store.dispatch('increment')

dispatch 也支持 commit 中的 playload 參數以及對象風格的調用方式.

// dispatch with a payload
store.dispatch('incrementAsync', {
  amount: 10
})

// dispatch with an object
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

mapActions

類似 mapMutations

Actions 組合

actions 通常是異步的, 我們怎么來組合多個 actions 來執行復雜的操作?

首先我們需要知道的是 store.dispatch 返回 actions 中處理函數的返回值, 因此我們可以返回一個 Promise:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  },

  actionB ({ dispatch, commit }) {
    // 組合
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

使用 async/await 語法, 可以簡化為:

// 假設 getData() 和 getOtherData() 返回 Promises

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // wait for actionA to finish
    commit('gotOtherData', await getOtherData())
  }
}

Modules

當我們的程序足夠大時, store 也會變得非常大, 其中的 state, getters, mutations, actions 也會非常大.

因此 Vuex 允許我們將 store 分成幾個 modules, 每個 modules 都有自己的 state, getters, mutations, actions 甚至它自己的 modules.

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA's state
store.state.b // -> moduleB's state

Modules 當前狀態

modules 中, gettersmutations 的第一個參數都是 modulesstate, 同樣 actionscontext.state 也是 modulesstate, 根節點的狀態可以通過 context.rootState 訪問到. getters 的可以通過第三個參數訪問 $rootState:

const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

命名空間

modulesstate 放到根節點的對應的屬性中, 而 actions, mutationsgetters 沒有命名空間. 所以多個 modules 可以對同一個 commitdispatch 做響應. 因此必須自己通過前綴或后綴來避免命名沖突.

動態 Modules 注冊

store.registerModule('myModule', {
  // ...
})


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM