vuex 原始碼:深入 vuex 之 state

cobish發表於2018-03-01

前言

接下來我們則開始要研究的是 vuex 的 store 物件。

store 物件中有一個屬性叫 state。state 包含了全部的應用層級狀態。應用中的各個元件若使用了 state,則會保持與同步最新的狀態。state 就好比是 vue 中的 data,但它是整個應用的 data。

舉個簡單的例子:應用中的子元件 a 和子元件 b 用到了 state.count,兩個元件是非父子關係。這時,子元件 a 修改了 state.count,子元件 b 中的 state.count 也會相應修改。

那我們就來看看,vuex 是如何在各個元件中做到監聽 state 的屬性的

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

解讀

在開始閱讀原始碼之前,我一直有一個疑惑。我在元件 a 修改了 state.count,元件 b 是怎麼監聽到 state.count 的改變的?難道是 store 裡對 state 做了類似 object.defineproperty() 的處理。

帶著疑惑,開始啦.....

從哪開始切入呢?就從 store 物件被例項化的程式碼中開始吧。

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0
  }
});

new Vue({
  el: '#app',
  store,
  // ...
});
複製程式碼

store 物件是通過 new Vuex.Store 被例項化出來的,開啟 src/index.js 檔案,最下面的程式碼中可以看到 vuex 暴露出的 Store 這個 API:

export default {
  Store,
  install,
  mapState,
  mapMutations,
  mapGetters,
  mapActions
}
複製程式碼

Store

找到 Store 這一個類,挺長的一大段程式碼。老辦法,全部方法摺疊不看,只看建構函式 constructor。開啟過濾之眼,過濾掉與 state 無關的程式碼,最後可以把程式碼簡化成這樣:

constructor (options = {}) {
  const {
    state = {}
  } = options

  // init root module.
  // this also recursively registers all sub-modules
  // 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 兩個方法的實現即可。緊接著,再透露一個好訊息,installModule 看了一眼,是關於 module 的初始化,等以後研究 module 再看也不遲,所以只需要研究 resetStoreVM 即可。

resetStoreVM

定位到 resetStoreVM 方法,再次過濾,其中 Vue.config.silent 去 vue 的官網上搜了一下,是取消 vue 所有的日誌與警告的功能,所以也過濾掉,剩下程式碼如下:

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

resetStoreVM 函式做的事情就是給 store 新增一個 _vm 屬性,並將 state 作為一個 vue 物件的 data,最後將這個 vue 物件賦值給 _vm。所以到這裡我們知道了 store 類的建構函式為其新增了一個 _vm 屬性。

set 與 get

建構函式解析完畢,接下來得繼續找跟 state 相關的方法,於是找到了 setget 方法:

get state () {
  return this._vm.state
}

set state (v) {
  assert(false, `Use store.replaceState() to explicit replace store state.`)
}
複製程式碼

set 方法沒什麼好說的,意思就是 store 不給你直接修改 state(但其實是可以修改 state 物件的屬性,先不管那麼多了)。

get 方法返回了剛剛建構函式新增的 _vm 屬性的一個 data(state)。閱讀到這裡,這下我們應該知道為什麼元件 a 修改了 state.count,元件 b 也會跟著變了吧。因為 state 就是一個新的 vue 物件裡 data 的一個屬性啊。到這裡如果還不明白,是不是要回去 vue 官網重新學習 data 啦。

這樣總算是解決了我剛開始閱讀時的疑惑了。仔細思考一下,這不就是 vue 官網非父子元件的通訊中的 bus 注入到整個應用的 vue 物件中嗎?!

總結

本篇我們瞭解了 store 的 state,知道它是通過 new 一個新的 vue 物件 _vm 來監聽的,而這個 _vm 又是綁在 store 上的。所以通過這一系列的關係,最後我們能在各個元件中使用到被監聽的 this.$store.state

相關文章