通俗易懂Vuex源碼導讀3-Vuex官方文檔對照說明

Vuex 的使用解析

回顧前面幾章咱們介紹的內容

  • 第零章對Vuex的總體運行思路,重點變量進行了介紹。
  • 第一章介紹了Vuex的安裝過程。
  • 上一章介紹了Vuex的初始化過程,在正式使用Vuex前作了哪些準備工做。
  • 這一章,將對照Vuex的官方說明文檔,逐一介紹示例代碼背後的運行邏輯。

首先介紹的是state

  • 惟一狀態樹html

    • 惟一:是指整個Vuex數據存儲是由Store這一個對象實例完成的。
    • 狀態:是指各個數據。
    • 樹:指 module 和 state 的樹結構。
    • 經過 Store 變量能夠查看當前項目的數據存儲狀況(做者的用意,多是將Vuex做爲Vue項目的整個數據存儲庫,將全部數據內容都交由Vuex進行管理),能夠經過Store觀察整個項目的數據狀況。
    • 經過熱重載功能,進行數據的回退,實現時間穿梭的調整功能(本系列文章沒有介紹到,有興趣的同窗能夠看看源碼。hot關鍵字相關內容)。
  • 在計算屬性中使用state
    不知你是否會疑惑,在Store的構造函數中並無定義類的state屬性,爲何能夠經過store.state獲取到state數據呢?vue

    • state的定義是經過class的取值函數getter及存值函數setter來完成的。取值設值函數的功能,和Object.defineProperties函數相似,至關於設置攔截器,當獲取值或設置值時,調用對應函數。

      圖

    • 在vue組件中,調用this.$store.state.count的運行邏輯是:es6

      • this.$store在「mixin.js」的函數「vuexInit」中定義,在Vuex註冊部分介紹過,指向Store對象。
        圖
      • this.\$store.state即調用Store對象的state屬性。這時將觸發並調用get取值函數,返回this._vm._data.$$state (這時this指向store對象,由於這是在類中的this指針)

        圖

      • 至關於調用this.\$store._vm._data.\$\$state,其中_vm在resetStoreVM函數中定義,是一個Vue實例。_vm._data.\$\$state,即這個Vue實例中,經過data定義的一個$$state變量。

        圖

      • 而這個$$state變量指向的是Store對象的state變量(this._modules.root.state,在構造函數中有介紹),爲何要這樣放在Vue中,後續會介紹到。
      • 而根模塊的state變量,在模塊安裝函數(installModule)中,經過遞歸定義,將各個層級的state變量,都經過樹結構組織起來,樹的根,或者說樹狀態的索引入口就是這個根模塊的state變量。

        圖

      • 也就是this.\$store._vm._data.$$state。也就是this.$store.state。
    • 因此Vuex 使用單一狀態樹,經過State樹結構保存了Vuex中的全部數據
    • 爲何state須要經過Vue的data進行保存?vuex

      • 由於咱們看到在使用state時,是放在computed中使用, return this.$store.state.count

        圖

      • 由於要實現數據響應,當state的值發生變化時,須要通知對應的computed函數。
      • 這個功能就要藉助vue的數據綁定功能,因此要在data中定義。至於底層原理是什麼,請關注後續文章,vue源碼解讀
    • Vue子組件的註冊,是經過minix混合功能來實現,具體原理在「第一章」中介紹過
  • mapState輔助函數segmentfault

    • 先來看看源碼,輔助函數的定義在helpers.js文件中。
    • normalizeNamespace函數,全部輔助函數公用,用於適配「單純的map寫法」以及「帶上命名空間的寫法」。api

      • 「單純的map寫法」是指沒有設置命名空間前綴,直接索取須要的變量,如

        圖

      • 「帶上命名空間的寫法」即調用 createNamespacedHelpers 返回帶上命名空間前綴的輔助函數。即官網介紹的這個

        圖

      • createNamespacedHelpers的內部,是將複製函數的第一個參數填寫爲null,第二個爲命名空間前綴

        圖

      • 而咱們看回各輔助函數的定義,是經過normalizeNamespace函數生成的,即至關於往normalizeNamespace函數中,第一個參數填寫爲null,第二個爲命名空間前綴
      • normalizeNamespace函數,是對命名空間前綴的識別和兼容,bind(null)是爲了避免改變this的指向,讓this仍然指向vue組件,參數二是命名空間前綴。這是輔助函數的第一個參數namespace就有了值。
      • 經過bind函數,使得後續傳遞參數時,後續使用時,從參數的第二個開始填充,此項技術爲偏函數
        圖
      • 經過偏函數,使得在實際使用時,直接傳遞所需的屬性,與「單純的map寫法」統一用法
      • 下面介紹的是沒有「單純的map寫法」的流程,「帶上命名空間的寫法」如此類推
    • 回調函數參數介紹,namespace是命名空間前綴,states是在調用mapState時,用戶傳遞的內容,能夠一個是包含函數,或者鍵值對的對象,或者一個單純的key數組。

      圖

    • 定義一個對象res
    • normalizeMap函數,一樣全部輔助函數公用,用於兼容數組寫法和對象寫法。數組

      • 將內容均解析爲key,val對象,例如
      • normalizeMap([a, b, c]) => [ { key: a, val: a }, { key: b, val: b }, { key: c, val: c } ]
      • normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
      • 這也爲何能兼容多種傳值方式

        圖

    • 對返回的每一個key,val對象,調用回調函數。往res對象中,添加名爲上面生成的key的函數promise

      • 找到state和getters,並判斷是否設置命名空間,返回不一樣的值。有命名空間則經過 getModuleByNamespace 函數返回,緩存

        • 拿到具體模塊內部的context變量中的state和getters(實際上也是this.$store.state中的內容,只是添加了命名空間前綴)
      • 兼容函數寫法和對象或數組寫法,數組或對象,則直接返回值。函數則返回一個綁定了this的函數,並出入state和getters,對應官網,官網的介紹,缺了對參數二getters的介紹,其實若是傳入函數時,函數能夠接受第二個參數,getter。
    • res[key].vuex = true // 函數標識符,開發中沒什麼用,在調試工具中使用。
  • 對象展開符ide

    • 因爲返回的是一個res對象,因此能夠經過對象展開運算符,展開每個對象屬性。
    • 因爲res對象的屬性都是一個個函數,因此用在computed中定義計算屬性,而不是放在data中定義。

接着介紹的是getter

  • 經過store 的計算屬性,例如,this.$store.getters.doneTodosCount

    • this.$store.getters,即store對象的getters屬性,坑爹的是,getters也不是在構造函數中定義的。getters在resetStoreVM中定義的。

      圖

    • 經過Object.defineProperty函數,爲getters的屬性定義攔截器,返回store._vm[key]
    • 而store._vm[key],即調用store內部的vue組件的屬性,對應的屬性,經過了computed 計算屬性去定義,計算屬性的函數即爲getter函數自己
    • 因此官網介紹「Vuex 容許咱們在 store 中定義「getter」(能夠認爲是 store 的計算屬性)」,其實自己就是計算屬性,因此才能將計算結果緩存起來。
  • 經過屬性訪問,例如,this.$store.getters.doneTodosCount

    • 正如剛剛說的,是在computed定義,固然能夠經過對象的方式訪問。就比如咱們在vue中在computed定義屬性,在函數中引用。
  • 經過方法訪問,例如,this.$store.getters.getTodoById(2)

    • 即在函數中,返回函數,沒有好講的。
  • mapGetters 輔助函數

    • 大致方法和mapsState相似,一樣是同命名空間進行了一些兼容,再同全局getter容器中返回this.$store.getters。

Mutation

  • 示例,store.commit('increment')運行流程

    • 官網中的store,是直接使用Store對象,放在vue組件中,則經過this.$store訪問。
    • commit的定義在store的構造函數中,commit函數是綁定了this爲store的函數,綁定this是爲了在輔助函數使用時,this指針不被改變,前介紹過,bind(null)。

      圖

    • 調用commit函數時,

      • 從_mutations容器中,獲取與commit提交的mutation函數同名的數組,即保存同名函數的數組。
      • 調用_withCommit,執行commit時,將狀態設置爲committing。經過committing標示符,使得其餘修改state的都是非法操做。
      • 對數組中的每個函數進行調用,並傳入負載參數,對應官網提交載荷(Payload)

        • 爲何在定義mutation中,還有一個state變量呢?
        • 這由於在註冊Mutation函數函數時(registerMutation函數),已經經過call函數,local.state放入了函數的第一個參數中。
      • this._subscribers是在插件中使用的,對每一個commit函數的進行監聽,訂閱 store 的 mutation。handler 會在每一個 mutation 完成後調用,該功能經常使用於插件。
  • 對象風格的提交方式

    • 這個特性是在commit函數第一句被設置的,經過unifyObjectStyle函數兼容對象寫法和負載參數寫法。
    • unifyObjectStyle函數的原理就是,判斷參數是否爲對象,是對象則進行解析,並調整參數位置。

      圖

  • Mutation 需遵照 Vue 的響應規則

    • 其實這個跟mutation沒什麼直接關係,只是說當mutation中使用到state的某屬性時,須要提早在state中定義,而不是中往state插入元素,即便插入,也須要經過vue.set插入。
    • 由於store內部,也是經過Vue的date來保存state的。既然想要響應式。天然是須要遵循Vue的規則。
  • 使用常量替代 Mutation 事件類型,這是代碼風格問題,與邏輯無關。
  • Mutation 必須是同步函數

    • 由於在store的commit裏面,是對mutaion的簡單調用,並無設置回調函數,或者promise resolve。因此必須是同步函數。
  • 在組件中提交 Mutation

    • 大致方法和mapsState相似,一樣是同命名空間進行了一些兼容,再同全局_mutation容器中返回,沒什麼好講的。

action

  • 函數參數

    • 定義action時,能夠傳入不少參數。這些參數是從哪裏來的呢?
    • 找到registerAction函數,能夠看到是在註冊Action的時候傳遞進入的
      圖

module

  • module部分均在介紹模塊的配置,屬於配置過程,在Vuex的初始化中生效,沒有太多的運行邏輯。

總結

  • Vuex自己原理很簡單,可是爲了模塊化,加上了命名空間,添加一堆適配的代碼。嚴重增長了代碼複雜度。

    圖

工做繁忙,斷斷續續,歷時一個月,終於寫完。

  • 但願能你們理解Vuex源碼
  • 文章繁瑣,不用打我
  • 文章有必定紕漏,歡迎指正

圖

各位大佬,以爲OK的話,幫忙點個讚唄~

總目錄

相關文章
相關標籤/搜索