深入vuex原理(下)
在深入 vuex原理(上) 中,我們回答了 “vuex的store是如何注入到元件中的?” 的問題,下面我們繼續對以下問題進行探討!
- vuex的state 和 getter 是如何對映到 各個元件例項中自動更新的?
本篇文章主要討論 "vuex的state 和 getter 是如何對映到 各個元件例項中自動更新的?" 這個問題。首先,我們對問題進行簡單剖析!
注:本篇文章只展示關鍵核心程式碼,一來由於篇幅原因,二來展示核心程式碼更容易讓人理解!再者,本篇屬於 vuex 高階篇,對於本篇章中 涉及的 vue 相關的機制 以及 vuex的 高階使用 等 不進行過多贅述!請自行前往官網檢視!
準備
疑問:vuex的state 和 getter 是如何對映到各個元件例項中自動更新的呢?
問題剖析
該問題的核心問題是當store中的 state 和 getter 方式變更時,vuex如何保證各個元件例項中的資料自動更新,並update 元件!簡言之,某一元件store更新時,如何通知其他元件進行資料更新,和UI更新!通過簡單分析可知,問題的根本就是元件通訊的問題!
淺談元件通訊
從分析可知,要解答本篇疑問,我們需要從 vue 元件通訊談起! 在使用vue的過程中,需要頻繁的進行元件間通訊!通訊的主體之間的關係可以是 父子元件,也可以是 類似 兄弟元件 或者是 無關元件 等非父子元件。 總的來說 有如下幾種方式!
- 通過props向子元件傳遞資料:父 -> 子
- 通過事件向父元件傳送訊息:子 -> 父,使用$emit傳送事件
- 父鏈 和 子索引:
this.$parent
與this.$children
- 依賴注入:provide 和 inject
- 子元件引用: ref與$refs
- 特性繫結:
v-bind="$attrs"
和v-on="$listeners"
- event bus
-
$dispatch 和 $broadcast
: 在vue1中使用 - 利用全域性變數、storage、cookie、query、hash等傳遞資料: 非vue特性,不做贅述。
- 全域性事件廣播
下圖展示了 上述 方案 1~6 的元件通訊方式。

由於 方案 8 官方已經不建議在vue2中使用,此處不做贅述;而方案9~10 不屬於vue特性,而是前端通用的資料通訊方案,此處也不進行贅述!如有其它方案,歡迎補充!
下面,我們主要介紹一下與本文內容息息相關的方案7,中央事件匯流排的解決方案!其核心設計思想是引入中央通訊橋樑——中央事件匯流排,使各個元件只與其進行通訊,達到資料同步的通訊目的!如下圖 元件A資料變更,通知中央事件匯流排,其他元件監聽並接收變更的資料。

下面程式碼展示了在vue中使用 中央事件匯流排 進行元件通訊!
let bus = new Vue({ methods: { emit (event, ...args) { this.$emit(event, ...args); }, on (event, callback) { this.$on(event, callback); } } }); //component A bus.emit('updateData', data); // 傳送資料給 bus。 //component B bus.on('updateData', data => { // 接收 updateData事件 傳送的資料資訊。 }) 複製程式碼
vue的中央事件匯流排的實現 簡單講就是新建了一個vue物件,藉助vue物件的特性( on) 作為其他元件的通訊橋樑,實現元件間的通訊 以及資料共享!
探祕原理
本部分將針對以上疑問,通過原始碼分析,剖析核心程式碼,對問題進行解答。
使用我們探討的是state 和 getter,首先我們先來看一下在vue元件中如何方便的獲取 vuex的state和getter吧!
this.$store.state.xxx; this.$store.state.moduleA.xxx; this.$store.getters.xxx; this.$store.getters.moduleA.xxx; 複製程式碼
正如我們所知的,vuex的Store 會劃分出 state 和 getters 兩個資料區。getter是從store的state中派生出的狀態!程式碼如下:
// 初始化store時,劃分出 state資料區 與 getters資料區 new Vuex.Store({state, getters}); 複製程式碼
原始碼分析
首先,我們先來看state。從原始碼中我們找到了state的get方法,如下:
get state () { return this._vm._data.$$state } 複製程式碼
從原始碼得知,在vue元件中 使用 this.$store.getters.xxx
獲取 xxx
屬性時,實際上是獲取的 store._vm.data.$$state
物件上的同名屬性。那麼我們將關注點放在 _vm上。我們通過Store 的constructor 找到了處理state 和 getter的核心函式 resetStoreVM(this, state)
。其核心程式碼如下:
store._vm = new Vue({ data: { $$state: state } }) 複製程式碼
上述程式碼初始化了一個vue例項 _vm,由於vue的data是響應式的,所以,$$state也是響應式的,那麼當我們 在一個元件例項中 對state.xxx進行 更新時,基於vue的data的響應式機制,所有相關元件的state.xxx的值都會自動更新,UI自然也會自動更新!可見,這和vue的中央事件匯流排 設計思想如出一轍,同樣藉助 vue物件特性(響應式的data)作為其他元件的通訊橋樑,實現元件間的通訊 以及資料共享!
總結
vuex的state是藉助vue的響應式data實現的。設計思想與vue中央事件匯流排如出一轍!
猜測
vue中 computed從data中派生出的計算屬性, vuex中 getter是從state中派生出的屬性;而 vuex 中的state是藉助data實現的,那麼getter是否是藉助computed實現的呢?
原始碼驗證
在resetStoreVM 中可以看到 對於wrappedGetters(經過包裝,收集的getters,處理細節忽略)的處理。
const computed = {}; // 處理getters forEachValue(wrappedGetters, (fn, key) => { computed[key] = () => fn(store) // 將getter儲存在computed上 //this.$store.getters.XXX的時候獲取的是store._vm.XXX Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true }) }) 複製程式碼
上述程式碼,對wrappedGetters 進行處理,讓getter 儲存至computed物件上,下面我們關注後續computed的處理即可!
store._vm = new Vue({ computed }) 複製程式碼
上述程式碼將computed物件掛載至 vue例項 _vm的computed屬性上,得益於vue的計算屬性特性,資料的變更同樣可以同步至其他相關元件上。這與我們的猜測一致!getter的實現藉助了vue的computed的特性而實現!
分析至此,我們已經得出該問題的答案!