從未看過源碼,到底該如何入手?分享一次完整的源碼閱讀過程

?鏈接每一位開發(fā)者,讓編程更有趣兒!關(guān)注
我覺得每個(gè)人可能都有過看源碼的想法吧,也包括我。因?yàn)榭丛创a不光能使自己對這個(gè)庫更加熟悉,還能學(xué)習(xí)到作者強(qiáng)大的思想,久而久之,自己的水平和思想也會有明顯的提升的。
但對于我來說,之前從來沒有閱讀過源碼,想閱讀源碼卻不敢邁出那一步,因?yàn)橐粋€(gè)成熟的庫有著太多的方法、邏輯,閱讀起來可能會比較困難,但人總要勇于嘗試的嘛,于是我就準(zhǔn)備把Vuex?的源碼?clone?下來,沒有別的原因,只是因?yàn)檫@個(gè)庫體積比較小,算上注釋,核心代碼只有1000行不到,我覺得非常適合第一次閱讀源碼的人拿來練手
說干就干,我就先在?github?上給自己列了一個(gè)計(jì)劃表,預(yù)計(jì)?15?天看完源碼并完成總結(jié),然后每天記錄一下當(dāng)天的收獲

不過最后的結(jié)果倒是出乎我的意料,閱讀源碼加上整理總結(jié)只用了8天左右的時(shí)間
在閱讀源碼之前,我是先去看了一下?Vuex?的官方文檔,算是一種回顧、查漏補(bǔ)缺,我也非常建議這樣做,因?yàn)槟憧丛创a,你就會看到這個(gè)庫里面所有的內(nèi)容,那么你連這個(gè)庫都沒用明白呢,閱讀源碼的難度無形之中又增加了嘛!即先會熟練使用這個(gè)庫的各個(gè)方法(盡管你并不知道為何這么使用),再在閱讀源碼的過程中看到相應(yīng)的代碼時(shí)聯(lián)想到那個(gè)方法的使用,兩者相互結(jié)合,對于源碼的理解就變得容易許多了
這里放上?Vuex?官方文檔的鏈接,如果有興趣跟著我的思路閱讀?Vuex?源碼的小伙伴可以先把文檔中提到的所有使用都熟悉一下
????「Vuex官方文檔」:https://vuex.vuejs.org/zh/
?
??? 源碼解析文末有?「總結(jié)」?和?「問答環(huán)節(jié)」
?
對于源碼的所有注釋和理解我都收錄在我?github?的?Vuex-Analysis?倉庫里了,想要看更詳細(xì)的注釋的,可以?fork?下來參考一下(點(diǎn)擊文末的?「閱讀原文」?跳轉(zhuǎn)我的倉庫地址)
接下來本文就按照我當(dāng)時(shí)閱讀源碼的思路,一步一步詳細(xì)地講解,希望大家耐心看完,謝謝啦~
一、源碼目錄結(jié)構(gòu)分析
整個(gè)?Vuex?的源碼文件非常多,我們直接看最主要的文件,即?src?文件夾中的內(nèi)容,結(jié)構(gòu)示例如下:
├──?src
????├──?module????//?與模塊相關(guān)的操作
????│???├──?module-collection.js???//?用于收集并注冊根模塊以及嵌套模塊
????│???└──?module.js???//?定義Module類,存儲模塊內(nèi)的一些信息,例如:?state...
????│
????├──?plugins???//?一些插件
????│???├──?devtool.js???//?開發(fā)調(diào)試插件
????│???└──?logger.js????//?
????│
????├──?helpers.js???????//?輔助函數(shù),例如:mapState、mapGetters、mapMutations...
????├──?index.cjs.js?????//?commonjs?打包入口
????├──?index.js?????????//?入口文件
????├──?index.mjs????????//?es6?module?打包入口
????├──?mixin.js?????????//?將vuex實(shí)例掛載到全局Vue的$store上
????├──?store.js?????????//?核心文件,定義了Store類
????└──?util.js??????????//?提供一些工具函數(shù),例如:?deepCopy、isPromise、isObject...
二、源碼閱讀
1. 查看工具函數(shù)
首先我個(gè)人覺得肯定是要看一下?util.js?,這里面存放的是源碼中頻繁用到的工具函數(shù),所以我覺得要最先了解一下每個(gè)函數(shù)的作用是什么
/**
?*?Get?the?first?item?that?pass?the?test
?*?by?second?argument?function
?*
?*?@param?{Array}?list
?*?@param?{Function}?f
?*?@return?{*}
?*/
//?找到數(shù)組list中第一個(gè)符合要求的元素
export?function?find?(list,?f)?{
??return?list.filter(f)[0]
}
/**
?*?深拷貝
?*?
?*?@param?{*}?obj
?*?@param?{Array?cache
?*?@return?{*}
?*/
export?function?deepCopy?(obj,?cache?=?[])?{
??//?just?return?if?obj?is?immutable?value
??if?(obj?===?null?||?typeof?obj?!==?'object')?{
????return?obj
??}
??//?if?obj?is?hit,?it?is?in?circular?structure
??const?hit?=?find(cache,?c?=>?c.original?===?obj)
??if?(hit)?{
????return?hit.copy
??}
??const?copy?=?Array.isArray(obj)???[]?:?{}
??//?put?the?copy?into?cache?at?first
??//?because?we?want?to?refer?it?in?recursive?deepCopy
??cache.push({
????original:?obj,
????copy
??})
??Object.keys(obj).forEach(key?=>?{
????copy[key]?=?deepCopy(obj[key],?cache)
??})
??return?copy
}
//?遍歷obj對象的每個(gè)屬性的值
export?function?forEachValue?(obj,?fn)?{
??Object.keys(obj).forEach(key?=>?fn(obj[key],?key))
}
//?判斷是否為對象(排除null)
export?function?isObject?(obj)?{
??return?obj?!==?null?&&?typeof?obj?===?'object'
}
//?判斷是否為Promise對象
export?function?isPromise?(val)?{
??return?val?&&?typeof?val.then?===?'function'
}
//?斷言
export?function?assert?(condition,?msg)?{
??if?(!condition)?throw?new?Error(`[vuex]?${msg}`)
}
//?保留原始參數(shù)的閉包函數(shù)
export?function?partial?(fn,?arg)?{
??return?function?()?{
????return?fn(arg)
??}
}
每個(gè)函數(shù)的作用我都寫上了注釋,稍微閱讀一下應(yīng)該可以明白其作用
2. 入口文件
最主要的代碼都在?src?目錄下,所以以下提到的文件都是默認(rèn)?src?目錄下的文件
首先,肯定從入口文件?index.js?開始看,但能發(fā)現(xiàn)的是,還有?index.cjs?和?index.mjs?,這兩者分別是?commonjs?和?es6 module?的打包入口,我們就不用管了
import?{?Store,?install?}?from?'./store'
import?{?mapState,?mapMutations,?mapGetters,?mapActions,?createNamespacedHelpers?}?from?'./helpers'
import?createLogger?from?'./plugins/logger'
export?default?{
??Store,
??install,
??version:?'__VERSION__',
??mapState,
??mapMutations,
??mapGetters,
??mapActions,
??createNamespacedHelpers,
??createLogger
}
export?{
??Store,
??install,
??mapState,
??mapMutations,
??mapGetters,
??mapActions,
??createNamespacedHelpers,
??createLogger
}
從入口文件中可以看到,主要導(dǎo)出了?Store?類 、install?方法以及一些輔助函數(shù)(mapState、mapMutations、mapGetters...)
那么我們主要看的就是?vuex?的核心代碼,即?store.js?,可以看到?Store?類就出自于這個(gè)文件
3. Store類的實(shí)現(xiàn)
整個(gè)?Store?類的主要邏輯都在它的構(gòu)造函數(shù)?constructor?中,因此我們就從?constructor?中分步去捋邏輯、看代碼
3.1 存放類的狀態(tài)
首先是定義了一些實(shí)例狀態(tài),用于存放模塊、mutations?、actions?、getters?緩存等東西
const?{
??plugins?=?[],
??strict?=?false
}?=?options??????//?生成Store類的入?yún)?/span>
this._committing?=?false????????//?表示提交的狀態(tài),當(dāng)通過mutations方法改變state時(shí),該狀態(tài)為true,state值改變完后,該狀態(tài)變?yōu)閒alse;?在嚴(yán)格模式下會監(jiān)聽state值的改變,當(dāng)改變時(shí),_committing為false時(shí),會發(fā)出警告,即表明state值的改變不是經(jīng)過mutations的
this._actions?=?Object.create(null)??//?用于記錄所有存在的actions方法名稱(包括全局的和命名空間內(nèi)的,且允許重復(fù)定義)??????
this._actionSubscribers?=?[]???????//?存放actions方法訂閱的回調(diào)函數(shù)
this._mutations?=?Object.create(null)??//?用于記錄所有存在的的mutations方法名稱(包括全局的和命名空間內(nèi)的,且允許重復(fù)定義)
this._wrappedGetters?=?Object.create(null)??//?收集所有模塊包裝后的的getters(包括全局的和命名空間內(nèi)的,但不允許重復(fù)定義)
this._modules?=?new?ModuleCollection(options)??//?根據(jù)傳入的options配置,注冊各個(gè)模塊,此時(shí)只是注冊、建立好了各個(gè)模塊的關(guān)系,已經(jīng)定義了各個(gè)模塊的state狀態(tài),但getters、mutations等方法暫未注冊
this._modulesNamespaceMap?=?Object.create(null)???//?存儲定義了命名空間的模塊
this._subscribers?=?[]????//?存放mutations方法訂閱的回調(diào)
this._watcherVM?=?new?Vue()??//?用于監(jiān)聽state、getters
this._makeLocalGettersCache?=?Object.create(null)???//?getters的本地緩存
關(guān)于各個(gè)變量狀態(tài)的作用都寫在這了,其中只有?this._modules = new ModuleCollection(option)?執(zhí)行了一些操作,其作用就是進(jìn)行「模塊遞歸收集」,根據(jù)?ModuleCollection?的來源,我們移步到?./module/module-collection.js?文件
3.1.1 遞歸收集模塊
在?Module-collection.js?文件中定義了?ModuleCollection?類,其作用就是通過遞歸遍歷?options?入?yún)?,將每個(gè)模塊都生成一個(gè)獨(dú)立的?Moudle
這里先來熟悉一下?options?的結(jié)構(gòu),如下:
import?Vuex?from?'vuex'
const?options?=?{
??state:?{...},
??getters:?{...},
??mutations:?{...},
??actions:?{...},
??modules:?{
????ModuleA:?{
??????state:?{...},
??????...
??????modules:?{
????????ModuleA1:?{...}
??????}
????},
????ModuleB:?{
??????state:?{...},
??????...
??????modules:?{
????????ModuleB1:?{...}
??????}
????}
??}
}
const?store?=?new?Vuex.Store(options)
export?default?store
可以看到傳入的?options?整體可以看成一個(gè)根模塊?root?,然后?root?的?modules?中嵌套著另外兩個(gè)子模塊:ModuleA?和ModuleB?,而?ModuleA?和ModuleB?內(nèi)部也分別嵌套著一個(gè)子模塊,分別為?ModuleA1?、ModuleB1?。這樣就組成了一個(gè)模塊樹,因此?ModuleCollection?類的工作就是將保留原來的模塊關(guān)系,將每個(gè)模塊封裝到一個(gè)?Module?類中
export?default?class?ModuleCollection?{
??constructor?(rawRootModule)?{
????//?遞歸注冊模塊
????this.register([],?rawRootModule,?false)
??}
??
??//?根據(jù)路徑順序,從根模塊開始遞歸獲取到我們準(zhǔn)備添加新的模塊的父模塊
??get?(path)?{
????return?path.reduce((module,?key)?=>?{
??????return?module.getChild(key)
????},?this.root)
??}
??
??//?遞歸注冊模塊
??register?(path,?rawModule,?runtime?=?true)?{
????if?(__DEV__)?{
??????assertRawModule(path,?rawModule)
????}
????
????const?newModule?=?new?Module(rawModule,?runtime)??//?初始化一個(gè)新的模塊
????if?(path.length?===?0)?{????//?當(dāng)前沒有別的模塊
??????this.root?=?newModule?????//?則此模塊為根模塊
????}?else?{????//?有多個(gè)模塊?????
??????const?parent?=?this.get(path.slice(0,?-1))???//?獲取到新模塊從屬的父模塊,所以是path.slice(0,?-1),最后一個(gè)元素就是我們要添加的子模塊的名稱
??????parent.addChild(path[path.length?-?1],?newModule)????//?在父模塊中添加新的子模塊
????}
????if?(rawModule.modules)?{?????//?如果有嵌套模塊
??????/**
???????*??1.?遍歷所有的子模塊,并進(jìn)行注冊;
???????*??2.?在path中存儲除了根模塊以外所有子模塊的名稱
???????*??*/?
??????forEachValue(rawModule.modules,?(rawChildModule,?key)?=>?{
????????this.register(path.concat(key),?rawChildModule,?runtime)
??????})
????}
??}
}
「函數(shù)作用:」
register(path, rawModule, runtime):注冊新的模塊,并根據(jù)模塊的嵌套關(guān)系,將新模塊添加作為對應(yīng)模塊的子模塊
- path:表示模塊嵌套關(guān)系。當(dāng)前為根模塊時(shí),沒有任何嵌套關(guān)系,此時(shí)?
path = []?; 當(dāng)前不是根模塊時(shí),存在嵌套關(guān)系,例如上述例子中的?ModuleA1?,它是?ModuleA的子模塊 ,而?ModuleA?又是根模塊的子模塊,此時(shí)?path = ['ModuleA', 'ModuleA1'] - rawModule:表示模塊對象,此時(shí)是一個(gè)對象類型
- runtime:表示程序運(yùn)行時(shí)
get(path):根據(jù)傳入的?path?路徑,獲取到我們想要的?Module?類
ModuleCollection?的構(gòu)造函數(shù)中調(diào)用了?register?函數(shù),前兩個(gè)參數(shù)分別為:[]?、rawRootModule?,此時(shí)肯定是從根模塊開始注冊的,所以?path?里無內(nèi)容,并且?rawRootModule?指向的是根模塊
然后來看一下?register?函數(shù)里的邏輯。
首先將當(dāng)前要注冊的模塊生成一個(gè)?
Module?,并將?rawModule?作為參數(shù),用于存放?Module?的信息然后通過?
if(path.length === 0)?判斷是否為根模塊,是的話就將?this.root指向?Module?; 否則就跳到第3步判斷當(dāng)前模塊不是根模塊,就通過?
get?函數(shù)找到當(dāng)前模塊的父模塊,然后調(diào)用父模塊中的?addChild?方法將當(dāng)前模塊添加到子模塊中最后再判斷當(dāng)前模塊是否還有嵌套的模塊,有的話就重新回到第1步進(jìn)行遞歸操作 ; 否則不做任何處理
按照上面的邏輯,就可以將所有的模塊遞歸收集并注冊好了,其中有一個(gè)?Module?類還沒有具體提到,所以這里移步到?./module/module.js
import?{?forEachValue?}?from?'../util'
//?定義了Vuex中的?Module?類,包含了state、mutations、getters、actions、modules
export?default?class?Module?{
??constructor?(rawModule,?runtime)?{
????this.runtime?=?runtime
????
????this._children?=?Object.create(null)???//?創(chuàng)建一個(gè)空對象,用于存放當(dāng)前模塊的子模塊
????
????this._rawModule?=?rawModule?????????//?當(dāng)前模塊的一些信息,例如:state、mutations、getters、actions、modules
????const?rawState?=?rawModule.state????//?1.?函數(shù)類型?=>?返回一個(gè)obj對象;?2.?直接獲取到obj對象
????//?存儲當(dāng)前模塊的state狀態(tài)
????this.state?=?(typeof?rawState?===?'function'???rawState()?:?rawState)?||?{}???
??}
??//?判斷該模塊是否定義了namespaced,定義了則返回true;?否則返回false
??get?namespaced?()?{
????return?!!this._rawModule.namespaced
??}
??//?添加子模塊,名稱為key
??addChild?(key,?module)?{
????this._children[key]?=?module
??}
??//?移除名稱為key的子模塊
??removeChild?(key)?{
????delete?this._children[key]
??}
??//?獲取名稱為key的子模塊
??getChild?(key)?{
????return?this._children[key]
??}
??//?是否存在名稱為key的子模塊
??hasChild?(key)?{
????return?key?in?this._children
??}
?
??//?將當(dāng)前模塊的命名空間更新到指定模塊的命名空間中,并同時(shí)更新一下actions、mutations、getters的調(diào)用來源
??update?(rawModule)?{
????this._rawModule.namespaced?=?rawModule.namespaced
????if?(rawModule.actions)?{
??????this._rawModule.actions?=?rawModule.actions
????}
????if?(rawModule.mutations)?{
??????this._rawModule.mutations?=?rawModule.mutations
????}
????if?(rawModule.getters)?{
??????this._rawModule.getters?=?rawModule.getters
????}
??}
??//?遍歷當(dāng)前模塊的所有子模塊,并執(zhí)行回調(diào)操作
??forEachChild?(fn)?{
????forEachValue(this._children,?fn)
??}
??//?遍歷當(dāng)前模塊的所有g(shù)etters,并執(zhí)行回調(diào)操作
??forEachGetter?(fn)?{
????if?(this._rawModule.getters)?{
??????forEachValue(this._rawModule.getters,?fn)
????}
??}
??//?遍歷當(dāng)前模塊的所有actions,并執(zhí)行回調(diào)操作
??forEachAction?(fn)?{
????if?(this._rawModule.actions)?{
??????forEachValue(this._rawModule.actions,?fn)
????}
??}
??//?遍歷當(dāng)前模塊的所有mutations,并執(zhí)行回調(diào)操作
??forEachMutation?(fn)?{
????if?(this._rawModule.mutations)?{
??????forEachValue(this._rawModule.mutations,?fn)
????}
??}
}
來看一下剛才模塊收集時(shí),創(chuàng)建的?Module?類內(nèi)部做了什么事情,同樣的從?constructor?中開始看
this._children?是一個(gè)對象值,用于存放該模塊嵌套的其它?Module?類 ;
this._rawModule?就是用于存放該模塊內(nèi)部的一些信息,例如:state、mutations?、actions?、getters?、moudles?;
this.state?對應(yīng)的就是?this._rawModule?中的?state?;
這是整個(gè)構(gòu)造函數(shù)中執(zhí)行的操作,我們可以看到,在生成一個(gè)?Module?類的時(shí)候,其只定義了?state?屬性,而?mutations?、getters?、actions?、modules?都是沒有被定義的,即例如現(xiàn)在是無法通過?Module.mutations?獲取到該模塊所有的?mutations?方法,那么這些方法都是在何時(shí)被定義的呢?自然是等模塊全部都收集完畢以后才進(jìn)行的操作,因?yàn)?vuex?中的嵌套模塊可能會存在命名空間?namespaced
3.2 注冊模塊
到此為止,各個(gè)模塊的類都創(chuàng)建好了,那么繼續(xù)回到?./src/store.js?的?constructor?構(gòu)造函數(shù)中
//?將?dispatch?和?commit?方法綁定到?Store?的實(shí)例上,避免后續(xù)使用dispatch或commit時(shí)改變了this指向
const?store?=?this
const?{?dispatch,?commit?}?=?this
this.dispatch?=?function?boundDispatch?(type,?payload)?{
??return?dispatch.call(store,?type,?payload)
}
this.commit?=?function?boundCommit?(type,?payload,?options)?{
??return?commit.call(store,?type,?payload,?options)
}
//?判斷store是否未嚴(yán)格模式。true:?所有的state都必須經(jīng)過mutations來改變
this.strict?=?strict
//?將根模塊的state賦值給state變量
const?state?=?this._modules.root.state
這段代碼首先對?Store?實(shí)例上的?dispatch?和?commit?方法進(jìn)行了一層包裝,即通過?call?將這兩個(gè)方法的作用對象指向當(dāng)前的?Store?實(shí)例,這樣就能防止后續(xù)我們操作時(shí),出現(xiàn)?this.$store.dispatch.call(obj, 1)?類似的情況而報(bào)錯(cuò)
this.strict?是用于判斷是否是嚴(yán)格模式。因?yàn)?vuex?中,建議所有的?state?變量的變化都必須經(jīng)過?mutations?方法,因?yàn)檫@樣才能被?devtool?所記錄下來,所以在嚴(yán)格模式下,未經(jīng)過?mutations?而直接改變了?state?的值,開發(fā)環(huán)境下會發(fā)出警告??
const state = this._modules.root.state??獲取的是根模塊的?state?,用于后續(xù)的一些操作
一切都準(zhǔn)備就緒了,下面就開始為每個(gè)模塊注冊信息了
//?從根模塊開始,遞歸完善各個(gè)模塊的信息
installModule(this,?state,?[],?this._modules.root)
調(diào)用了?installModule?方法,并將?store?實(shí)例對象 、state?屬性 、路徑 、根模塊對象依次作為參數(shù)進(jìn)行傳遞
//?注冊完善各個(gè)模塊內(nèi)的信息
function?installModule?(store,?rootState,?path,?module,?hot)?{
??const?isRoot?=?!path.length??//?是否為根模塊
??const?namespace?=?store._modules.getNamespace(path)??//?獲取當(dāng)前模塊的命名空間,格式為:second/?或 second/third/
??//?如果當(dāng)前模塊設(shè)置了namespaced?或?繼承了父模塊的namespaced,則在modulesNamespaceMap中存儲一下當(dāng)前模塊
??if?(module.namespaced)?{
????if?(store._modulesNamespaceMap[namespace]?&&?__DEV__)?{
??????console.error(`[vuex]?duplicate?namespace?${namespace}?for?the?namespaced?module?${path.join('/')}`)
????}
????store._modulesNamespaceMap[namespace]?=?module
??}
??//?如果不是根模塊,將當(dāng)前模塊的state注冊到其父模塊的state上
??if?(!isRoot?&&?!hot)?{
????const?parentState?=?getNestedState(rootState,?path.slice(0,?-1))?//?獲取父模塊的state
????const?moduleName?=?path[path.length?-?1]???//?當(dāng)前模塊的名稱
????store._withCommit(()?=>?{
??????if?(__DEV__)?{
????????if?(moduleName?in?parentState)?{
??????????console.warn(
????????????`[vuex]?state?field?"${moduleName}"?was?overridden?by?a?module?with?the?same?name?at?"${path.join('.')}"`
??????????)
????????}
??????}
??????//?將當(dāng)前模塊的state注冊在父模塊的state上,并且是響應(yīng)式的
??????Vue.set(parentState,?moduleName,?module.state)
????})
??}
??//?設(shè)置當(dāng)前模塊的上下文context
??const?local?=?module.context?=?makeLocalContext(store,?namespace,?path)
??//?注冊模塊的所有mutations
??module.forEachMutation((mutation,?key)?=>?{
????const?namespacedType?=?namespace?+?key?????//?例如:first/second/mutations1
????registerMutation(store,?namespacedType,?mutation,?local)
??})
??//?注冊模塊的所有actions
??module.forEachAction((action,?key)?=>?{
????/**
?????* actions有兩種寫法:
?????*?
?????*?actions:?{
?????*????AsyncAdd?(context,?payload)?{...},???//?第一種寫法
?????*????AsyncDelete:?{???????????????????????//?第二種寫法
?????*??????root:?true,
?????*??????handler:?(context,?payload)?{...}
?????*????}?
?????*?}
?????*/
????const?type?=?action.root???key?:?namespace?+?key???//?判斷是否需要在命名空間里注冊一個(gè)全局的action
????const?handler?=?action.handler?||?action??????????//?獲取actions對應(yīng)的函數(shù)
????registerAction(store,?type,?handler,?local)???
??})
??//?注冊模塊的所有g(shù)etters
??module.forEachGetter((getter,?key)?=>?{
????const?namespacedType?=?namespace?+?key
????registerGetter(store,?namespacedType,?getter,?local)
??})
??//?遞歸注冊子模塊
??module.forEachChild((child,?key)?=>?{
????installModule(store,?rootState,?path.concat(key),?child,?hot)
??})
}
const namespace = store._modules.getNamespace(path)?是將路徑?path作為參數(shù), 調(diào)用?ModuleCollection?類實(shí)例上的?getNamespace?方法來獲取當(dāng)前注冊對象的命名空間的
/**
*?根據(jù)模塊是否有命名空間來設(shè)定一個(gè)路徑名稱
*?例如:A為父模塊,B為子模塊,C為子孫模塊
*?1.?若B模塊命名空間為second,C模塊未設(shè)定命名空間時(shí);?C模塊繼承了B模塊的命名空間,為?second/
*?2.?若B模塊未設(shè)定命名空間,?B模塊命名空間為third;?則此時(shí)B模塊繼承的是A模塊的命名空間,而C模塊的命名空間路徑為?third/
*/
getNamespace?(path)?{
??let?module?=?this.root
??return?path.reduce((namespace,?key)?=>?{
????module?=?module.getChild(key)???//?獲取子模塊
????return?namespace?+?(module.namespaced???key?+?'/'?:?'')
??},?'')
}
從這可以看出,未指定命名空間的模塊會繼承父模塊的命名空間
??//?如果當(dāng)前模塊設(shè)置了namespaced?或?繼承了父模塊的namespaced,則在modulesNamespaceMap中存儲一下當(dāng)前模塊
if?(module.namespaced)?{
??if?(store._modulesNamespaceMap[namespace]?&&?__DEV__)?{
????console.error(`[vuex]?duplicate?namespace?${namespace}?for?the?namespaced?module?${path.join('/')}`)
??}
??store._modulesNamespaceMap[namespace]?=?module
}
這段代碼是將所有存在命名空間的模塊記錄在?store._modulesNamespaceMap?中,便于之后的輔助函數(shù)可以調(diào)用(這里還未提到輔助函數(shù),可以先不管,到時(shí)候回頭來看)
3.2.1 注冊模塊的state
//?如果不是根模塊,將當(dāng)前模塊的state注冊到其父模塊的state上
if?(!isRoot?&&?!hot)?{
??const?parentState?=?getNestedState(rootState,?path.slice(0,?-1))?//?獲取父模塊的state
??const?moduleName?=?path[path.length?-?1]???//?當(dāng)前模塊的名稱
??store._withCommit(()?=>?{
????if?(__DEV__)?{
??????if?(moduleName?in?parentState)?{
????????console.warn(
??????????`[vuex]?state?field?"${moduleName}"?was?overridden?by?a?module?with?the?same?name?at?"${path.join('.')}"`
????????)
??????}
????}
????//?將當(dāng)前模塊的state注冊在父模塊的state上,并且是響應(yīng)式的
????Vue.set(parentState,?moduleName,?module.state)
??})
}
這段代碼主要是將非根模塊的?state?掛載到父模塊的?state?上
const parentState = getNestedState(rootState, path.slice(0, -1))根據(jù)當(dāng)前的模塊路徑,從根模塊的?state?開始找,最終找到當(dāng)前模塊的父模塊的state,可以看一下?getNestedState?方法內(nèi)部的具體實(shí)現(xiàn)
//?獲取到嵌套的模塊中的state
function?getNestedState?(state,?path)?{
??return?path.reduce((state,?key)?=>?state[key],?state)
}
const moduleName = path[path.length - 1]?從路徑?path?中將當(dāng)前模塊的名稱提取出來
store._withCommit(()?=>?{
??if?(__DEV__)?{
????if?(moduleName?in?parentState)?{
??????console.warn(
????????`[vuex]?state?field?"${moduleName}"?was?overridden?by?a?module?with?the?same?name?at?"${path.join('.')}"`
??????)
????}
??}
??//?將當(dāng)前模塊的state注冊在父模塊的state上,并且是響應(yīng)式的
??Vue.set(parentState,?moduleName,?module.state)
})
這段代碼中最主要的部分就是?Vue.set(parentState, moduleName, module.state)?,作用就是調(diào)用了?Vue?的?set?方法將當(dāng)前模塊的?state?響應(yīng)式地添加到了父模塊的?state?上,這是因?yàn)樵谥笪覀儠吹?state?會被放到一個(gè)新的?Vue?實(shí)例的?data?中,所以這里不得不使用?Vue?的?set?方法來響應(yīng)式地添加
同樣的,從這段代碼中我們也可以知道了為什么平時(shí)在獲取子模塊上?state?的屬性時(shí),是通過?this.$store.state.ModuleA.name?這樣的形式來獲取的了
3.2.2 生成模塊調(diào)用上下文
//?設(shè)置當(dāng)前模塊的上下文context
const?local?=?module.context?=?makeLocalContext(store,?namespace,?path)
這行代碼也可以說是非常核心的一段代碼了,它根據(jù)命名空間為每個(gè)模塊創(chuàng)建了一個(gè)屬于該模塊調(diào)用的上下文,并將該上下文賦值了給了該模塊的?context?屬性
接下來看一下這個(gè)上下文是如何創(chuàng)建的吧
//?若設(shè)置了命名空間則創(chuàng)建一個(gè)本地的commit、dispatch方法,否則將使用全局的store
function?makeLocalContext?(store,?namespace,?path)?{
??const?noNamespace?=?namespace?===?''??
??const?local?=?{
????dispatch:?noNamespace???store.dispatch?:?(_type,?_payload,?_options)?=>?{
??????const?args?=?unifyObjectStyle(_type,?_payload,?_options)
??????const?{?payload,?options?}?=?args
??????let?{?type?}?=?args
??????if?(!options?||?!options.root)?{??//?若傳入了第三個(gè)參數(shù)設(shè)置了root:true,則派發(fā)的是全局上對應(yīng)的的actions方法
????????type?=?namespace?+?type
????????if?(__DEV__?&&?!store._actions[type])?{
??????????console.error(`[vuex]?unknown?local?action?type:?${args.type},?global?type:?${type}`)
??????????return
????????}
??????}
??????return?store.dispatch(type,?payload)
????},
????commit:?noNamespace???store.commit?:?(_type,?_payload,?_options)?=>?{
??????const?args?=?unifyObjectStyle(_type,?_payload,?_options)
??????const?{?payload,?options?}?=?args
??????let?{?type?}?=?args
??????if?(!options?||?!options.root)?{???//?若傳入了第三個(gè)參數(shù)設(shè)置了root:true,則派發(fā)的是全局上對應(yīng)的的mutations方法
????????type?=?namespace?+?type
????????if?(__DEV__?&&?!store._mutations[type])?{
??????????console.error(`[vuex]?unknown?local?mutation?type:?${args.type},?global?type:?${type}`)
??????????return
????????}
??????}
??????store.commit(type,?payload,?options)
????}
??}
??/**
???*?若沒有設(shè)定命名空間,則直接讀取store.getters(store.getters已經(jīng)掛載到vue實(shí)例的computed上了);
???*?若設(shè)定了命名空間,則從本地緩存_makeLocalGettersCache中讀取getters
???*/
??Object.defineProperties(local,?{
????getters:?{
??????get:?noNamespace
??????????()?=>?store.getters????
????????:?()?=>?makeLocalGetters(store,?namespace)
????},
????state:?{
??????get:?()?=>?getNestedState(store.state,?path)
????}
??})
??return?local
}
local?這個(gè)變量存儲的就是一個(gè)模塊的上下文。
先來看其第一個(gè)屬性?dispatch?,當(dāng)該模塊沒有設(shè)置命名空間時(shí),調(diào)用該上下文的?dispatch?方法時(shí)會直接調(diào)用?sotre.dispatch?,即調(diào)用了根模塊的?dispatch?方法 ; 而存在命名空間時(shí),會先判斷相應(yīng)的命名空間,以此來決定調(diào)用哪個(gè)?dispatch?方法
if (!options || !options.root)?是判斷調(diào)用?dispatch?方法時(shí)有沒有傳入第三個(gè)參數(shù)?{root: true}?,若有則表示調(diào)用全局根模塊上對應(yīng)的的?dispatch?方法
那么同樣的,local?中的?commit?屬性就類似于?dispatch?,這里就不多說了
然后最后通過?Object.defineProperties?方法對?local?的?getters?屬性和state?屬性設(shè)置了一層獲取代理,等后續(xù)對其訪問時(shí),才會進(jìn)行處理。例如,訪問?getters?屬性時(shí),先判斷是否存在命名空間,若沒有,則直接返回?store.getters?; 否則的話,根據(jù)命名空間創(chuàng)建一個(gè)本地的?getters?緩存,根據(jù)這個(gè)緩存來獲取對應(yīng)的?getters?,來看一下代碼
//?創(chuàng)建本地的getters緩存
function?makeLocalGetters?(store,?namespace)?{
??//?若緩存中沒有指定的getters,則創(chuàng)建一個(gè)新的getters緩存到__makeLocalGettersCache中
??if?(!store._makeLocalGettersCache[namespace])?{
????const?gettersProxy?=?{}
????const?splitPos?=?namespace.length
????Object.keys(store.getters).forEach(type?=>?{
??????//?如果store.getters中沒有與namespace匹配的getters,則不進(jìn)行任何操作
??????if?(type.slice(0,?splitPos)?!==?namespace)?return
??????//?獲取本地getters名稱
??????const?localType?=?type.slice(splitPos)
??????//?對getters添加一層代理
??????Object.defineProperty(gettersProxy,?localType,?{
????????get:?()?=>?store.getters[type],
????????enumerable:?true
??????})
????})
????//?把代理過的getters緩存到本地
????store._makeLocalGettersCache[namespace]?=?gettersProxy
??}
??return?store._makeLocalGettersCache[namespace]
}
當(dāng)存在命名空間時(shí)訪問?local.getters?,首先會去?store._makeLocalGettersCache?查找是否有對應(yīng)的?getters?緩存,若沒有,則創(chuàng)建一個(gè)?gettersProxy?,在store.getters?上找到對應(yīng)的?getters?,然后用?Object.defineProperty?對gettersProxy?做一層處理,即當(dāng)訪問?local.getters.func?時(shí),相當(dāng)于訪問了?store.getters['first/func']?,這樣做一層緩存,下一次訪問該?getters?時(shí),就不會重新遍歷?store.getters?了 ; 若有緩存,則直接從緩存中獲取
上下文已經(jīng)創(chuàng)建好了,接下來就是注冊?mutations?、actions?、getters?了
3.2.3 注冊模塊的mutations
//?注冊模塊的所有mutations
module.forEachMutation((mutation,?key)?=>?{
??const?namespacedType?=?namespace?+?key?????//?例如:first/second/mutations1
??registerMutation(store,?namespacedType,?mutation,?local)
})
這里遍歷了模塊的所有?mutations?方法,通過命名空間 +?mutations?方法名的形式生成了?namespacedType
然后跳到?registerMutations?方法看看具體是如何注冊的
//?注冊mutations方法
function?registerMutation?(store,?type,?handler,?local)?{
??const?entry?=?store._mutations[type]?||?(store._mutations[type]?=?[])??//?通過store._mutations?記錄所有注冊的mutations
??entry.push(function?wrappedMutationHandler?(payload)?{
????handler.call(store,?local.state,?payload)
??})
}
首先根據(jù)我們傳入的?type?也就是上面的?namespacedType?去store._mutations?尋找是否有入口?entry?,若有則直接獲取 ; 否則就創(chuàng)建一個(gè)空數(shù)組用于存儲?mutations?方法
在獲取到?entry?以后,將當(dāng)前的?mutations?方法添加到?entry?末尾進(jìn)行存儲。其中?mutations?接收的參數(shù)有兩個(gè),即 上下文中的?state?和 我們傳入的參數(shù)?payload
從這段代碼我們可以看出,整個(gè)?store?實(shí)例的所有?mutations?方法都是存儲在?store._mutations?中的,并且是以鍵值對的形式存放的,例如:
store._mutations?=?{
??'mutations1':?[function?handler()?{...}],
??'ModuleA/mutations2':?[function?handler()?{...},?function?handler()?{...}],
??'ModuleA/ModuleB/mutations2':?[function?handler()?{...}]
}
其中「鍵」是由命名空間和?mutations?方法名組成的,「值」是一個(gè)數(shù)組,存放著所有該鍵對應(yīng)的?mutations?方法
為什么是用數(shù)組存放呢?因?yàn)樵谏厦嬲f過,假設(shè)父模塊ModuleA?里有一個(gè)叫?func?的mutations?方法,那么其在?store._mutations?中就是這個(gè)樣子的
store._mutations?=?{
??'ModuleA/func':?[function?handler()?{...}]
}
若子模塊沒有設(shè)置命名空間,那么他是會繼承父模塊的命名空間的,此時(shí)子模塊里也有一個(gè)叫func?的?mutations?方法,那么在獲取?entry?時(shí),獲取到的是?store._mutations['ModuleA/func']?,但此時(shí)這個(gè)?entry?中已經(jīng)有一個(gè)?mutations?方法了,那么為了保證之前的方法不被替換,就選擇添加到數(shù)組的末尾,此時(shí)應(yīng)該就可以猜測到了,后續(xù)如果調(diào)用該?mutations?方法,會先獲取到相應(yīng)的數(shù)組,然后遍歷依次執(zhí)行
得出個(gè)「結(jié)論」:mutations?方法是可以重名的
3.2.4 注冊模塊的actions
//?注冊模塊的所有actions
module.forEachAction((action,?key)?=>?{
??const?type?=?action.root???key?:?namespace?+?key???//?判斷是否需要在命名空間里注冊一個(gè)全局的action
??const?handler?=?action.handler?||?action??????????//?獲取actions對應(yīng)的函數(shù)
??registerAction(store,?type,?handler,?local)???
})
遍歷模塊的所有?actions?方法,其中對于?type?和?handler?的處理主要是為了兼容兩種寫法:
//?第一種寫法:
actions:?{
??func(context,?payload)?{
????//?省略業(yè)務(wù)代碼...
??}
}
//?第二種寫法:
actions:?{
??func:?{
????root:?true,
????handler(context,?payload)?{
??????//?省略業(yè)務(wù)代碼...
????}
??}
}
當(dāng)采用第二種寫法,并且?root = true?時(shí),就會將該?actions?方法注冊到全局上,即前面不加上任何的命名空間前綴
再來看看?registerAction?方法里具體實(shí)現(xiàn)了什么
//?注冊actions方法,接收兩個(gè)參數(shù):context(包含了上下文中的dispatch方法、commit方法、getters方法、state)、傳入的參數(shù)payload
function?registerAction?(store,?type,?handler,?local)?{
??const?entry?=?store._actions[type]?||?(store._actions[type]?=?[])???//?通過store._actions?記錄所有注冊的actions
??entry.push(function?wrappedActionHandler?(payload)?{
????let?res?=?handler.call(store,?{
??????dispatch:?local.dispatch,
??????commit:?local.commit,
??????getters:?local.getters,
??????state:?local.state,
??????rootGetters:?store.getters,
??????rootState:?store.state
????},?payload)
????//?若返回值不是一個(gè)promise對象,則包裝一層promise,并將返回值作為then的參數(shù)
????if?(!isPromise(res))?{
??????res?=?Promise.resolve(res)
????}
????if?(store._devtoolHook)?{
??????return?res.catch(err?=>?{
????????store._devtoolHook.emit('vuex:error',?err)
????????throw?err
??????})
????}?else?{
??????return?res
????}
??})
}
與?mutations?類似,先從?store._actions?獲取入口?entry?,然后將當(dāng)前的actions?進(jìn)行包裝處理后添加到?entry?的末尾。actions?方法接收兩個(gè)參數(shù),即context?和我們傳入的參數(shù)?payload?,其中?context?是一個(gè)對象,里面包含了dispatch?、commit?、getters?、state?、rootGetters?、rootState?,前4個(gè)都是在當(dāng)前模塊的上下文中調(diào)用的,后2個(gè)是在全局上調(diào)用的
最后對于?actions?的返回值還做了一層處理,因?yàn)?actions?規(guī)定是處理異步任務(wù)的,所以我們肯定希望其值是一個(gè)?promise?對象,這樣方便后續(xù)的操作。所以這里對?actions?方法的返回值做了一個(gè)判斷,如果本身就是?promise?對象,那么就直接返回 ;若不是,則包裝一層?promise?對象,并將返回值?res?作為參數(shù)返回給?.then
同樣的,actions?方法也是可以重名的
3.2.5 注冊模塊的getters
//?注冊模塊的所有g(shù)etters
module.forEachGetter((getter,?key)?=>?{
??const?namespacedType?=?namespace?+?key
??registerGetter(store,?namespacedType,?getter,?local)
})
與上面的類似,這里就不多說了,直接跳到?registerGetters?方法
//?注冊getters
function?registerGetter?(store,?type,?rawGetter,?local)?{
??if?(store._wrappedGetters[type])?{???//?若記錄過getters了,則不再重復(fù)記錄
????if?(__DEV__)?{
??????console.error(`[vuex]?duplicate?getter?key:?${type}`)
????}
????return
??}
??//?在store._wrappedGetters中記錄getters
??store._wrappedGetters[type]?=?function?wrappedGetter?(store)?{
????return?rawGetter(
??????local.state,?//?local?state
??????local.getters,?//?local?getters
??????store.state,?//?root?state
??????store.getters?//?root?getters
????)
??}
}
這里發(fā)現(xiàn)?getters?并不像?mutations?和?actions?一樣去獲取一個(gè)?entry?,而是直接查看?store._wrappedGetters[type]?是否有對應(yīng)的?getters?,若有,則不再重復(fù)記錄 ; 否則將?getters?包裝一下存在?sotre._wrappedGetters?中,其中經(jīng)過包裝后的?getters?接收4個(gè)參數(shù),即?state?、getters?、rootState?、rootGetters?,前2個(gè)分別表示當(dāng)前上下文中的?state?和?getters?,后2個(gè)分別表示根模塊的?state?和?getters
所以我們在使用?Vuex?時(shí),調(diào)用子模塊的?getters?時(shí)是這樣的:
const?store?=?Vuex.Store({
??state:?{
????a:?1,
????b:?2
??},
??getters:?{
????addA(state)?{
??????return?state.a?+?1
????}
??},
??modules:?{
????//?子模塊A
????ModuleA:?{
??????state:?{
????????c:?3
??????},
??????getters:?{
???????sum(state,?getters,?rootState,?rootGetters)?{
??????????console.log(state.c)???//?3
??????????console.log(getters.addC)??//?4
??????????console.log(rootState.b)??//?2
??????????console.log(rootGetters.addA)??//?2
????????},
????????addC(state)?{
??????????return?state.c?+?1
????????}
??????}
????}
??}
})
最后我們再次得出一個(gè)結(jié)論,getters?是不能重名的,并且前一個(gè)命名的不會被后一個(gè)命名的所覆蓋
3.2.6 遞歸注冊子模塊
//?遞歸注冊子模塊
module.forEachChild((child,?key)?=>?{
??installModule(store,?rootState,?path.concat(key),?child,?hot)
})
然后就是判斷當(dāng)前的模塊里有沒有嵌套的子模塊了,有的話就將子模塊的名稱添加到?path末尾,然后把相應(yīng)的參數(shù)傳入?installModule?方法,重新走一遍本文中?3.2?里所有的流程
3.3 注冊vm
上面已經(jīng)將模塊的注冊完畢了,看一下?constructor?中下一行代碼是什么:
resetStoreVM(this,?state)
跳到相應(yīng)的方法中去看一下:
//?初始化vm
function?resetStoreVM?(store,?state,?hot)?{
??const?oldVm?=?store._vm
??store.getters?=?{}????//?在實(shí)例store上設(shè)置getters對象
??
??store._makeLocalGettersCache?=?Object.create(null)??//?清空本地緩存
??const?wrappedGetters?=?store._wrappedGetters
??const?computed?=?{}
??//?遍歷getters,將每一個(gè)getter注冊到store.getters,訪問對應(yīng)getter時(shí)會去vm上訪問對應(yīng)的computed
??forEachValue(wrappedGetters,?(fn,?key)?=>?{
????computed[key]?=?partial(fn,?store)
????Object.defineProperty(store.getters,?key,?{
??????get:?()?=>?store._vm[key],
??????enumerable:?true?//?for?local?getters
????})
??})
??const?silent?=?Vue.config.silent
??Vue.config.silent?=?true
??//?使用Vue實(shí)例來存儲Vuex的state狀態(tài)樹,并利用computed去緩存getters返回的值
??store._vm?=?new?Vue({
????data:?{
??????$$state:?state
????},
????computed
??})
??Vue.config.silent?=?silent
??//?啟用嚴(yán)格模式的監(jiān)聽警告
??if?(store.strict)?{
????enableStrictMode(store)
??}
??//?若存在舊的vm,?銷毀舊的vm
??if?(oldVm)?{
????if?(hot)?{
??????//?解除對舊的vm對state的引用
??????store._withCommit(()?=>?{
????????oldVm._data.$$state?=?null
??????})
????}
????Vue.nextTick(()?=>?oldVm.$destroy())
??}
}
這個(gè)方法里主要做的就是生成一個(gè)?Vue?的實(shí)例?_vm?,然后將?store._makeLocalGettersCache?里的?getters?以及?store.state?交給一個(gè)?_vm?托管,即將?store.state?賦值給?_vm.data.$$state?,將?store._makeLocalGettersCache?通過轉(zhuǎn)化后賦值給?_vm.computed?,這樣一來,state?就實(shí)現(xiàn)了響應(yīng)式,getters?實(shí)現(xiàn)了類似?computed?的功能
因?yàn)樯闪诵碌?_vm?,所以最后通過?oldVm.$destory()?將舊的?_vm?給銷毀掉了
值得注意的是,其將?sotre.getters?的操作放在了這個(gè)方法里,是因?yàn)槲覀兒罄m(xù)訪問某個(gè)?getters?時(shí),訪問的其實(shí)是?_vm.computed?中的內(nèi)容。因此,通過?Object.defineProperty?對?store.getters?進(jìn)行了處理
3.4 訪問 state 、mutations 、actions
到此為止,已經(jīng)實(shí)現(xiàn)了可以通過?store.getter.某個(gè)getters?來使用?getters?,那么如何訪問?state?、mutations?、actions?呢?
3.4.1 訪問 state
通過搜索,在?Store?類中定義了一個(gè)?get?函數(shù),用于處理?store.state?的操作:
get?state?()?{
??return?this._vm._data.$$state
}
可以很清楚地看到,當(dāng)我們訪問?store.state?時(shí),就是去訪問?store._vm.data.$$state?,與剛才介紹?_vm?時(shí)說的一樣
3.4.2 訪問 mutations
其實(shí)?mutations?的訪問在一開始就觸及到了,只不過當(dāng)時(shí)只是提了一嘴,因?yàn)楫?dāng)時(shí)直接來看可能不會太明白
const?store?=?this
const?{?dispatch,?commit?}?=?this
this.dispatch?=?function?boundDispatch?(type,?payload)?{
??return?dispatch.call(store,?type,?payload)
}
this.commit?=?function?boundCommit?(type,?payload,?options)?{
??return?commit.call(store,?type,?payload,?options)
}
在?Store?中,對?store.commit?和?store.dispatch?方法做了一層處理,將該方法的調(diào)用指向了?store?,先來看看?commit?方法的具體實(shí)現(xiàn)
commit?(_type,?_payload,?_options)?{
??//?check?object-style?commit
??const?{
????type,
????payload,
????options
??}?=?unifyObjectStyle(_type,?_payload,?_options)
??const?mutation?=?{?type,?payload?}
??const?entry?=?this._mutations[type]????//?查找_mutations上是否有對應(yīng)的方法
??//?查找不到則不執(zhí)行任何操作
??if?(!entry)?{
????if?(__DEV__)?{
??????console.error(`[vuex]?unknown?mutation?type:?${type}`)
????}
????return
??}
??//?若有相應(yīng)的方法,則執(zhí)行
??this._withCommit(()?=>?{
????entry.forEach(function?commitIterator?(handler)?{
??????handler(payload)
????})
??})
??this._subscribers
????.slice()?//?shallow?copy?to?prevent?iterator?invalidation?if?subscriber?synchronously?calls?unsubscribe
????.forEach(sub?=>?sub(mutation,?this.state))
??if?(
????__DEV__?&&
????options?&&?options.silent
??)?{
????console.warn(
??????`[vuex]?mutation?type:?${type}.?Silent?option?has?been?removed.?`?+
??????'Use?the?filter?functionality?in?the?vue-devtools'
????)
??}
}
首先通過?unifyObjectStyle?方法對傳入的參數(shù)進(jìn)行了處理,來看一下這個(gè)方法是干什么的
function?unifyObjectStyle?(type,?payload,?options)?{
??if?(isObject(type)?&&?type.type)?{
????options?=?payload
????payload?=?type
????type?=?type.type
??}
??if?(__DEV__)?{
????assert(typeof?type?===?'string',?`expects?string?as?the?type,?but?found?${typeof?type}.`)
??}
??return?{?type,?payload,?options?}
}
使用過?Vuex?的應(yīng)該都知道,commit?有兩種提交方式:
//?第一種提交方式
this.$store.commit('func',?1)
//?第二種提交方式
this.$store.commit({
??type:?'func',
??num:?1
})
其先對第一個(gè)參數(shù)進(jìn)行判斷是否為對象,是的話就當(dāng)作對象提交風(fēng)格處理,否則的話就直接返回
在處理完參數(shù)以后,根據(jù)?type?從?store._mutations?上獲取到?entry?,前面分析過了,mutations?方法是以數(shù)組形式存儲的,所以可能有多個(gè)方法。然后在?_withCommit?方法中遍歷?entry?依次執(zhí)行?mutations?方法,這是因?yàn)?Vuex?規(guī)定?state?的改變都要通過?mutations?方法,store._committing?這個(gè)屬性就是用來判斷當(dāng)前是否處于調(diào)用?mutations?方法的,當(dāng)?state?值改變時(shí),會先去判斷?store._committing?是否為?true?,若不為?true?,則表示?state?的值改變沒有經(jīng)過?mutations?方法,于是會打印警告?? 信息
而?this._subscribers?這段代碼我暫時(shí)還不清楚是干什么的,通過詞義,目測應(yīng)該是一個(gè)存放訂閱的東西吧,就先放著不管了,等后續(xù)回來再看
3.4.3 訪問 actions
dispatch?(_type,?_payload)?{
??//?check?object-style?dispatch
??const?{
????type,
????payload
??}?=?unifyObjectStyle(_type,?_payload)
??const?action?=?{?type,?payload?}
??const?entry?=?this._actions[type]??//?查找_actions上是否有對應(yīng)的方法
??//?查找不到則不執(zhí)行任何操作
??if?(!entry)?{
????if?(__DEV__)?{
??????console.error(`[vuex]?unknown?action?type:?${type}`)
????}
????return
??}
??try?{
????this._actionSubscribers
??????.slice()?//?shallow?copy?to?prevent?iterator?invalidation?if?subscriber?synchronously?calls?unsubscribe
??????.filter(sub?=>?sub.before)
??????.forEach(sub?=>?sub.before(action,?this.state))
??}?catch?(e)?{
????if?(__DEV__)?{
??????console.warn(`[vuex]?error?in?before?action?subscribers:?`)
??????console.error(e)
????}
??}
??const?result?=?entry.length?>?1
????Promise.all(entry.map(handler?=>?handler(payload)))
??:?entry[0](payload)
??return?new?Promise((resolve,?reject)?=>?{
????result.then(res?=>?{
??????try?{
????????this._actionSubscribers
??????????.filter(sub?=>?sub.after)
??????????.forEach(sub?=>?sub.after(action,?this.state))
??????}?catch?(e)?{
????????if?(__DEV__)?{
??????????console.warn(`[vuex]?error?in?after?action?subscribers:?`)
??????????console.error(e)
????????}
??????}
??????resolve(res)
????},?error?=>?{
??????try?{
????????this._actionSubscribers
??????????.filter(sub?=>?sub.error)
??????????.forEach(sub?=>?sub.error(action,?this.state,?error))
??????}?catch?(e)?{
????????if?(__DEV__)?{
??????????console.warn(`[vuex]?error?in?error?action?subscribers:?`)
??????????console.error(e)
????????}
??????}
??????reject(error)
????})
??})
}
前半部分與?commit?方法類似,就不多說了
代碼中又出現(xiàn)了?this._actionSubscribers?,與?commit?中的也類似,可能這里是存放?actions?的訂閱者的東西,所以這些都先不看了
其中變量?result?,先判斷?entry?的長度,若大于1,則表示有多個(gè)異步方法,所以用Promise.all?進(jìn)行包裹 ; 否則直接執(zhí)行?entry[0]
最后創(chuàng)建并返回了一個(gè)新的?promise?,內(nèi)部判斷了?result?的狀態(tài),成功則執(zhí)行?resolve?,失敗則執(zhí)行?reject
到此為止,我們已經(jīng)實(shí)現(xiàn)了?store.state?、store.getters?、store.commit?、store.dispatch?的調(diào)用了
3.5 插件的調(diào)用
繼續(xù)看?constructor?中的代碼(這段代碼也是整個(gè)?Store?類的構(gòu)造函數(shù)中最后的一小段代碼了)
//?依次調(diào)用傳入的插件
plugins.forEach(plugin?=>?plugin(this))
const?useDevtools?=?options.devtools?!==?undefined???options.devtools?:?Vue.config.devtools
//?使用vue的開發(fā)插件
if?(useDevtools)?{
??devtoolPlugin(this)
}
首先就是遍歷創(chuàng)建?Store?類時(shí)傳入的參數(shù)?Plugins?,依次調(diào)用傳入的插件函數(shù)(當(dāng)然一般我們都沒有傳入,所以?Plugins?默認(rèn)是空數(shù)組)
然后就是調(diào)用?devtoolPlugin?方法啦,根據(jù)導(dǎo)入的路徑我們?nèi)サ较鄳?yīng)的文件
//?文件路徑:./plugins/devtool.js
const?target?=?typeof?window?!==?'undefined'
????window
??:?typeof?global?!==?'undefined'
??????global
????:?{}
const?devtoolHook?=?target.__VUE_DEVTOOLS_GLOBAL_HOOK__
export?default?function?devtoolPlugin?(store)?{
??if?(!devtoolHook)?return
??store._devtoolHook?=?devtoolHook
??devtoolHook.emit('vuex:init',?store)
??devtoolHook.on('vuex:travel-to-state',?targetState?=>?{
????store.replaceState(targetState)
??})
??store.subscribe((mutation,?state)?=>?{
????devtoolHook.emit('vuex:mutation',?mutation,?state)
??},?{?prepend:?true?})
??store.subscribeAction((action,?state)?=>?{
????devtoolHook.emit('vuex:action',?action,?state)
??},?{?prepend:?true?})
}
看了半天,搜索了半天,都沒有找到哪個(gè)文件里有?__VUE_DEVTOOLS_GLOBAL_HOOK__,應(yīng)該是?dev-tools?插件里定義的,為了保證?Vuex?的源碼閱讀進(jìn)度,就先舍棄閱讀dev-tools?插件的內(nèi)容了
3.6 其它方法
整個(gè)?Store?實(shí)例生成的全過程差不多就是這樣了,另外還會發(fā)現(xiàn),其實(shí)有很多方法都沒有被用到,但是卻被定義出來了,這里可以稍微列舉幾個(gè)簡單地看一下
3.6.1 更新 state
//?在store._committing?=?true?的狀態(tài)下更新一下state
replaceState?(state)?{
??this._withCommit(()?=>?{
????this._vm._data.$$state?=?state
??})
}
一目了然,這是提供了一種直接修改?state?的方法,并且不會打印警告信息
3.6.2 注冊、卸載模塊
//?注冊模塊
registerModule?(path,?rawModule,?options?=?{})?{
??if?(typeof?path?===?'string')?path?=?[path]
??if?(__DEV__)?{
????assert(Array.isArray(path),?`module?path?must?be?a?string?or?an?Array.`)
????assert(path.length?>?0,?'cannot?register?the?root?module?by?using?registerModule.')
??}
??this._modules.register(path,?rawModule)
??installModule(this,?this.state,?path,?this._modules.get(path),?options.preserveState)
??//?reset?store?to?update?getters...
??resetStoreVM(this,?this.state)
}
//?卸載模塊
unregisterModule?(path)?{
??if?(typeof?path?===?'string')?path?=?[path]
??if?(__DEV__)?{
????assert(Array.isArray(path),?`module?path?must?be?a?string?or?an?Array.`)
??}
??this._modules.unregister(path)
??this._withCommit(()?=>?{
????const?parentState?=?getNestedState(this.state,?path.slice(0,?-1))
????Vue.delete(parentState,?path[path.length?-?1])
??})
??resetStore(this)
}
3.6.3 重置 store 實(shí)例
//?重置store,即注冊模塊、生成vm等操作
function?resetStore?(store,?hot)?{
??store._actions?=?Object.create(null)
??store._mutations?=?Object.create(null)
??store._wrappedGetters?=?Object.create(null)
??store._modulesNamespaceMap?=?Object.create(null)
??const?state?=?store.state
??//?init?all?modules
??installModule(store,?state,?[],?store._modules.root,?true)
??//?reset?vm
??resetStoreVM(store,?state,?hot)
}
將所有的狀態(tài)都清空,然后重新執(zhí)行一邊?installModule?和?resetStoreVM?,這一般在模塊結(jié)構(gòu)變化以后調(diào)用,例如某個(gè)模塊被卸載
4. install 注冊
Store?類的所有實(shí)現(xiàn)都了解完了,再來看一下入口文件里還有什么,突然發(fā)現(xiàn)忘記看一下非常重要的?install?方法了,根據(jù)?install?方法的導(dǎo)入路徑找到相應(yīng)的函數(shù):
//?提供install方法
export?function?install?(_Vue)?{
??if?(Vue?&&?_Vue?===?Vue)?{
????if?(__DEV__)?{
??????console.error(
????????'[vuex]?already?installed.?Vue.use(Vuex)?should?be?called?only?once.'
??????)
????}
????return
??}
??Vue?=?_Vue
??applyMixin(Vue)
}
當(dāng)我們調(diào)用?Vue.use(vuex)?時(shí),調(diào)用這個(gè)方法,先判斷?vuex?是否已被注冊,若已被注冊,則不執(zhí)行任何操作 ; 若沒有被注冊,則調(diào)用?applyMixin?方法,現(xiàn)在移步到?./mixin.js?文件:
export?default?function?(Vue)?{
??const?version?=?Number(Vue.version.split('.')[0])
??//?2.x版本直接通過全局混入Vue.mixin的方式掛載store
??if?(version?>=?2)?{
????Vue.mixin({?beforeCreate:?vuexInit?})
??}?else?{
????//?兼容1.x版本
????const?_init?=?Vue.prototype._init
????Vue.prototype._init?=?function?(options?=?{})?{
??????options.init?=?options.init
??????????[vuexInit].concat(options.init)
????????:?vuexInit
??????_init.call(this,?options)
????}
??}
??//?將vuex混入到$options中
??function?vuexInit?()?{
????//?獲取當(dāng)前組件的?$options
????const?options?=?this.$options
????//?若當(dāng)前組件的$options上已存在store,則將$options.store賦值給this.$store(一般是用于根組件的)
????if?(options.store)?{
??????this.$store?=?typeof?options.store?===?'function'
??????????options.store()
????????:?options.store
????}?
????//?當(dāng)前組件的$options上沒有store,則獲取父組件上的$store,即$options.parent.$store,并將其賦值給this.$store(一般用于子組件)
????else?if?(options.parent?&&?options.parent.$store)?{
??????this.$store?=?options.parent.$store
????}
??}
}
applyMixin?方法先判斷了?Vue?的版本號,主要做的是一個(gè)向下兼容?Vue 1.x?的版本,這里我對?Vue 1.x?的版本不太熟悉,所以就直接看?Vue 2.x?版本的處理方式吧
通過?Vue.minxin?方法做了一個(gè)全局的混入,在每個(gè)組件?beforeCreate?生命周期時(shí)會調(diào)用?vuexInit?方法,該方法處理得非常巧妙,首先獲取當(dāng)前組件的?$options,判斷當(dāng)前組件的?$options?上是否有?sotre?,若有則將?store?賦值給當(dāng)前組件,即?this.$store?,這個(gè)一般是判斷根組件的,因?yàn)橹挥性诔跏蓟?Vue?實(shí)例的時(shí)候我們才手動(dòng)傳入了?store?; 若?$options?上沒有?store?,則代表當(dāng)前不是根組件,所以我們就去父組件上獲取,并賦值給當(dāng)前組件,即當(dāng)前組件也可以通過?this.$store?訪問到?store?實(shí)例了
這里不得不感嘆,這個(gè)處理方式太棒了。
5. 輔助函數(shù)
store實(shí)例生成并且也?install?到?Vue?上了,看一下入口文件中只剩下輔助函數(shù)了,它們有?mapState?、mapGetters?、mapMutations?、mapActions?、createNamespacedHelpers?,進(jìn)到相應(yīng)的文件?./helpers.js?中看一下
import?{?isObject?}?from?'./util.js'
export?const?mapState?=?normalizeNamespace((namespace,?states)?=>?{
??const?res?=?{}
??if?(__DEV__?&&?!isValidMap(states))?{
????console.error('[vuex]?mapState:?mapper?parameter?must?be?either?an?Array?or?an?Object')
??}
??normalizeMap(states).forEach(({?key,?val?})?=>?{
????res[key]?=?function?mappedState?()?{
??????let?state?=?this.$store.state
??????let?getters?=?this.$store.getters
??????if?(namespace)?{
????????const?module?=?getModuleByNamespace(this.$store,?'mapState',?namespace)
????????if?(!module)?{
??????????return
????????}
????????state?=?module.context.state
????????getters?=?module.context.getters
??????}
??????return?typeof?val?===?'function'
??????????val.call(this,?state,?getters)
????????:?state[val]
????}
????//?mark?vuex?getter?for?devtools
????res[key].vuex?=?true
??})
??return?res
})
export?const?mapMutations?=?normalizeNamespace((namespace,?mutations)?=>?{
??const?res?=?{}
??if?(__DEV__?&&?!isValidMap(mutations))?{
????console.error('[vuex]?mapMutations:?mapper?parameter?must?be?either?an?Array?or?an?Object')
??}
??normalizeMap(mutations).forEach(({?key,?val?})?=>?{
????res[key]?=?function?mappedMutation?(...args)?{
??????//?Get?the?commit?method?from?store
??????let?commit?=?this.$store.commit
??????if?(namespace)?{
????????const?module?=?getModuleByNamespace(this.$store,?'mapMutations',?namespace)
????????if?(!module)?{
??????????return
????????}
????????commit?=?module.context.commit
??????}
??????return?typeof?val?===?'function'
??????????val.apply(this,?[commit].concat(args))
????????:?commit.apply(this.$store,?[val].concat(args))
????}
??})
??return?res
})
export?const?mapGetters?=?normalizeNamespace((namespace,?getters)?=>?{
??const?res?=?{}
??if?(__DEV__?&&?!isValidMap(getters))?{
????console.error('[vuex]?mapGetters:?mapper?parameter?must?be?either?an?Array?or?an?Object')
??}
??normalizeMap(getters).forEach(({?key,?val?})?=>?{
????//?The?namespace?has?been?mutated?by?normalizeNamespace
????val?=?namespace?+?val
????res[key]?=?function?mappedGetter?()?{
??????if?(namespace?&&?!getModuleByNamespace(this.$store,?'mapGetters',?namespace))?{
????????return
??????}
??????if?(__DEV__?&&?!(val?in?this.$store.getters))?{
????????console.error(`[vuex]?unknown?getter:?${val}`)
????????return
??????}
??????return?this.$store.getters[val]
????}
????//?mark?vuex?getter?for?devtools
????res[key].vuex?=?true
??})
??return?res
})
export?const?mapActions?=?normalizeNamespace((namespace,?actions)?=>?{
??const?res?=?{}
??if?(__DEV__?&&?!isValidMap(actions))?{
????console.error('[vuex]?mapActions:?mapper?parameter?must?be?either?an?Array?or?an?Object')
??}
??normalizeMap(actions).forEach(({?key,?val?})?=>?{
????res[key]?=?function?mappedAction?(...args)?{
??????//?get?dispatch?function?from?store
??????let?dispatch?=?this.$store.dispatch
??????if?(namespace)?{
????????const?module?=?getModuleByNamespace(this.$store,?'mapActions',?namespace)
????????if?(!module)?{
??????????return
????????}
????????dispatch?=?module.context.dispatch
??????}
??????return?typeof?val?===?'function'
??????????val.apply(this,?[dispatch].concat(args))
????????:?dispatch.apply(this.$store,?[val].concat(args))
????}
??})
??return?res
})
/**
?*?Rebinding?namespace?param?for?mapXXX?function?in?special?scoped,?and?return?them?by?simple?object
?*?@param?{String}?namespace
?*?@return?{Object}
?*/
export?const?createNamespacedHelpers?=?(namespace)?=>?({
??mapState:?mapState.bind(null,?namespace),
??mapGetters:?mapGetters.bind(null,?namespace),
??mapMutations:?mapMutations.bind(null,?namespace),
??mapActions:?mapActions.bind(null,?namespace)
})
function?normalizeMap?(map)?{
??if?(!isValidMap(map))?{
????return?[]
??}
??return?Array.isArray(map)
??????map.map(key?=>?({?key,?val:?key?}))
????:?Object.keys(map).map(key?=>?({?key,?val:?map[key]?}))
}
function?isValidMap?(map)?{
??return?Array.isArray(map)?||?isObject(map)
}
function?normalizeNamespace?(fn)?{
??return?(namespace,?map)?=>?{
????if?(typeof?namespace?!==?'string')?{
??????map?=?namespace
??????namespace?=?''
????}?
????else?if?(namespace.charAt(namespace.length?-?1)?!==?'/')?{
??????namespace?+=?'/'
????}
????return?fn(namespace,?map)
??}
}
function?getModuleByNamespace?(store,?helper,?namespace)?{
??const?module?=?store._modulesNamespaceMap[namespace]
??if?(__DEV__?&&?!module)?{
????console.error(`[vuex]?module?namespace?not?found?in?${helper}():?${namespace}`)
??}
??return?module
}
整個(gè)文件里東西非常多,但我們很明確地知道,我們主要看的就是那幾個(gè)輔助函數(shù),觀察發(fā)現(xiàn),每個(gè)輔助函數(shù)都會先調(diào)用?normalizeNamespace?函數(shù)進(jìn)行處理,那么我們就先看看這個(gè)函數(shù)做了什么:
function?normalizeNamespace?(fn)?{
??return?(namespace,?map)?=>?{
????if?(typeof?namespace?!==?'string')?{
??????map?=?namespace
??????namespace?=?''
????}?
????else?if?(namespace.charAt(namespace.length?-?1)?!==?'/')?{
??????namespace?+=?'/'
????}
????return?fn(namespace,?map)
??}
}
根據(jù)函數(shù)名的字面意思知道這應(yīng)該是根據(jù)不同的調(diào)用方法,標(biāo)準(zhǔn)化命名空間的。
首先返回一個(gè)函數(shù),接收兩個(gè)參數(shù),即?namespace?和?map?,這也是我們調(diào)用輔助函數(shù)時(shí)可以傳入的兩個(gè)參數(shù) ;
然后判斷?namespace?是否為字符串形式,若不是字符串,則表示是普通的調(diào)用方式,例如:
mapMutations(['first/second/foo',?'first/second/bar'])
mapMutations({
???foo:?'first/second/foo',
???bar:?'first/second/bar',
})
這種情況,就直接將第一個(gè)參數(shù)?namespace?賦值給映射變量?map?,而?namespace設(shè)為空
若是字符串的話,則表示調(diào)用的是帶命名空間的綁定函數(shù)的,例如:
mapState('first/second',?['foo',?'bar'])
mapState('first/second',?{
??foo:?'foo',
??bar:?'bar',
})
處理好這兩種不同的調(diào)用方式以后,調(diào)用一下?fn?,并將?namespace?和?map?作為參數(shù)
那么就先從?mapState?開始看吧
5.1 mapState
export?const?mapState?=?normalizeNamespace((namespace,?states)?=>?{
??const?res?=?{}
??if?(__DEV__?&&?!isValidMap(states))?{
????console.error('[vuex]?mapState:?mapper?parameter?must?be?either?an?Array?or?an?Object')
??}
??normalizeMap(states).forEach(({?key,?val?})?=>?{
????res[key]?=?function?mappedState?()?{
??????let?state?=?this.$store.state
??????let?getters?=?this.$store.getters
??????if?(namespace)?{
????????const?module?=?getModuleByNamespace(this.$store,?'mapState',?namespace)
????????if?(!module)?{
??????????return
????????}
????????state?=?module.context.state
????????getters?=?module.context.getters
??????}
??????return?typeof?val?===?'function'
??????????val.call(this,?state,?getters)
????????:?state[val]
????}
????//?mark?vuex?getter?for?devtools
????res[key].vuex?=?true
??})
??return?res
})
這里的?namespace?是一個(gè)字符串,states?是我們剛才處理好的映射變量?map
首先創(chuàng)建一個(gè)空對象?res?,這是我們最后處理好要返回的變量 ;
然后通過?isValidMap?方法判斷?map?是否符合要求,即是否是數(shù)組或?qū)ο?;
再然后調(diào)用了?normalizeMap?方法處理了變量?states?,從字面意義上來看,這是用來標(biāo)準(zhǔn)化該變量的,因?yàn)楫吘褂锌赡苁菙?shù)組又有可能是對象嘛,所以要統(tǒng)一一下。來看一下normalizeMap?方法的實(shí)現(xiàn):
function?normalizeMap?(map)?{
??if?(!isValidMap(map))?{
????return?[]
??}
??return?Array.isArray(map)
??????map.map(key?=>?({?key,?val:?key?}))
????:?Object.keys(map).map(key?=>?({?key,?val:?map[key]?}))
}
首先仍然要先判斷?map?是否合法,若不合法,則返回空數(shù)組,避免后續(xù)的代碼報(bào)錯(cuò) ;
然后判斷?map?是否為數(shù)組,若是數(shù)組,則遍歷?map?進(jìn)行處理:
將?[1,?2,?3]?變成?[{key:?1,?val:?1},?{key:?2,?val:?2},?{key:?3,?val:?3}]
若?map?不是數(shù)組,則一定為對象,那么同樣也要把其處理成跟上面一樣的格式:
將?{a:?1,?b:?2,?c:?3}?變成?[{key:?a,?val:?1},?{key:?b,?val:?2},?{key:?c,?val:?3}]
處理好了以后就直接返回,在得到標(biāo)準(zhǔn)化以后的?map?后要對其進(jìn)行?forEach?遍歷,將遍歷到的每一個(gè)對象經(jīng)過處理后存放在?res?中,即?res[key] = function mappedState() {...}?,來看一下這個(gè)?mappedState?里做了什么處理
首先獲取一下根模塊上的?state?和?getters
//?獲取根模塊的?state?、getters
let?state?=?this.$store.state
let?getters?=?this.$store.getters
然后判斷是否存在命名空間,即?namespace?是否為空,若為空,則不做任何處理 ; 否則調(diào)用?getModuleByNamespace?方法獲取到?namespace?對應(yīng)的模塊?module
function?getModuleByNamespace?(store,?helper,?namespace)?{
??const?module?=?store._modulesNamespaceMap[namespace]
??if?(__DEV__?&&?!module)?{
????console.error(`[vuex]?module?namespace?not?found?in?${helper}():?${namespace}`)
??}
??return?module
}
可以看到?store._modulesNamespaceMap?終于派上了用場,在生成?Store?實(shí)例注冊所有模塊的時(shí)候,將帶有命名空間的模塊都存儲在了該變量上,原來是在這里用上了
然后將剛才聲明的變量?state?和?getters?替換成?module?對應(yīng)上下文中的?state?和?getters
if?(namespace)?{
??//?獲取命名空間namespace對應(yīng)的模塊
??const?module?=?getModuleByNamespace(this.$store,?'mapState',?namespace)
??if?(!module)?{
????return
??}
??//?將?state?、getters?變成該模塊上下文中的?state?、getters
??state?=?module.context.state
??getters?=?module.context.getters
}
這個(gè)?context?也是非常的巧妙,在注冊模塊的時(shí)候,獲取到該模塊的上下文的同時(shí),還將其存儲了一下,即:
const?local?=?module.context?=?makeLocalContext(store,?namespace,?path)
之前看到的時(shí)候不知道有啥用,但在這里看到后,覺得真的非常得贊 ??
確定好了?state?和?getters?的值,最后就可以返回值了
return?typeof?val?===?'function'
????val.call(this,?state,?getters)
?:?state[val]
這里還做了一層處理是因?yàn)橐幚韮煞N不同的方式,例如:
mapState({
??foo:?state?=>?state.foo,
??bar:?'bar'
})
在這里我又發(fā)現(xiàn)了一個(gè)官方文檔里沒有提及的,就是以函數(shù)形式返回的時(shí)候,還能接收第二個(gè)參數(shù)?getters?,即:foo: (state, getters) => state.foo + getters.bar
5.2 mapMutations
export?const?mapMutations?=?normalizeNamespace((namespace,?mutations)?=>?{
??const?res?=?{}
??if?(__DEV__?&&?!isValidMap(mutations))?{
????console.error('[vuex]?mapMutations:?mapper?parameter?must?be?either?an?Array?or?an?Object')
??}
??normalizeMap(mutations).forEach(({?key,?val?})?=>?{
????res[key]?=?function?mappedMutation?(...args)?{
??????//?Get?the?commit?method?from?store
??????let?commit?=?this.$store.commit
??????if?(namespace)?{
????????const?module?=?getModuleByNamespace(this.$store,?'mapMutations',?namespace)
????????if?(!module)?{
??????????return
????????}
????????commit?=?module.context.commit
??????}
??????return?typeof?val?===?'function'
??????????val.apply(this,?[commit].concat(args))
????????:?commit.apply(this.$store,?[val].concat(args))
????}
??})
??return?res
})
mapMutations?與?mapState?的實(shí)現(xiàn)大體相似,主要的不同就在下面這段代碼:
return?typeof?val?===?'function'
????val.apply(this,?[commit].concat(args))
?:?commit.apply(this.$store,?[val].concat(args))
這里也是像?mapState?一樣處理了函數(shù)的調(diào)用類型和普通的調(diào)用類型,例如:
mapMutations({
??foo:?(commit,?num)?=>?{
????commit('foo',?num)
??},
??bar:?'bar'
})
當(dāng)是函數(shù)的調(diào)用類型時(shí),則將?commit?作為第一個(gè)參數(shù),并把額外的參數(shù)一并傳入,所以才有的?val.apply(this, [commit].concat(args))?這段代碼 ;
當(dāng)是普通的調(diào)用類型時(shí),則直接執(zhí)行?commit?,其中?val?對應(yīng)的就是該命名空間下需要調(diào)用的?mutations?方法名,然后再接收額外的參數(shù),即?commit.apply(this.$store, [val].concat(args))
5.3 mapGetters
export?const?mapGetters?=?normalizeNamespace((namespace,?getters)?=>?{
??const?res?=?{}
??if?(__DEV__?&&?!isValidMap(getters))?{
????console.error('[vuex]?mapGetters:?mapper?parameter?must?be?either?an?Array?or?an?Object')
??}
??normalizeMap(getters).forEach(({?key,?val?})?=>?{
????//?The?namespace?has?been?mutated?by?normalizeNamespace
????val?=?namespace?+?val
????res[key]?=?function?mappedGetter?()?{
??????if?(namespace?&&?!getModuleByNamespace(this.$store,?'mapGetters',?namespace))?{
????????return
??????}
??????if?(__DEV__?&&?!(val?in?this.$store.getters))?{
????????console.error(`[vuex]?unknown?getter:?${val}`)
????????return
??????}
??????return?this.$store.getters[val]
????}
????//?mark?vuex?getter?for?devtools
????res[key].vuex?=?true
??})
??return?res
})
這個(gè)也沒什么好說的了,拿到命名空間?namespace?,直接拼接上?val?通過?this.$store.getters[val]?進(jìn)行訪問。簡單舉個(gè)例子:
第一種情況
//?第一種
mapGetters(['first/foo'])
這種情況下?namespace?被處理成了空字符串,map?被處理成了?['first/foo'],遍歷?map?,此時(shí)?val = 'first/foo'?,那么?val = namespace + val處理后?val?仍然等于?first/foo?,所以最后就相當(dāng)于調(diào)用?this.$store.getters['first/foo']
再來看第二種情況
//?第二種
mapGetters('first',?['foo'])
這種情況下?namespace?被處理成了?first/,map?被處理成了?['foo']?,遍歷map?,此時(shí)?val = 'foo'?,那么?val = namespace + val?處理后?val?就等于?first/foo?,所以最后仍然是相當(dāng)于調(diào)用?this.$store.getters['first/foo']
5.4 mapActions
export?const?mapActions?=?normalizeNamespace((namespace,?actions)?=>?{
??const?res?=?{}
??if?(__DEV__?&&?!isValidMap(actions))?{
????console.error('[vuex]?mapActions:?mapper?parameter?must?be?either?an?Array?or?an?Object')
??}
??normalizeMap(actions).forEach(({?key,?val?})?=>?{
????res[key]?=?function?mappedAction?(...args)?{
??????//?get?dispatch?function?from?store
??????let?dispatch?=?this.$store.dispatch
??????if?(namespace)?{
????????const?module?=?getModuleByNamespace(this.$store,?'mapActions',?namespace)
????????if?(!module)?{
??????????return
????????}
????????dispatch?=?module.context.dispatch
??????}
??????return?typeof?val?===?'function'
??????????val.apply(this,?[dispatch].concat(args))
????????:?dispatch.apply(this.$store,?[val].concat(args))
????}
??})
??return?res
})
簡單看了一下,處理流程跟?mapMutations?幾乎一模一樣,就不多說了
5.5 createNamespacedHelpers
export?const?createNamespacedHelpers?=?(namespace)?=>?({
??mapState:?mapState.bind(null,?namespace),
??mapGetters:?mapGetters.bind(null,?namespace),
??mapMutations:?mapMutations.bind(null,?namespace),
??mapActions:?mapActions.bind(null,?namespace)
})
該方法是根據(jù)傳入的命名空間?namespace?創(chuàng)建一組輔助函數(shù)。巧妙之處就是先通過?bind?函數(shù)把第一個(gè)參數(shù)先傳入
import?{?createNamespacedHelpers?}?from?'vuex'
const?{?mapState,?mapActions?}?=?createNamespacedHelpers('first/second')
export?default?{
??computed:?{
????...mapState({
??????a:?'a',??//?相當(dāng)于?first/second/a
??????b:?'b',??//?相當(dāng)于?first/second/b
????})
??},
??methods:?{
????...mapActions([
??????'foo',??????//?相當(dāng)于?first/second/foo
??????'bar',??????//?相當(dāng)于?first/second/bar
????])
??}
}
?? 心得體會首先,我一直有一個(gè)閱讀源碼的想法,但卻因?yàn)槟芰τ邢捱t遲沒有行動(dòng),之后在一次與大佬的交流中,我發(fā)現(xiàn)了自己的不足,沒有深入學(xué)習(xí),即只停留在「會用」的階段,卻沒有做到知其然知其所以然。說實(shí)話,這樣真的很難受,每次用某個(gè)庫時(shí),出現(xiàn)了某個(gè)問題只會先看考慮是否自己調(diào)用的方式有問題,然后上搜索引擎找答案,長期這樣自己也很難有進(jìn)步。
所以,因?yàn)橐韵氯c(diǎn)原因,我準(zhǔn)備靠自己好好看一下?Vuex?源碼:
Vuex?的核心源碼比較少,對于像我一樣第一次閱讀源碼的人比較友好- 深入學(xué)習(xí)了常用的庫以后,在使用的時(shí)候遇到問題,可以快速地找到問題根源
- 不能只停留在成熟的庫的表面,要學(xué)習(xí)它們的思想、技術(shù),這樣有助于自己的成長
剛開始不知道自己能花多久時(shí)間看完?Vuex?的核心源碼,我初步給自己定了?15?天的期限,預(yù)計(jì)每天至少看?2?小時(shí)。于是我把?Vuex?的源碼?fork?并?clone?了下來,第一天簡單地找了一下核心代碼的位置,然后非常粗略地看了一下源碼里的大致流程。同時(shí),我去?Vuex?官方文檔里重新仔仔細(xì)細(xì)地回顧了一下所有的核心使用方法
接下來的時(shí)間我就按照我本文的閱讀順序進(jìn)行源碼的閱讀
這里總結(jié)幾點(diǎn)閱讀源碼的「心得體會」吧:
- 對于這個(gè)庫的使用一定要十分熟練,即明白各種方法的使用,強(qiáng)烈建議把官方文檔吃透(「重點(diǎn)」)
- 找到核心代碼的位置,從入口文件開始,一步步看
- 多看源碼中的英文注釋,看不懂的可以用翻譯,這些注釋基本上能幫你理解這段代碼的作用
- 遇到看不懂的地方可以先打個(gè)備注,因?yàn)樗赡芘c后面的某些代碼有所聯(lián)系,等之后回頭來看之前看不懂的代碼時(shí),就會明白了
- 閱讀源碼的過程中,看到某些變量或函數(shù)時(shí),先看命名,因?yàn)檫@些命名的字面意思基本上就代表了它的作用,然后要學(xué)會聯(lián)想到這個(gè)正常的調(diào)用是什么樣的,這樣更便于理解
- 多多利用編譯器的搜索功能。因?yàn)橛袝r(shí)你看到的函數(shù)或變量可能在別的地方也有用到,為了方便尋找,可以利用好編譯器的搜索功能(包括當(dāng)前「本地搜索」和「全局搜索」)
本地搜索
全局搜索?? 問答環(huán)節(jié)這里放上幾個(gè)群友對于這次閱讀源碼問我的問題:
「Q1:」?你是怎么看源碼的?有看別人的視頻或者別人的文章嗎?
「A1:」?沒有看別人的視頻或者文章,就當(dāng)時(shí)自己思考了一下該如何看源碼,列了一個(gè)步驟,就這樣摸索著看完了,覺得還挺有意思的
「Q2:」?光自己看能看懂嗎?
「A2:」?說實(shí)話確實(shí)有些地方挺難看懂的,但結(jié)合著源碼自帶的英文注釋,基本上能把大致的思路理清,然后看不懂的地方就先做上記號并暫時(shí)略過,等到看了更多的代碼了以后,回過頭來就發(fā)現(xiàn)似乎看懂了些。最后要說的就是,源碼真不是一遍就能看懂的,真的是要反反復(fù)復(fù)多看幾遍,才能理解其中的原理
「Q3:」?看完源碼后,你能自己手寫出來嗎?
「A3:」?emmmm...這可能有點(diǎn)難度,但是我覺得手寫一些核心代碼,實(shí)現(xiàn)一個(gè)簡陋的?Vuex?還是可以做到的吧,而且我覺得很有必要自己再去手寫一下核心代碼,因?yàn)檫@又是一次對源碼的鞏固,并且我也已經(jīng)開始在寫一個(gè)簡陋版的?Vuex?了,放在倉庫的?myVuex?文件夾下
若本文對于?Vuex?源碼閱讀有任何錯(cuò)誤的地方,歡迎大家給我提意見,一定虛心聽取你們的指正,
Vuex?源碼閱讀倉庫可以點(diǎn)擊文末的?「閱讀原文」?查看,若覺得不錯(cuò)的,也可以點(diǎn)個(gè)??「star」??? 支持一下我。
最后,也可以關(guān)注我的公眾號:「前端印象」,或是添加我的微信(Lpyexplore333)私底下進(jìn)行交流
這篇文章我真的很用心了,你們?nèi)绦牟唤o點(diǎn)個(gè)贊 ?? 和 在看 嘛~
「歡迎各位大佬關(guān)注我,掃二維碼即可」
熱文導(dǎo)讀
