使用vue已經有半年有餘, 在各類正式非正式項目中用過, 開始專一於業務比較多, 用到如今也碰見很多由於理解不深致使的問題. 有問題就有找緣由的勇氣, 因此帶着問題搞一波.html
因此來整理了一下使用過程當中不注意或者不規範, 或者簡化寫法的奇技淫巧, 會結合文檔的說明和實際的問題來看看源碼, 問題:vue
看的源碼版本爲vuex2.3.1git
咱們使用vuex多是相似:github
import Vue from 'vue' import Vuex from 'vuex' import plugins from './plugins' Vue.use(Vuex) export default new Vuex.Store({ state: { todo: ["todo1", "todo2"] }, mutations: { mutationName(state, payload) { state.xxx = payload.xxx } }, actions: { actionName({ commit, dispatch }, payload) { commit(mutationName, payload) } }, modules: { catagories: { state: {}, mutations: {} } }, plugins })
使用vuex的方法爲使用Vue.use
來installvuex
, 並new一個Store實例, 咱們來看一下vuex核心對象.vuex
line6: 本地vue變量, 在install時會被賦值, 以後會經過vue是否爲undefined
來判斷是否installapi
line10~14: 判斷vuex是否被正確使用
line16~26: 獲取options, state
能夠和vue的component的data
同樣爲函數return一個對象, 會在這段代碼中被parse
line28~36: store對象內部變量初始化
line39~46: 綁定commit和dispatch方法到自身
line54: 裝載動做
line58: 裝載響應動做
line61: 調用插件數組
this._committing = false
是否合法更新state的標識, 對象有方法_withCommit
是惟一能夠改動_committing
的方法, 只有對象內部使用_withCommit
更新狀態纔是合法的, 在strict
模式下非法更新state會拋出異常.數據結構
this._modules = new ModuleCollection(options)
modules的cache, 直接把store的參數所有扔給了ModuleCollection
新建一個modules對象.app
點擊跳轉ModuleCollection對象來看分析.函數
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
其他的變量是新建了空的變量, 以後會在install模塊的時候賦值.
在line39~46, 對dispatch和commit方法進行綁定, 使dispatch方法能夠調用在Store對象上註冊過的._actions
和._mutations
的方法.
dispatch方法在line108, 先兼容了參數的寫法, 取到參數, 而後判斷Store對象的_.actions
屬性是否註冊過, 若是註冊過多個, 將會依次調用. 也就是若是type重複了也是會調用屢次的, 這個地方若是出錯debug會很是困難. 暫時沒有理解vuex此處設計的意圖.
commit方法稍微多一點, 大致思路是同樣的, 只是直接執行沒有返回值, dispatch會返回執行結果. 另外在line95進行了subscriber的操做, 咱們暫且不知道subscriber的做用. 稍後再看.
首先來看參數:
function installModule (store, rootState, path, module, hot) // 調用 installModule(this, state, [], this._modules.root)
line255 根據path得到namespace, 作法是讀取path的每一個模塊, 若是namespaced爲true則拼接, 例如path爲['catagories', 'price', 'detail']
, 其中price
的namespaced爲false, 其他爲true, 那麼得到的namespace爲catagories/detail/
.
line258~260 把namespaced爲true的module註冊到_modulesNamespaceMap
.
line271的makeLocalContext
函數整理了namespace和type的關係. 在以後的三個module.forEachXxx
中, 都調用了registerXxx
, 最後的參數都是makeLocalContext
的返回值. 咱們來分析一下makeLocalContext
的做用:
被註冊到全局的mutation/actiongetter實際的type相似於namespace1/namespace2/type
的形式, 而咱們在namespaced爲true的module中調用的type只是:type
. 因此在namespace[true]的action中調用的全部dispatch
, commit
, getter
, state
都會被加上 path.join("/") + "/" 的type來調用到正確的方法.
根據註冊的type, 我還獲得了一個偏門結論: 能夠經過設置type爲namespace1/namespace2/type
來調用其餘namespace的type(待測試), 由於他們是這樣被註冊的.
經過比較, install child module的時候是改了第三第四個參數: path
=> path.concat(key)
, module
=> module.getChild(key)
.
主要區別只是在line264~268, 與ModuleCollection的遞歸註冊子module行爲相似, 遞歸的path參數流程上只是多了一步把當前loop產生的對象掛到父節點上. 作法也是同樣的, 把module名字(path)做爲key, 套在父級state上. 也就是結構爲:
state: { ...currentState, moduleName: { ...subState }, module2Name: { ...anotherSubState } }
在以前註冊Mutation的時候vuex也是經過這個方法來試mutation得到嵌套過的state做爲arguments[0]的.
store對象把傳入的options放入了各個變量進行儲存, 並提供了commit, dispatch等方法來調用和處理他們:
._modules
這裏存放raw的modules, 未經處理的, 以module名字做爲key的方式遞歸子module.
.state
這裏也是以module名字做爲key的方式遞歸儲存傳入的state
這裏的entry指._actions
, ._mutations
, ._getters
. 他們的儲存方式並無遞歸儲存key, 而是平級的, 用/
來分割namespace來分辨type, 並在註冊時把當前的entry綁定對應的state(經過getNestedState
方法).
問題: 若是在不一樣module註冊了相同type的mutation, 會發生什麼?
回答: 會依次在本身的state中執行, 不會影響對方state, 可是會形成錯誤執行. (待測試). 因此應該在大的項目中儘可能使用namespaced[true]的方式, 而不是命名的方式.(可是也是能夠利用/
來串namespace的, 因此本身type命名避免/
)
._modulesNamespaceMap
根據namespace爲key來存放子module
這裏會新建一個Vue實例並賦值給Store對象的._vm
屬性, 把整個vuex的狀態放進去. 並判斷嚴格模式, 若是爲嚴格模式會在非法改變狀態的時候拋出異常.
這樣整個構建動做已經完成了, 那麼這個._vm
在何時用的, 請看下面的章節.
line64 state的getter方法, 會獲取._vm
的vue實例的state. 因此咱們在vue代碼中this.$store.state.xxx
獲取到的東西就是這個vue的實例的數據.
line68 當直接set Store的state時報錯, 只能經過設置._vm
來進行.
剩餘的方法的是vuex的進階用法, 是能夠在使用時對vuex狀態進行操做的方法, 詳見文檔
咱們來看下ModuleCollection
的構造方法.
調用了register
方法, 把參數的path設爲根目錄, runtime設爲false.
register
方法一開始(l30)就判斷了除state
外的屬性的值是否爲函數, 若不是則拋出異常.
line33 把module參數(仍是初始的options, 就是{state:{...}, mutations:{...}, actions: {...}}
這個)和runtime = false 來構建了Module
對象(稍後咱們看Module對象的構造)
line35 把ModuleCollection
的root私有變量設爲了剛纔使用初始options新建的Module
對象.
line42 若是初始options有modules這個屬性, 就開始遞歸註冊modules.
上面是register
的第一個參數path
爲空, 也就是root節點的時候的流程, 在最後一部分(line42)根據是否當前註冊的module含有modules屬性來遞歸註冊, 這部分咱們來看一下register的path參數的行爲會把數據存成什麼結構. 以概覽部分的例子的參數爲例(modules含有一個key爲catagories
)來走一遍代碼流程. (開始~)
被做爲子module傳入register
方法的參數應該爲: path
(['catagories']), rawModule
(state: {},mutations: {}), runtime
(false).
注意到的是, 若是catagories
有同級module, 被傳入的path
也是一個元素的數組, 也就是path的意思應該相似於從跟到當前module的層級, 對於兄弟節點是無感的.
這裏的runtime
還沒有明白用途, 多是在別處調用的. 註冊流程應該runtime都爲false.
一路看下來, 也是new了一個Module
對象, 可是沒有走到line35把new出的對象放到root
變量裏, 而是在line37~38去尋找當前module的父節點並把本身做爲child, append到父節點上.
這裏又腦補了一下數據結構: path.slice(0, -1)
是獲取被pop()一下的path, path[path.lengt - 1]
是獲取當前path的最後一個元素, 也就是當前正在被register的module的key. 因此以前對於path的數據結構判斷是正確的.
這裏的appendChild
和getChild
很明顯是Module
對象的方法了, 咱們再繼續看Module
對象的結構.
Module
對象最後來看Module
對象的構造~
接受2個參數, 一個rawModule
, 一個runtime
, 第一個參數是剛纔相對於key爲catagories
的value, 也就是相似{state: xxx, mutations: xxx, actions: xxx}
的options.
Module
的構造函數只是把參數拆分, 放入了本身的私有變量, 其中state
也接受函數, 並執行函數parse成對象存入私有變量. 其餘變量都是原封不動儲存的, 因此vuex給他起名爲 rawModule 吧. 剩下那些方法都是顧名思義的, 語法上也簡單, 沒什麼好看的.
那麼這樣Store對象的._modules
屬性的數據結構已經很清楚了. 相似於(腦內):
{ // (ModuleCOllection實例) root: { // (Module實例) _rawModule: { state: {...}, mutations: {...}, // ...(全是options直接傳入) }, state: {}, // 進行過parse的state, 若是是function會調用並賦值 _children: { catagories: { // (Module實例) }, anotherModule: { // (Module實例), 遞歸 } } } }
總結一點, 就是這裏貯存的數據都是"raw"的.
全部的helper都用了兩個wrap方法, 先來看下這兩個方法的做用.
normalizeNamespace
由於helper是都接受兩種傳參方式:mapState(namespace, map)
/ mapState(map)
, 若是第一個參數爲map時這個函數把namespace設爲空字符串 , 而且檢查namespace的最後一個字符是否是/
, 若是不是的話加上.
normalizeMap
咱們map的內容也接受兩種語法:
[ "state1", "state2" ]
或者是
{ state1: state => state.state1, state2: state => state.state2 }
這個wrap函數會把兩種形式都normalize爲含有key
和val
屬性的數組, 便於統一處理. 也就是上面個兩個形式會轉化爲:
// Array like [{ key: "state1", val: "state1" }, { key: "state2", val: "state2" }] // Object like [{ key: "state1", val: state => state.state1 }, { key: "state2", val: state => state.state2 }]
這裏作了2個處理:
makeLocalContext
的返回值)mapAction的val語法只接受字符串的, 因此先把val前借namespace, 變爲: namespace/val
, 這樣能符合在Store裏註冊的entry名.
而後檢查了一下namespace是否被註冊過, 也就是防碰撞, 而後把val做爲type, 並把剩餘參數帶着dispatch Store裏的action.
參考: