摘要:大家好,今天給大家帶來的是源碼分析,希望能夠能跟大家進行交流,歡迎提意見,寫的不好的地方歡迎拍磚源碼地址首先我們先來看看是如何跟項目一起結合使用的,以下是官方中的一個簡單例子我們必須先創建一個將這個傳給的實例,這樣我們就能夠在中獲取到這個并
大家好,今天給大家帶來的是vuex(2.3.1)源碼分析,希望能夠能跟大家進行交流,歡迎提意見,寫的不好的地方歡迎拍磚 [github源碼地址][1] 首先我們先來看看vuex是如何跟vue項目一起結合使用的,以下是官方demo中的一個簡單例子
(1)我們必須先創建一個store
import Vue from "vue" import Vuex from "vuex" import { state, mutations } from "./mutations" import plugins from "./plugins" Vue.use(Vuex) export default new Vuex.Store({ state, mutations, plugins })
(2)將這個store傳給vue的實例,這樣我們就能夠在vue中獲取到這個store并且使用它的功能了
import "babel-polyfill" import Vue from "vue" import store from "./store" import App from "./components/App.vue" new Vue({ store, // inject store to all children el: "#app", render: h => h(App) })
以上就是vuex的簡單使用方法,然后接下來我們就開始來分析vuex的源碼吧
目錄結構
從目錄結構可以看出,vuex是一個代碼比較簡潔的框架
index.js——入口文件
import { Store, install } from "./store" import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from "./helpers" export default { Store, install, version: "__VERSION__", mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }
入口文件只做了一件事,就是導入了其他相關的文件,并且將vuex的功能export出去,相當于定義vuex對外使用的API
store.js——vuex的倉庫,也是vuex中比較重要的一環
這個文件比較長,我們可以一點一點來分析:
總體來說,這個文件做了幾件事,定義并導出了Store這個類和install方法,并執行了install這個方法,我們都知道,vue的所有插件都是通過install這個方法來安裝的
import applyMixin from "./mixin" import devtoolPlugin from "./plugins/devtool" import ModuleCollection from "./module/module-collection" import { forEachValue, isObject, isPromise, assert } from "./util"
一開始導入相關的方法,后面會解釋這些方法的用處
let Vue // 定義了變量Vue,為的是引用外部的vue構造函數,這樣vuex框架就可以不用導入vue這個庫了
----------------------------------------------------------這是分割線----------------------------------------------------------------------------------------
接下來是定義Store這個類,從圖中可以看出這個vuex中的外store對外提供的能力,包括常用的commit,dispatch,watch等
先看看構造函數吧:
constructor (options = {}) { // 這個是在開發過程中的一些環節判斷,vuex要求在創建vuex store實例之前必須先使用這個方法Vue.use(Vuex)來安裝vuex,項目必須也得支持promise,store也必須通過new來創建實例 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 let { state = {} } = options // 這個簡單的,不解釋 if (typeof state === "function") { state = state() } // store internal state // 初始化store內部狀態,Object.create(null)可以創建一個干凈的空對象 this._committing = false this._actions = Object.create(null) this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) // vuex支持模塊,即將state通過key-value的形式拆分為多個模塊 // 模塊的具體內容可以查看這里 :https://vuex.vuejs.org/en/mutations.html this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) // 監聽隊列,當執行commit時會執行隊列中的函數 this._subscribers = [] // 創建一個vue實例,利用vue的watch的能力,可以監控state的改變,具體后續watch方法會介紹 this._watcherVM = new Vue() // bind commit and dispatch to self const store = this // 緩存dispatch和commit方法 const { dispatch, commit } = this // 定義dispatch方法 this.dispatch = function boundDispatch (type, payload) { return dispatch.call(store, type, payload) } // 定義commit方法 this.commit = function boundCommit (type, payload, options) { return commit.call(store, type, payload, options) } // strict mode // 定義嚴格模式,不要在發布環境下啟用嚴格模式!嚴格模式會深度監測狀態樹來檢測不合規的狀態變更——請確保在發布環境下關閉嚴格模式,以避免性能損失。 // 具體后續enableStrictMode方法會提到 this.strict = strict // init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters // 這個作者的注釋已經寫得挺明白,就是初始化根模塊,遞歸注冊子模塊,收集getter installModule(this, state, [], this._modules.root) // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) // 初始化store中的state,使得state變成響應式的,原理就是將state作為一個vue實例的data屬性傳入,具體在分析這個函數的時候會介紹 resetStoreVM(this, state) // apply plugins // 執行插件,這個是一個數組,所以遍歷他,然后執行每個插件的函數 plugins.concat(devtoolPlugin).forEach(plugin => plugin(this)) }
呼呼呼~ 至此,終于把store類全部讀完了,休息五分鐘,然后繼續往下看哈。
接下來關于state的獲取和設置
// 獲取state, 直接返回內部data的$$state get state () { return this._vm._data.$$state } set state (v) { if (process.env.NODE_ENV !== "production") { assert(false, `Use store.replaceState() to explicit replace store state.`) } }
commit是vuex中一個比較重要的操作,因為它可以觸發mutation修改對state的修改,并且是同步執行的
commit (_type, _payload, _options) { // check object-style commit // 首先統一傳入參數的格式,主要是針對當type是個對象的情況,需要把這個對象解析出來 const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) // 緩存本次commit操作的類型和負荷,以供后續監聽隊列(this._subscribers)使用 const mutation = { type, payload } // 獲取相關的type的mutation函數,我們都知道,在vuex中都是通過commit一個類型然后觸發相關的mutation函數來操作state的,所以在此必須獲取相關的函數 const entry = this._mutations[type] if (!entry) { if (process.env.NODE_ENV !== "production") { console.error(`[vuex] unknown mutation type: ${type}`) } return } // 在_withCommit中觸發上面獲取的mutation函數,簡單粗暴使用數組的forEach執行哈哈,之所以要在外面包一層_withCommit,是表明操作的同步性 this._withCommit(() => { entry.forEach(function commitIterator (handler) { handler(payload) }) }) // 這個就是之前說的監聽隊列,在每次執行commit函數時都會遍歷執行一下這個隊列 this._subscribers.forEach(sub => sub(mutation, this.state)) 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" ) } }
dispatch是跟commit有點相似的函數,但是commit必須是同步的,而dispatch是異步的,內部還是必須通過commit來操作state
dispatch (_type, _payload) { // check object-style dispatch // 同上面commit,不解釋 const { type, payload } = unifyObjectStyle(_type, _payload) // 因為dispatch觸發的是actions中的函數,所以這里獲取actions相關函數,過程類似commit const entry = this._actions[type] if (!entry) { if (process.env.NODE_ENV !== "production") { console.error(`[vuex] unknown action type: ${type}`) } return } // 因為dispatch支持異步,所以這里作者使用Promise.all來執行異步函數并且判斷所有異步函數是否都已經執行完成,所以在文件最開始判斷了當前環境必須支持promise就是這個原因 return entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) }
subscribe函數,這是pub/sub模式在vuex中的一個運用,用戶可以通過subscribe函數來監聽state的變化,函數返回一個取消監聽的函數,便于用戶在合適的時機取消訂閱
subscribe (fn) { const subs = this._subscribers if (subs.indexOf(fn) < 0) { subs.push(fn) } // 返回取消訂閱的函數,通過函數額splice方法,來清除函數中不需要的項 return () => { const i = subs.indexOf(fn) if (i > -1) { subs.splice(i, 1) } } }
watch函數,響應式地監測一個 getter 方法的返回值,當值改變時調用回調函數,原理其實就是利用vue中的watch方法
watch (getter, cb, options) { if (process.env.NODE_ENV !== "production") { assert(typeof getter === "function", `store.watch only accepts a function.`) } // 在上面構造函數中,我們看到this._watcherVM就是一個vue的實例,所以可以利用它的watch來實現vuex的watch,原理都一樣,當監聽的值或者函數的返回值發送改變的時候,就觸發相應的回調函數,也就是我們傳入的cb參數,options則可以來讓監聽立即執行&深度監聽對象 return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options) }
replaceState,根據名字就可知道,是替換當前的state
replaceState (state) { this._withCommit(() => { this._vm._data.$$state = state }) }
registerModule函數,可以使用 store.registerModule 方法注冊模塊
registerModule (path, rawModule) { 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.`) assert(path.length > 0, "cannot register the root module by using registerModule.") } //其實內部時候通過,register方法,遞歸尋找路徑,然后將新的模塊注冊root模塊上,具體后續介紹到module的時候會詳細分析 this._modules.register(path, rawModule) //安裝模塊,因為每個模塊都有他自身的getters,actions, modules等,所以,每次注冊模塊都必須把這些都注冊上,后續介紹installModule的時候,會詳細介紹到 installModule(this, this.state, path, this._modules.get(path)) // reset store to update getters... // 重置VM resetStoreVM(this, this.state) }
unregisterModule函數,上述registerModule函數的相反操作,具體在module的時候會介紹到,在此了解個大概,先不糾結細節
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.`) } this._modules.unregister(path) this._withCommit(() => { const parentState = getNestedState(this.state, path.slice(0, -1)) // 利用vue.delete方法,確保模塊在被刪除的時候,視圖能監聽到變化 Vue.delete(parentState, path[path.length - 1]) }) resetStore(this) }
hotUpdate函數,Vuex 支持在開發過程中熱重載 mutation、modules、actions、和getters
hotUpdate (newOptions) { this._modules.update(newOptions) resetStore(this, true) }
_withCommit函數,從函數名可以看出這是一個內部方法,作用就是保證commit過程中執行的方法都是同步的
_withCommit (fn) { // 保存原來的committing的狀態 const committing = this._committing //將想在的committing狀態設置為true this._committing = true //執行函數 fn() //將committing狀態設置為原來的狀態 this._committing = committing }
到目前為止,我們已經看完了Store這個類的所有代碼~慢慢消化,然后繼續往下
----------------------------------------------------------這又是分割線----------------------------------------------------------------------------------------
接下來,我們分析一下,一些其他的輔助方法,跟上面store的一些內容會有相關。ready? Go
resetStore函數,用于重置整個vuex中的store,從代碼中可以看出,這個函數主要的功能,就是將傳入的store實例的_actions,_mutations,_wrappedGetters,_modulesNamespaceMap置為空,然后重新安裝模塊和重置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) }
resetStoreVM函數,這個用于重置store中的vm,所謂vm,指的就是視圖模型,也就是常見mvvm中的vm,在此指的是將state作為data中$$state屬性的一個vue實例
function resetStoreVM (store, state, hot) { // 保存原有store的_vm const oldVm = store._vm // bind store public getters store.getters = {} // store的_wrappedGetters緩存了當前store中所有的getter const wrappedGetters = store._wrappedGetters const computed = {} //遍歷這個對象,獲取每個getter的key和對應的方法 forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism // 將getter以key-value的形式緩存在變量computed中,其實后面就是將getter作為vue實例中的計算屬性 computed[key] = () => fn(store) // 當用戶獲取getter時,相當于獲取vue實例中的計算屬性,使用es5的這個Object.defineProperty方法做一層代理 Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters }) }) // use a Vue instance to store the state tree // suppress warnings just in case the user has added // some funky global mixins const silent = Vue.config.silent // silent設置為true,則取消了所有的警告和日志,眼不見為凈 Vue.config.silent = true // 將傳入的state,作為vue實例中的data的$$state屬性,將剛剛使用computed變量搜集的getter,作為實例的計算屬性,所以當state和getter都變成了響應式的了 store._vm = new Vue({ data: { $$state: state }, computed }) Vue.config.silent = silent // enable strict mode for new vm if (store.strict) { //如果設置了嚴格模式則,不允許用戶在使用mutation以外的方式去修改state enableStrictMode(store) } if (oldVm) { if (hot) { // dispatch changes in all subscribed watchers // to force getter re-evaluation for hot reloading. store._withCommit(() => { // 將原有的vm中的state設置為空,所以原有的getter都會重新計算一遍,利用的就是vue中的響應式,getter作為computed屬性,只有他的依賴改變了,才會重新計算,而現在把state設置為null,所以計算屬性重新計算 oldVm._data.$$state = null }) } // 在下一次周期銷毀實例 Vue.nextTick(() => oldVm.$destroy()) } }
installModule函數,用于安裝模塊,注冊相應的mutation,action,getter和子模塊等
function installModule (store, rootState, path, module, hot) { //判斷是否為根模塊 const isRoot = !path.length //根據路徑生成相應的命名空間 const namespace = store._modules.getNamespace(path) // register in namespace map if (module.namespaced) { store._modulesNamespaceMap[namespace] = module } // set state if (!isRoot && !hot) { // 將模塊的state設置為響應式 const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { Vue.set(parentState, moduleName, module.state) }) } //設置本地上下文,主要是針對模塊的命名空間,對dispatch,commit,getters和state進行修改,后面講到makeLocalContext的時候會詳細分析,現在只需要知道,這個操作讓用戶能夠直接獲取到對象子模塊下的對象就可以了 const local = module.context = makeLocalContext(store, namespace, path) //將mutation注冊到模塊上 module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) //將action注冊到模塊上 module.forEachAction((action, key) => { const namespacedType = namespace + key registerAction(store, namespacedType, action, local) }) //將getter注冊到模塊上 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) }) }
makeLocalContext函數,就是installModule中設置本地上下文的具體實現
function makeLocalContext (store, namespace, path) { //如果沒有命名空間,則是使用全局store上的屬性,否則對store上的屬性進行本地化處理 const noNamespace = namespace === "" const local = { dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => { //dispatch的本地化處理,就是修改type const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { //在type前面加上命名空間 type = namespace + type if (process.env.NODE_ENV !== "production" && !store._actions[type]) { console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`) return } } //調用store上的dispatch方法 return store.dispatch(type, payload) }, commit: noNamespace ? store.commit : (_type, _payload, _options) => { // commit的本地化修改跟dispatch相似,也是只是修改了type,然后調用store上面的commit const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { type = namespace + type if (process.env.NODE_ENV !== "production" && !store._mutations[type]) { console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`) return } } store.commit(type, payload, options) } } // getters and state object must be gotten lazily // because they will be changed by vm update //gettters和state的修改,則依賴于makeLocalGetters函數和getNestedState函數,后面會分析 Object.defineProperties(local, { getters: { get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace) }, state: { get: () => getNestedState(store.state, path) } }) return local }
makeLocalGetters函數,則是對getter進行本地化處理
function makeLocalGetters (store, namespace) { const gettersProxy = {} const splitPos = namespace.length Object.keys(store.getters).forEach(type => { //這里獲取的每個type都是一個有命名空間+本地type的字符串,例如: type的值可能為 “m1/m2/”+"typeName" // skip if the target getter is not match this namespace if (type.slice(0, splitPos) !== namespace) return // extract local getter type const localType = type.slice(splitPos) // Add a port to the getters proxy. // Define as getter property because // we do not want to evaluate the getters in this time. //相當于做了一層代理,將子模塊的localType映射到store上的type Object.defineProperty(gettersProxy, localType, { get: () => store.getters[type], enumerable: true }) }) return gettersProxy }
registerMutation函數,就是注冊mutation的過程,將相應type的mutation推到store._mutations[type]的隊列中,當commit這個type的時候就觸發執行隊列中的函數
function registerMutation (store, type, handler, local) { const entry = store._mutations[type] || (store._mutations[type] = []) entry.push(function wrappedMutationHandler (payload) { handler(local.state, payload) }) }
registerAction函數,注冊action的過程,原理類似于registerMutation,不同點在于action支持異步,所以必須用promise進行包裝
function registerAction (store, type, handler, local) { const entry = store._actions[type] || (store._actions[type] = []) entry.push(function wrappedActionHandler (payload, cb) { let res = handler({ dispatch: local.dispatch, commit: local.commit, getters: local.getters, state: local.state, rootGetters: store.getters, rootState: store.state }, payload, cb) 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 } }) }
registerGetters函數,根據type,將getter方法掛載在store._wrappedGetters[type]下面
function registerGetter (store, type, rawGetter, local) { if (store._wrappedGetters[type]) { if (process.env.NODE_ENV !== "production") { console.error(`[vuex] duplicate getter key: ${type}`) } return } store._wrappedGetters[type] = function wrappedGetter (store) { // 為子模塊的getter提供了這個四個參數,方便用戶獲取,如果是根模塊,則local跟store取出來的state和getters相同 return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) } }
enableStrictMode函數則是在嚴格模式下,不允許state被除mutation之外的其他操作修改,代碼比較簡單,利用vue的$watch方法實現的
function enableStrictMode (store) { store._vm.$watch(function () { return this._data.$$state }, () => { if (process.env.NODE_ENV !== "production") { assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`) } }, { deep: true, sync: true }) }
getNestedState函數,獲取對應路徑下的state
function getNestedState (state, path) { return path.length ? path.reduce((state, key) => state[key], state) : state }
unifyObjectStyle函數,作用是調整參數,主要是當type是一個對象的時候,對參數進行調整
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 } }
以上是相關輔助函數的全部內容,你看明白了么~
----------------------------------------------------------這依然是分割線------------------------------------------------------------------------------------
文件的最后,就是定義了install函數,然后自動執行了這個函數,讓vuex能夠在項目中運作起來
export function install (_Vue) { if (Vue) { if (process.env.NODE_ENV !== "production") { console.error( "[vuex] already installed. Vue.use(Vuex) should be called only once." ) } return } Vue = _Vue //在vue的生命周期中初始化vuex,具體實現后面講到mixin.js這個文件時會說明 applyMixin(Vue) } // auto install in dist mode if (typeof window !== "undefined" && window.Vue) { install(window.Vue) }
以上就是store.js的所有內容啦~
----------------------------------------------------------嚴肅分割線------------------------------------------------------------------------------------
接下來講解有關module目錄下的內容,該目錄有兩個文件分別是module-collection.js和module.js,這兩個文件主要是有關于vuex中模塊的內容;
首先我們看看module-collection.js,這個文件主要導出一個ModuleCollection類:
構造函數
constructor (rawRootModule) { // register root module (Vuex.Store options) //主要是注冊根模塊,我們在之前store的構造函數中曾經使用到 this._modules = new ModuleCollection(options),注冊一個根模塊然后緩存在this._module中 this.register([], rawRootModule, false) }
緊接著看看下面register函數,它用于注冊模塊
register (path, rawModule, runtime = true) { if (process.env.NODE_ENV !== "production") { assertRawModule(path, rawModule) } // 創建一個新模塊,具體會在后面講到Module的時候分析 const newModule = new Module(rawModule, runtime) // 判讀是否為根模塊 if (path.length === 0) { this.root = newModule } else { //根據path路徑,利用get方法獲取父模塊 const parent = this.get(path.slice(0, -1)) //為父模塊添加子模塊 parent.addChild(path[path.length - 1], newModule) } // register nested modules // 如果當前模塊里面有子模塊,則遞歸的去注冊子模塊 if (rawModule.modules) { forEachValue(rawModule.modules, (rawChildModule, key) => { this.register(path.concat(key), rawChildModule, runtime) }) } }
相反,unregister函數則是移除一個模塊
unregister (path) { // 通過get方法獲取父模塊 const parent = this.get(path.slice(0, -1)) //獲取需要刪除的模塊的名稱,即他的key const key = path[path.length - 1] if (!parent.getChild(key).runtime) return //利用module中removeChild方法刪除該模塊,其實就是delete了對象上的一個key parent.removeChild(key) }
get函數,其實就是利用es5中數組reduce方法,從根模塊開始根據傳入的path來獲取相應的子模塊
get (path) { return path.reduce((module, key) => { return module.getChild(key) }, this.root) }
getNamespace函數,利用傳入的參數path,生成相應的命名空間,實現的原理跟上述的get方法類似
getNamespace (path) { let module = this.root return path.reduce((namespace, key) => { module = module.getChild(key) return namespace + (module.namespaced ? key + "/" : "") }, "") }
upate方法,就是更新模塊,具體看下面update方法的實現
update (rawRootModule) { update([], this.root, rawRootModule) }
以上就是整個ModuleCollection類的實現
接下來講解一下function update的實現
function update (path, targetModule, newModule) { if (process.env.NODE_ENV !== "production") { assertRawModule(path, newModule) } // update target module //目標模塊更新為新模塊,具體實現是將原有模塊的namespaced,actions,mutations,getters替換為新模塊的namespaced,actions,mutations,getters // 具體會在Module類中update方法講解 targetModule.update(newModule) // update nested modules // 如果新的模塊有子模塊,則遞歸更新子模塊 if (newModule.modules) { for (const key in newModule.modules) { if (!targetModule.getChild(key)) { if (process.env.NODE_ENV !== "production") { console.warn( `[vuex] trying to add a new module "${key}" on hot reloading, ` + "manual reload is needed" ) } return } update( path.concat(key), targetModule.getChild(key), newModule.modules[key] ) } } }
至于assertRawModule方法和makeAssertionMessage方法,就是一些簡單的校驗和提示,不影響主流程&代碼比較簡單,這里不做贅述
以上就是整個module-collection.js文件的所有內容
接下來就應該分析目錄中的另一個文件module.js,這個文件主要導出一個Module類,這個類主要描述了vuex中模塊的功能
構造函數,主要做了一些模塊初始化的事情
//構造函數,主要做了一些模塊初始化的事情 constructor (rawModule, runtime) { //緩存運行時的標志 this.runtime = runtime //創建一個空對象來保存子模塊 this._children = Object.create(null) //緩存傳入的模塊 this._rawModule = rawModule //緩存傳入模塊的state,如果state是一個函數,則執行這個函數 const rawState = rawModule.state this.state = (typeof rawState === "function" ? rawState() : rawState) || {} }
namespaced函數是主要就是獲取當前模塊是否是命名模塊,vuex支持命名模塊和匿名模塊
get namespaced () { return !!this._rawModule.namespaced }
addChild,removeChild,getChild這三個函數就分別是添加,刪除,獲取子模塊,內容比較簡單,不贅述
update方法,將原有緩存模塊的namespaced,actions,mutations,getters替換成新傳入模塊的namespaced,actions,mutations,getters
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 } }
forEachChild函數,利用util中forEachValue方法,變量每個子模塊,將每個子模塊作為傳入的回調函數參數,然后執行回調函數
forEachChild (fn) { forEachValue(this._children, fn) }
forEachGetter,forEachAction,forEachMutation代碼邏輯跟上述forEachChild十分類似,不在贅述
以上就是module.js文件的所有內容,至此我們也已經全部分析完module目錄下的所有代碼了
---------------------------------------------------一本正經分割線--------------------------------------------------------------------------------
接下來,我們再看看help.js這個文件,這個文件主要是提供了一些幫助性的方法,使得用戶在使用vuex的過程中體驗更好,更加方便
首先我們先看看文件后面三個函數:normalizeMap,normalizeNamespace,getModuleByNamespace
normalizeMap函數,這個方法的作用是格式化傳入的對象
function normalizeMap (map) { // 如果傳入的對象是數組,則放回一個每一項都是key-val對象的數組,其中key和val的值相同 // 如果出入的是一個對象,則變量這個對象,放回一個每一項都是key-val數組,其中key對應對象的key,val對應相應key的值 return Array.isArray(map) ? map.map(key => ({ key, val: key })) : Object.keys(map).map(key => ({ key, val: map[key] })) }
normalizeNamespace函數,調整參數,格式化命名空間
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) } }
getModuleByNamespace函數,通過命名空間來獲取模塊
function getModuleByNamespace (store, helper, namespace) { // 返回store._modulesNamespaceMap緩存的模塊 const module = store._modulesNamespaceMap[namespace] if (process.env.NODE_ENV !== "production" && !module) { console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`) } return module }
mapState函數,我們可以通過這個方法將state解構到vue項目中去,使其變成vue實例中的計算屬性
export const mapState = normalizeNamespace((namespace, states) => { //定義一個空對象 const res = {} normalizeMap(states).forEach(({ key, val }) => { //收集states的所有key,對應key的值,改變成一個mappedState方法,符合計算屬性的特點 res[key] = function mappedState () { //獲取store的state和getters let state = this.$store.state let getters = this.$store.getters //如果存在命名空間,則將命名空間下子模塊的state和getters覆蓋原來store的state和getters if (namespace) { const module = getModuleByNamespace(this.$store, "mapState", namespace) if (!module) { return } state = module.context.state getters = module.context.getters } //如果對應的val是函數則執行,否則返回state下的值 return typeof val === "function" ? val.call(this, state, getters) : state[val] } // mark vuex getter for devtools res[key].vuex = true }) //返回這個包裝過state的對象,這個對象可以結構成vue中的計算屬性 return res })
mapMutations函數,則是將mutation解構到vue實例中的methods中,使得用戶可以直接調用methods中的方法來執行store.commit
export const mapMutations = normalizeNamespace((namespace, mutations) => { //定義一個空對象 const res = {} normalizeMap(mutations).forEach(({ key, val }) => { val = namespace + val res[key] = function mappedMutation (...args) { if (namespace && !getModuleByNamespace(this.$store, "mapMutations", namespace)) { return } //調用了store中的commit方法,觸發相應的mutation函數的執行 return this.$store.commit.apply(this.$store, [val].concat(args)) } }) return res })
mapGetters的邏輯跟mapState類似,mapActions的邏輯跟mapMutations類似,這里不再贅述
自此,我們把help.js的內容也分析完了
---------------------------------------------------一本正經分割線--------------------------------------------------------------------------------
接下來我們看看mixin.js文件
還記得之前store.js里面有個install方法么,這個方法就用到了mixin.js文件提供的內容
// 這個文件其實就導出了一個方法,供vuex在被引入的時候,能夠順利安裝到項目中 export default function (Vue) { // 首先,判斷vue版本,不同的vue版本,生命周期不同,所以需要做差異處理 const version = Number(Vue.version.split(".")[0]) if (version >= 2) { // 如果版本是2.0以上的,則在vue的beforeCreate生命周期中,觸發vuex的初始化 // 利用的是vue中全局混入的形式 Vue.mixin({ beforeCreate: vuexInit }) } else { // override init and inject vuex init procedure // for 1.x backwards compatibility. // 如果是1.x版本的話,就改寫原有Vue原型上的_init方法 // 先將原來的函數保存在常量_init中 const _init = Vue.prototype._init Vue.prototype._init = function (options = {}) { options.init = options.init ? [vuexInit].concat(options.init) : vuexInit // 將初始化方法作為原有init的參數傳入,所以在vue初始化的時候就會執行vuexInit方法來初始化vuex _init.call(this, options) } } /** * Vuex init hook, injected into each instances init hooks list. */ // vuex的初始化鉤子 function vuexInit () { const options = this.$options // store injection // 注入store if (options.store) { this.$store = typeof options.store === "function" ? options.store() : options.store } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store } } }
plugins文件夾中,主要是關于插件相關的內容
devtool.js,是關于是當用戶開啟vue-devtools時,觸發了一些操作
// 通過全局變量__VUE_DEVTOOLS_GLOBAL_HOOK__,判斷是否開啟vue-devtools const devtoolHook = typeof window !== "undefined" && window.__VUE_DEVTOOLS_GLOBAL_HOOK__ export default function devtoolPlugin (store) { if (!devtoolHook) return store._devtoolHook = devtoolHook // vue-devtool自身實現了一套事件機制,有興趣可以看看其中的實現 devtoolHook.emit("vuex:init", store) devtoolHook.on("vuex:travel-to-state", targetState => { //用targetState替換當前的state store.replaceState(targetState) }) // 當觸發commit的時候執行這個方法 store.subscribe((mutation, state) => { // devtoolHook會emit一個vuex:mutation事件 devtoolHook.emit("vuex:mutation", mutation, state) }) }
logger.js是在開發過程中記錄日志的插件
// Credits: borrowed code from fcomb/redux-logger // 引入深拷貝方法 import { deepCopy } from "../util" export default function createLogger ({ collapsed = true, filter = (mutation, stateBefore, stateAfter) => true, transformer = state => state, mutationTransformer = mut => mut } = {}) { return store => { // 保存原有的state let prevState = deepCopy(store.state) // 監聽state的變化 store.subscribe((mutation, state) => { if (typeof console === "undefined") { return } //深拷貝并且獲取新的state const nextState = deepCopy(state) if (filter(mutation, prevState, nextState)) { // 獲取當前時間 const time = new Date() // 格式化時間 const formattedTime = ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}` // 格式化mutation const formattedMutation = mutationTransformer(mutation) // 獲取輸出的信息 const message = `mutation ${mutation.type}${formattedTime}` // 在 Web控制臺上創建一個新的分組.隨后輸出到控制臺上的內容都會被添加一個縮進 const startMessage = collapsed ? console.groupCollapsed : console.group // render try { // 輸出日志 startMessage.call(console, message) } catch (e) { console.log(message) } console.log("%c prev state", "color: #9E9E9E; font-weight: bold", transformer(prevState)) console.log("%c mutation", "color: #03A9F4; font-weight: bold", formattedMutation) console.log("%c next state", "color: #4CAF50; font-weight: bold", transformer(nextState)) try { console.groupEnd() } catch (e) { console.log("—— log end ——") } } // 替換state prevState = nextState }) } }
至于util.js,內部提供一些簡單的工具方法,不再贅述啦~可自行研究
---------------------------------------------------最后的分割線--------------------------------------------------------------------------------
以上,便是vuex源碼的所有內容。。不管寫的怎么樣,你都看到這里啦,對此深表感謝~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84022.html
摘要:提供了函數,它把直接映射到我們的組件中,先給出的使用值為值為讓我們看看的源碼實現規范當前的命名空間。在中,都是同步事務。同步的意義在于這樣每一個執行完成后都可以對應到一個新的狀態和一樣,這樣就可以打個存下來,然后就可以隨便了。 Vue 組件中獲得 Vuex 狀態 按官網說法:由于 Vuex 的狀態存儲是響應式的,從 store 實例中讀取狀態最簡單的方法就是在計算屬性中返回某個狀態,本...
摘要:假如你通過閱讀源碼,掌握了對的實現原理,對生態系統有了充分的認識,那你會在面試環節游刃有余,達到晉級阿里的技術功底,從而提高個人競爭力,面試加分更容易拿。 前言 一年一度緊張刺激的高考開始了,與此同時,我也沒閑著,奔走在各大公司的前端面試環節,不斷積累著經驗,一路升級打怪。 最近兩年,太原作為一個準二線城市,各大互聯網公司的技術棧也在升級換代,假如你在太原面試前端崗位,而你的技術庫里若...
摘要:我們通常稱之為狀態管理模式,用于解決組件間通信的以及多組件共享狀態等問題。創建指定命名空間的輔助函數,總結的功能首先分為兩大類自己的實例使用為組件中使用便利而提供的輔助函數自己內部對數據狀態有兩種功能修改數據狀態異步同步。 what is Vuex ? 這句話我想每個搜索過Vuex官網文檔的人都看到過, 在學習源碼前,當然要有一些前提條件了。 了解Vuex的作用,以及他的使用場景。 ...
摘要:為了更清楚的理解它的原理和實現,還是從源碼開始讀起吧。結構梳理先拋開,的主要源碼一共有三個文件,,初始化相關用到了和我們使用創建的實例并傳遞給的根組件。這個方法的第一個參數是構造器。的中,在保證單次調用的情況下,調用對構造器進入了注入。 原文鏈接 Vuex 作為 Vue 官方的狀態管理架構,借鑒了 Flux 的設計思想,在大型應用中可以理清應用狀態管理的邏輯。為了更清楚的理解它的原理和...
摘要:的源碼分析系列大概會分為三篇博客來講,為什么分三篇呢,因為寫一篇太多了。這段代碼檢測是否存在,如果不存在那么就調用方法這行代碼用于確保在我們實例化之前,已經存在。每一個集合也是的實例。 vuex的源碼分析系列大概會分為三篇博客來講,為什么分三篇呢,因為寫一篇太多了。您看著費勁,我寫著也累showImg(https://segmentfault.com/img/bVXKqD?w=357&...
閱讀 2233·2021-09-23 11:52
閱讀 1899·2021-09-02 15:41
閱讀 3018·2019-08-30 10:47
閱讀 1984·2019-08-29 17:14
閱讀 2337·2019-08-29 16:16
閱讀 3192·2019-08-28 18:29
閱讀 3418·2019-08-26 13:30
閱讀 2610·2019-08-26 10:49