chapter1 store構造函數vue
1.constructornode
2.get state和set statevuex
3.commit數組
4.dispatchpromise
5.subscribe和subscribeAction微信
6.watch和replaceStateapp
7.registerModule和unregisterModule異步
8.hotUpdate和_withCommitide
chapter2 export install函數
Q:Vuex如何實現裝載的?
chapter3 輔助函數
1.registerMutation、registerAction、registerGetter
2.enableStrictMode、getNestedState
3.unifyObjectStyle(type, payload, options)
1.constructor
源碼分析
constructor (options = {}) { //安裝Vue對象 if (!Vue && typeof window !== 'undefined' && window.Vue) { console.log("window.vue"); install(window.Vue) } //開發環境對Vue、Promise和Store的判斷 if (process.env.NODE_ENV !== 'production') { assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`) assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`) assert(this instanceof Store, `store must be called with the new operator.`) } //options包括插件選項、嚴格模式選項 const { plugins = [], strict = false } = options // 存儲內部的狀態 this._committing = false this._actions = Object.create(null) this._actionSubscribers = [] this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._watcherVM = new Vue() // 綁定commit和dispatch 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) } // 嚴格模式 this.strict = strict const state = this._modules.root.state // 初始化根模塊,或者安裝子模塊 installModule(this, state, [], this._modules.root) //初始化vm resetStoreVM(this, state) // 應用插件 plugins.forEach(plugin => plugin(this)) if (Vue.config.devtools) { devtoolPlugin(this) } }
2.get state和set state
ES6的get和set是取值和存值的函數,這是是對屬性state攔截存取行爲。
示例1
E:\vuex>node //類的聲明,屬性prop進行存取攔截 > class MyClass { ... constructor() { ..... // ... ..... } ... get prop() { ..... return 'getter'; ..... } ... set prop(value) { ..... console.log('setter: ' + value); ..... } ... } undefined > let inst = new MyClass(); undefined //設置prop時,根據程序邏輯會console.log > inst.prop = 123; setter: 123 123 //獲取prop,根據return返回"getter"字符串 > inst.prop 'getter'
源碼1
//取值返回的是this屬性 get state () { return this._vm._data.$$state } //若是在非生產環境,那麼修改state就會使用assert打印錯誤信息 set state (v) { if (process.env.NODE_ENV !== 'production') { assert(false, `use store.replaceState() to explicit replace store state.`) } }
3.commit
commit (_type, _payload, _options) { // check object-style commit檢查對象風格提交 const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) //mutation的type判斷,也就是entry,若是不存在,那麼打印錯誤信息「不存在的mutation type」 const mutation = { type, payload } const entry = this._mutations[type] if (!entry) { if (process.env.NODE_ENV !== 'production') { console.error(`[vuex] unknown mutation type: ${type}`) } return; } //處理entry並訂閱它 this._withCommit(() => { entry.forEach(function commitIterator (handler) { handler(payload) }) }) this._subscribers.forEach(sub => sub(mutation, this.state)) //開發模式下的silent判斷 if ( process.env.NODE_ENV !== 'production' && options && options.silent ) { console.warn( `[vuex] mutation type: ${type}. Silent option has been removed. ` + 'Use the filter functionality in the vue-devtools' ) } }
(1)const { type, payload,options}=unify..........這是ES6的解構賦值。(node環境執行的哦)
示例2
E:\vuex>node > const person = { ... name: 'little bear', ... age: 18, ... sex: '男' ... } undefined > let { name,age,sex } = person undefined > name 'little bear'
(2)this._withCommit(...)小括號內的部分整體上說是_withCommit的fn參數。
this._withCommit()中有對this._committing進行設置,首先this._committing = false賦值給中間變量,接下來提交前設爲true,fn調用結束後再經過中間變量設爲初始值。
接下來講說entry。entry就是mutations的type也就是某個函數。但是明明forEach方法是數組啊。其實經過this._mutations[type]獲取到就是一個數組。那麼對數組的元素handler進行調用。entry
相似以下內容:
(3)this._subscribers.forEach(sub => sub(mutation, this.state))是_subscribers遍歷收集來的actions並執行。咱們要注意到actions的使用也有commit提交,不過是異步的。因此這裏的actions執行是爲了補充剛剛同步提交的方式。
圖示1
(4)process.env.NODE_ENV !== 'production' &&options && options.silent
檢查選項,silent是靜默選項,若是使用了silent,那麼告知"silent已經被移除,請在dev-tool中使用過濾器功能。
4,dispatch
dispatch (_type, _payload) { // 檢查數組風格的分發 const { type, payload } = unifyObjectStyle(_type, _payload) const action = { type, payload } //從this._actions拿到type對應的事件類型 const entry = this._actions[type] //若是entry也就是事件類型不存在,那麼打印信息"vuex不知道的action類型" if (!entry) { if (process.env.NODE_ENV !== 'production') { console.error(`[vuex] unknown action type: ${type}`) } return } //_actionSubscribers遍歷每一個訂閱 this._actionSubscribers.forEach(sub => sub(action, this.state)) //若是entry.length大於1,那麼返回promise return entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) }
5.subscribe和subscribeAction
subscribe訂閱store的mutation。回調函數會在每一個mutaion完成時觸發。
示例
const myPlugin = store => { // 當 store 初始化後訂閱 store.subscribe((mutation, state) => { //回調函數在每次mutation完成以後調用 state.count++; }) } const store = new Vuex.Store({ state:{ count:5 }, mutations:{ increment(state,payload){ state.count=state.count*payload; } }, plugins: [myPlugin] }) //提交"increment"事件 store.commit("increment",20) //最終store.state.count等於5*20+1=101。
subscribeAction訂閱action。回調函數會在每一個action完成時觸發。
const myPlugin2 = store => { // 當 store 初始化後訂閱 store.subscribeAction((action, state) => { //每次action完成後回調函數都會被觸發 state.huge--; }) } const store = new Vuex.Store({ state:{ huge:2000 }, mutations:{ REDUCE(state,payload){ state.huge=state.huge-payload } }, actions:{ reduce({commit,state},payload){ commit("REDUCE",payload) } }, plugins: [myPlugin2] }) store.dispatch("reduce",500) //store.state.huge結果2000-500-1等於1499
源碼分析
subscribe (fn) { //fn即剛纔說的每次mutation以後的回調函數 return genericSubscribe(fn, this._subscribers) } subscribeAction (fn) { return genericSubscribe(fn, this._actionSubscribers) } //subscribe和subscribeAction返回的是一個箭頭函數 function genericSubscribe (fn, subs) { //訂閱fn,那麼會push到this._subscribers或者this._actionSubscribers數組 if (subs.indexOf(fn) < 0) { subs.push(fn) } return () => { //箭頭函數在須要回調的時候再從數組裏裁剪出fn元素 const i = subs.indexOf(fn) if (i > -1) { subs.splice(i, 1) } } }
能夠看出,genericSubscribe功能是對訂閱數組的處理,先存進數組,須要的時候再取出來。
6.watch和replaceState
源碼分析
watch (getter, cb, options) { //若是傳入的getter不是function,那麼打印信息"store.watch只接受一個函數" if (process.env.NODE_ENV !== 'production') { assert(typeof getter === 'function', `store.watch only accepts a function.`) } //返回Vue.$watch方法,響應式監聽() => getter(this.state, this.getters)返回的值 //若是發生變化,那麼cb回調函數觸發 //options包括選項:deep,選項:immediate return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options) }
示例
<!--Vue API中$watch的用法--> <div id="app"> <button @click="addOne">加一</button> </div> <script> let vm= new Vue({ el:"#app", data:{ a:0 }, created:function(){ //$watch監聽第一個函數返回的只,一旦發生變化,那麼執行回調函數 this.$watch(function(){ return this.a; },function(newValue,oldValue){ console.log(newValue) }) }, methods:{ addOne(){ this.a=1; } } }) </script>
示例
//replaceState總體替換state,變化引發回調發生 const store=new Vuex.Store({ state:{ count:0 } }) store.watch(function(){ return store.state; },function(){ console.log(store.state.count)//20 }) store.replaceState({count:20})
示例
//經過mutation改變state,觸發watch回調 const store2=new Vuex.Store({ state:{ count:100 }, mutations:{ ADDONE(state){ state.count++; } } }) store2.watch(function(){ return store2.state.count },function(){ console.log(store2.state.count) //101 } ) store2.commit("ADDONE");
源碼分析
replaceState (state) { this._withCommit(() => { this._vm._data.$$state = state }) }
經過傳入一個新state對象,替換舊state。
示例
const store=new Vuex.Store({ state:{ count:1, num:20 } }) store.replaceState({count:0}); //經過替換,舊的state不存在,只有更新後的state store.state.count//等於0 store.state.num//undefined
7.registerModule和unregisterModule
示例
源碼分析
registerModule (path, rawModule, options = {}) { //傳入的第一個參數要麼是數組,要麼是字符串,字符串會轉化爲字符串爲元素的數組 if (typeof path === 'string') path = [path] //開發環境下的調試信息 if (process.env.NODE_ENV !== 'production') { //若是path不能轉爲數組或者不是數組,那麼打印"模塊path必須是字符串或者數組" assert(Array.isArray(path), `module path must be a string or an Array.`) //若是傳入的path爲[]空數組,那麼打印"不能使用registerModule來註冊根模塊" assert(path.length > 0, 'cannot register the root module by using registerModule.') } //在store._modules上註冊模塊 this._modules.register(path, rawModule) //安裝模塊 installModule(this, this.state, path, this._modules.get(path), options.preserveState) // reset store to update getters... //以當前state更新store.getters resetStoreVM(this, this.state) }
源碼分析
function installModule (store, rootState, path, module, hot) { const isRoot = !path.length const namespace = store._modules.getNamespace(path) // 註冊命名空間的映射數組_modulesNamespaceMap if (module.namespaced) { store._modulesNamespaceMap[namespace] = module } //hot,即options 能夠包含 preserveState: true 以容許保留以前的 state。用於服務端渲染。 if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { Vue.set(parentState, moduleName, module.state) }) } const local = module.context = makeLocalContext(store, namespace, path) //遍歷mutation並註冊mutation,會由於namespaced而不一樣 module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) //遍歷action並註冊action module.forEachAction((action, key) => { //若是action.root爲true,那麼type等於key索引值, //即全局action,不管是子模塊仍是子模塊的子模塊都如此 //若是action.root爲false,那麼type直接取namespacType const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local) }) //遍歷getter並註冊getterts,會由於namespaced而不一樣 module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) }) //遍歷子模塊,並遞歸調用installModule module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) }) }
源碼分析
unregisterModule (path) { if (typeof path === 'string') path = [path] //若是傳入參數不能轉爲數組,那麼打印"模塊路徑必須是字符串或者數組" if (process.env.NODE_ENV !== 'production') { assert(Array.isArray(path), `module path must be a string or an Array.`) } //取消註冊,那麼store._modules.root._children就不會定義myModule屬性了 this._modules.unregister(path) this._withCommit(() => { //getNestedState獲取到父級state const parentState = getNestedState(this.state, path.slice(0, -1)) //Vue刪除相應的module內容 Vue.delete(parentState, path[path.length - 1]) }) //以當前的this重置store resetStore(this) }
8.hotUpdate和_withCommit
源碼分析
//熱重載 hotUpdate (newOptions) { this._modules.update(newOptions) resetStore(this, true) }
Vuex 支持在開發過程當中熱重載 mutation、module、action 和 getter。
_withCommit (fn) { //每次提交的時候,內部代碼都會傳進來一個箭頭函數 const committing = this._committing this._committing = true fn() this._committing = committing }
示例
<script src="js/vue.js"></script> <!--這行語句安裝了window.Vue--> <script> let Vue; if (!Vue && typeof window !== 'undefined' && window.Vue) { console.log("window.vue"); install(window.Vue) } function install (_Vue) { Vue = _Vue console.log(Vue); //applyMixin(Vue)是爲了在Vue初始化以前(beforeCreate)來完成vuex的初始化 //由於2版本才提供了beforeCreate這個鉤子函數 //applyMixin主要邏輯:if (version >= 2) {Vue.mixin({ beforeCreate: vuexInit })} else {} } </script>
從中能夠看出vuex的初始化過程,以Vue2版本爲爲例:
源碼分析
export function install (_Vue) { //那麼問題來了,爲何要使用let Vue這個文件一個全局變量呢?主要是爲了不重複安裝 if (Vue && _Vue === Vue) { if (process.env.NODE_ENV !== 'production') { //若是已經安裝過,那麼Vue就等於window.Vue爲何呢? //Vue.use(plugin)方法會調用export的install方法,那麼調用中使用Vue=_Vue賦值語句 console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return } Vue = _Vue applyMixin(Vue) }
install調用邏輯分析:
1.registerMutation、registerAction、registerGetter
function registerMutation (store, type, handler, local) { //將type屬性添加到_mutations對象,其初始值爲空數組[] const entry = store._mutations[type] || (store._mutations[type] = []) //咱們應該記得mutation是一個函數,那麼function.call作一個繼承,local.state和payload都應用於store對象 entry.push(function wrappedMutationHandler (payload) { handler.call(store, local.state, payload) }) } ........ registerMutation(store, namespacedType, mutation, local)
function registerAction (stobre, type, handler, local) { //_actions具備type屬性,其初始值爲一個數組 const entry = store._actions[type] || (store._actions[type] = []) entry.push(function wrappedActionHandler (payload, cb) { //繼承於store對象 let res = handler.call(store, { dispatch: local.dispatch, commit: local.commit, getters: local.getters, state: local.state, rootGetters: store.getters, rootState: store.state }, payload, cb) //若是res不是一個promise,那麼至關於直接返回含有res內容的promise對象 if (!isPromise(res)) { res = Promise.resolve(res) } //_devtoolHook判斷 if (store._devtoolHook) { //攔截promise錯誤 return res.catch(err => { store._devtoolHook.emit('vuex:error', err) throw err }) } else { //返回res return res } }) } ......... registerAction(store, type, handler, local)
咱們應該還記得action是能夠寫異步操做的。
function registerGetter (store, type, rawGetter, local) { //若是對應已getter存在,進入分支,打印說"vuex重複的getter鍵" if (store._wrappedGetters[type]) { if (process.env.NODE_ENV !== 'production') { console.error(`[vuex] duplicate getter key: ${type}`) } return } store._wrappedGetters[type] = function wrappedGetter (store) { //經過當前local和store返回rawGetter對象 return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) } }
2.enableStrictMode、getNestedState
if (store.strict) { enableStrictMode(store) } //enableStrictMode功能是容許new vm的嚴格模式
function enableStrictMode (store) { //偵聽this._data.$$state也就是state store._vm.$watch(function () { return this._data.$$state }, () => { //state變化,回調函數觸發 //store._committing爲False,那麼打印"不要在mutation處理器外部提交state if (process.env.NODE_ENV !== 'production') { assert(store._committing, `do not mutate vuex store state outside mutation handlers.`) } //deep:true,跟蹤對象內部屬性的變化,sync:true,同步 }, { deep: true, sync: true }) }
首先,getNestedState的功能是父級state對象。
function getNestedState (state, path) { return path.length //state爲初始值,接下來遍歷path數組,並以state[key]取得state對象 ? path.reduce((state, key) => state[key], state) : state }
那麼爲何這個key好比state["myModule"]的索引就能拿到對應的state呢?這是由於state對象長這個樣子。
示例
let vm= new Vue({ el:"#app", }) const store=new Vuex.Store({ state:{ count:0 } }) function getNestedState (state, path) { return path.length ? path.reduce((state, key) => state[key], state) : state } let myModule={ state:{ count:8 } } store.registerModule("myModule",myModule) //找到父級state對象 //["myModule"].slice(0,-1)等於[] let parentState=getNestedState(store.state,["myModule"].slice(0,-1)) console.log(parentState)
結果以下:
3.unifyObjectStyle(type, payload, options)
首先運行一下這個函數,它能夠傳入3個參數(payload)。因爲process是nodejs環境的變量,那麼在nodejs環境中運行。
它的功能是把提交數據對象風格化。
//nodejs環境輸入function代碼 E:\vuex>node > function isObject (obj) { ... return obj !== null && typeof obj === 'object' ... }function unifyObjectStyle (type, payload, options) { ... if (isObject(type) && type.type) { ..... options = payload ..... payload = type ..... type = type.type ..... } ... ... if (process.env.NODE_ENV !== 'production') { ... assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`) ... ... } ... ... return { type, payload, options } ... } undefined //nodejs環境中調用剛剛定義的unifyObjectStyle。 > unifyObjectStyle("login",{name:"vicky",password:"123"}) { type: 'login', payload: { name: 'vicky', password: '123' }, options: undefined }
> unifyObjectStyle({type:"login",payload:{name:"vicky",password:"123"}}) { type: 'login', payload: { type: 'login', payload: { name: 'vicky', password: '123' } }, options: undefined }
它討論了兩種狀況。(1)若是type.type不存在,那麼就是以參數風格的提交,按照最終的對象格式return。(2)若是type.type存在,也就是對象風格的提交,那麼就讓對象的type和payload從新賦值。而後return。以最終實現對象風格的統一。
而process的部分是對type的值進行判斷,若是不是string,那麼assert一個報錯信息。
寫做不易,歡迎打賞!微信哦。