vuex 原始碼:深入 vuex 之 getter

cobish發表於2018-03-08

前言

vuex 把 getter 比作是 store 的計算屬性 。就像 vue 的計算屬性一樣,getter 的返回值會根據它的依賴被快取起來,且只有當它的依賴值 data 發生了改變才會被重新計算。

其實,知道了 state 是 _vm 例項中的 data,那首先可以猜測 getter 就是 computed。那麼,我們就來驗證一下這個猜測是否是正確的。先說結論,當然是正確的啦哈哈~

注:本次閱讀的是 vuex 的 2.0.0 版本,原始碼請戳 這裡

解讀

跟解讀 state 一樣,getter 是 store 物件的屬性,所以依然從 Store 這個類開始入手。

還是開始看建構函式 constructor,發現裡面並沒有 getter 的程式碼,但有兩個方法,進去後發現有相關的程式碼,於是程式碼簡化為:

constructor (options = {}) {
  // and collects all module getters inside this._wrappedGetters
  installModule(this, state, [], options)

  // initialize the store vm, which is responsible for the reactivity
  // (also registers _wrappedGetters as computed properties)
  resetStoreVM(this, state)
}
複製程式碼

接下來的任務就是從 installModuleresetStoreVM 兩個方法中找到 getter 的實現即可。

installModule

先來看 installModule。簡化了程式碼後,發現跟 getter 相關的只有 wrapGetters 方法:

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const {
    getters
  } = module

  if (getters) {
    wrapGetters(store, getters, path)
  }
}
複製程式碼

進入 wrapGetters 方法,我們會發現這個方法的實現是為了拼接 store 的一個 _wrappedGetters 物件。這樣拼接的目的是為了可以在需要執行 getter 裡的方法時,還能傳想傳的引數再執行。我們暫時忽略 modulePath 與一些判斷,簡化程式碼為:

function wrapGetters (store, moduleGetters) {
  Object.keys(moduleGetters).forEach(getterKey => {
    const rawGetter = moduleGetters[getterKey]
    
    // 將 options 裡的 getter 賦值到 _wrappedGetters
    // 因為 computed 的賦值就是 return 一個函式
    store._wrappedGetters[getterKey] = function wrappedGetter (store) {
      return rawGetter(
        store.state, // local state
        store.getters, // getters
        store.state // root state
      )
    }
  })
}
複製程式碼

resetStoreVM

專門為 store 拼接了一個 _wrappedGetters 物件有啥好處呢?別急,我們先看另一個方法 resetStoreVM,還是過濾掉與 getter 不相關的程式碼:

function resetStoreVM (store, state) {
  // bind store public getters
  store.getters = {}
  
  // 獲取剛剛拼接的 _wrappedGetters
  const wrappedGetters = store._wrappedGetters
  
  // 開始拼接 computed
  const computed = {}
  Object.keys(wrappedGetters).forEach(key => {
    const fn = wrappedGetters[key]
    // use computed to leverage its lazy-caching mechanism
    computed[key] = () => fn(store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key]
    })
  })

  // use a Vue instance to store the state tree
  store._vm = new Vue({
    data: { state },
    computed
  })
}
複製程式碼

裡面就用到了剛剛拼接的 _wrappedGetters 物件。先看方法裡下面的 store._vm,我們猜測的沒錯吧,果然是 computed。那麼上面的程式碼就是拼接一個 computed 物件了。以下這行程式碼就是拼接一個 Vue 能是識別的計算屬性 computed。

computed[key] = () => fn(store)
複製程式碼

並使用 Object.defineProperty 對 store.getters 的 get 方法進行重寫。這樣,一旦訪問了 this.$store.getters.count,那麼 get 方法就會返回 this.$store._vm.count,也就是 _vm 的計算屬性 count。

所以, store.getters 實際上就是 store._vm 的計算屬性 computed

總結

之前解讀了 state,再加上本篇的 getter,我們已經知道了 state 和 getter 分別對應著 store._vm 例項的 data 和 computed。所以下次再使用到 getter,我們可以把它當成 vue 的 computed 一樣使用即可。

相關文章