標籤:

Vuex2.0源碼解析

本文通過一些簡單流程圖和文字說明介紹,用一種背離源碼、更簡單的方式去了解Vuex的原理。讓我們在使用Vuex的時候明白背後的運行機制,方便我們更好的使用和調試解決問題。

1、什麼是Vuex?

Vuex是一個專門為Vue.js應用程序開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。通俗一點理解即:針對組件繁多交互複雜的單頁面應用,Vuex提供了一種便利、準確和可預測的狀態管理方式,方便組件之間的數據共享和修改。

2、Vuex核心概念

在理解Vuex源碼之前,有一些Vuex的核心概念需要簡單介紹一下,進而幫助更好的理解源碼。

  • State:即狀態,也就是Vuex核心管理的對象;
  • Getters:派生狀態,對state的二次包裝(例如:默認後端時間戳轉化為日期格式),Getters里的方法所有組件都可以使用;
  • Mutations:所有狀態的修改都是通過提交mutation,mutation類似事件,定義事件類型和回調函數,而回調函數就是進行狀態修改的地方,狀態修改一定是同步進行,從而確保狀態修改可以被追蹤到;
  • Actions:同樣是進行狀態修改,跟Mutations唯一不同的是進行非同步的狀態修改,本質是在回調提交Mutation;
  • Modules:為了解決狀態樹龐大進而store臃腫的問題,提出module概念,分化store到每個module,每個module都是一個小store。

3、Vuex2.0源碼結構

本文介紹Vuex2.0源碼部分,首先看下整個源碼構成,如下圖:

Vuex源碼部分總共包括五個部分:

  • install:安裝部分源碼;
  • store:源碼核心部分,本文重點介紹內容;
  • api:源碼提供一些內部和外部api;
  • 輔助函數:語法糖,讓我們在使用Vuex的時候書寫更為簡便;
  • plugin:提供一些默認插件,我們也可以自定義擴展插件的書寫。

4、核心源碼解析

4.1、install

安裝部分源碼的核心目的:給Vue注入一個store屬性。

總體流程如下:

核心注入源碼部分:

function vuexInit () {n const options = this.$optionsn // store 注入n if (options.store) {n this.$store = options.storen } else if (options.parent && options.parent.$store) {n this.$store = options.parent.$storen }n}n

4.2、store

store是整個Vuex的核心內容,其他幾個部分的源碼都是為了支撐store而存在的,在store中完成所有組件共享數據(狀態)的註冊、調用和修改方法。首先通過一個流程圖看下store構造函數的構成:

store像是一個生態環境,整個應用里的每個組件都要在這個環境里註冊,完成狀態的可預測管理。註冊之前需要進行環境檢測,這像是進入這個生態環境註冊的門票,或者說是通行證;達到註冊標準之後,進行一些內部屬性和方法的初始化工作,這像是為註冊工作搭建一個「舞台」,接下來的註冊工作都在這個「舞台」上完成。

4.2.1、installModule

安裝模塊部分源碼主要完成模塊的state、mutations、actions和getters的註冊工作,先總體看下源碼構成:

模塊安裝初期針對一些內部api的註冊工作,接下來是state的更新工作,更新邏輯如下:

熱更新:在註銷一個module的時候,其邏輯是安裝一個空module達到更新的狀態,而這個更新即為熱更新。熱更新的狀態修改單獨處理。

在獲取父節點的狀態之後,進行一次基於父節點狀態的commit提交修改即可完成模塊的state更新到state tree。源碼如下:

if (!isRoot && !hot) {n const parentState = getNestedState(rootState, path.slice(0, -1))n const moduleName = path[path.length - 1]n store._withCommit(() => {n Vue.set(parentState, moduleName, module.state)n })n}n

接下來是一次store本地化的操作,即完成dispatch、commit、getters和state的本地化工作。本地化的目的是為了更好的進行數據方法操作。

mutations註冊邏輯和actions註冊邏輯幾近相似,邏輯如下:

在store環境里有兩個屬性:_mutations和_actions分別用來存儲模塊定義的mutations和actions,依據當前模塊的類型進行查找對應的內部屬性對象,並且將模塊對應的回調函數進行包裝插入到對應屬性對象里,到此即完成註冊工作。二者差別在於包裝回調函數處理上的不同,mutation直接將回調函數包裝起來即可,action對於回調函數的結果進行Promise對象的處理,然後包裝。

getters的註冊邏輯如下:

getters是為了獲取派生狀態,因此命名的定義不允許重複,首先根據模塊類型進行重名判定,判定的依據來自內部屬性:_wrappedGetters,這個屬性存儲著整個應用的註冊getter。接下來就是將模塊的getters按照類型存入即完成註冊。最後是一個子模塊的遞歸調用的方法。

installModule只是完成了模塊的註冊工作,離我們可以使用這些狀態還有一些需要處理的代碼。

4.2.2、resetStoreVM

這個方法是對state和getters進行最後的使用處理,從而用戶可以調用這些狀態。源碼邏輯如下:

核心內容是store._vm這樣一個內部變數,本質上將註冊後的state和getters作為新的數據源實例化一個Vue對象傳遞給store._vm,並且刪除舊的store._vm。與此同時,定義store.getters.xxx=store._vm[xxx],從而完成使用getters的正確姿勢。state的使用是由store內部提供了一個api,調用這個api返回store._vm.data.$$state.xxx,在更新store._vm之後,就可以訪問這個模塊的state。

mutations和actions使用通過store內部提供的兩個重要api來實現,接下來介紹api部分。

5、api

5.1 commit 和 dispatch

這兩個api分別是用來完成mutations和actions的使用工作。源碼邏輯如下:

commit的邏輯是:從上面註冊過的內部屬性對象里依據參數拿到對應的mutations,然後通過_withCommit提交包裝的回調函數即可,同時使用內部api subscribe進行狀態修改追蹤訂閱。而dispatch則是通過參數拿到對應註冊的actions,然後promise.all執行回調,回調里則是進行commit提交。

5.2 _withCommit

_withCommit (fn) {n const committing = this._committingn this._committing = truen fn()n this._committing = committingn }n

這個內部api是每次提交狀態修改的核心源碼,其邏輯很簡單,在每次執行狀態修改的時候,保證內部屬性_committing為true,而這個屬性的默認初始值為false。這樣在追蹤狀態變化的時候,如果_committing不為true,那麼認為這次的修改是不正確的。 源碼中還有一些內部api類似registerModule、unregisterModule、hotUpdate、watch以及subscribe等,在這裡就不詳細贅述。

6、輔助函數

Vuex除了上述提供的api以外,還提供了一些輔助函數,即操作 store 各種屬性的一系列語法糖,目的是為了幫助我們使用Vuex的時候更方便。具體分為四個輔助函數:mapState、mapMutations、mapActions和mapGetters。為了更清晰的解釋語法糖的包裝形式,先看一下使用方法:

computed: mapState({n // 箭頭函數可使代碼更簡練n count: state => state.count,nn // 傳字元串參數 count 等同於 `state => state.count`n countAlias: count,nn // 為了能夠使用 `this` 獲取局部狀態,必須使用常規函數n countPlusLocalState (state) {n return state.count + this.localCountn }n })n

對state進行舉例說明,以上代碼是使用方法,從代碼量上來看確實精簡了不少,這樣一個包裝過程中重要的是將這個語法糖翻譯成計算機可識別的js代碼,因此在mapState的源碼封裝過程中實際做了很重要的一件事:將state注入到Vue實例的computed屬性里,同時對於不同格式的語法糖進行函數形式的解構包裝即可,從而還原成完整的執行函數。

同理可以對於mapMutations、mapActions和mapGetters進行解釋:mapMutations和mapActions分別將commit 和 dispatch注入vue實例的methods里,而mapActions是將getters注入到vue實例的computed里,剩下的都是一些將語法糖書寫改為函數形式即可,對於這幾種的語法糖封裝方式在源碼上都大同小異。

7、plugin

Vuex2.0里提供了兩個plugin:devtool和logger。分別是接入開發者工具和輸出state變化的log插件;從源碼角度去看插件邏輯沒什麼需要特別地說明,只是我們在開發插件的過程中可能需要對於內部提供的一些api和屬性有更多的了解和掌握,例如subscribe 的內部API,例如travel-to-state、mutation鉤子函數等,這樣我們才可以根據自身的需求去開發相應的插件。

8、總結

本文介紹了Vuex2.0的源碼核心,整體的源碼量不大,通過一些簡易流程和介紹說明源碼運行機制,讓大家基本在脫離源碼的基礎上簡略理解Vuex的原理。

本文介紹了源碼里比較核心的部分,源碼里還有一些輔助函數和內部api值得推敲和品味,還是希望大家有機會去閱讀一下完整的源碼,從而更完整的理解Vuex,也更容易幫助你進行debug;其次通過通讀源碼,整體地理解它的設計理念以及編碼風格,幫助你日後在邁向技術高工路上進行實踐學習。


推薦閱讀:

Vuex 模塊化實現待辦事項的狀態管理
Vuex新手入門指南
vue組件什麼條件下需要摧毀?

TAG:Vuex |