2019-12-8 seo達人
一、前言
我們都知道,vue組件中通信是用props進行父子通信,或者用provide和inject注入的方法,后者類似與redux的porvider,父組件使用,包含在里面的子組件都可以使用,provide/inject用法看我的博客(provide/inject用法),provide/indect官方文檔,不過provide/indect一般用的不多,都是用前者,但是props有一個問題,父傳子沒問題,但是子后面還有子,子后面還有子,子子孫孫無窮盡也,所以這就要一層層的傳下去,太麻煩了,所以vuex就派上用場了,vuex作為一個很輕量的狀態(tài)管理器,有著簡單易用的的API接口,在任意組件里面都能使用,獲取全局狀態(tài),統(tǒng)一獲取改變,今天,就看一下源碼怎么實現(xiàn)的。
二、準備
1)先去github上將vuex源碼下載下來。
2)打開項目,找到examples目錄,在終端,cd進入examples,執(zhí)行npm i,安裝完成之后,執(zhí)行node server.js
3)執(zhí)行上述之后,會得到下方的結(jié)果,表示編譯完成,打開瀏覽器 輸入 localhost:8080
4) 得到頁面,點擊counter
5)最終,我們就從這里出發(fā)調(diào)試吧。
三、調(diào)試
找到counter目錄下的store.js 寫一個debugger,瀏覽器打開F12,刷新頁面,調(diào)試開始。
1.Vue.use()
先進入Vue.use(Vuex),這是Vue使用插件時的用法,官方文檔也說了,Vue.use,會調(diào)用插件的install方法,所以如果你要寫插件的話,你就要暴露一個install方法,詳情請看vue官方文檔之use的用法
即use會調(diào)用下方的方法(debugger會進入)
function initUse (Vue) {
Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
var args = toArray(arguments, 1);
args.unshift(this);
if (typeof plugin.install === 'function') {
// 如果插件的install是一個function,調(diào)用install,將 this指向插件,并將Vue作為第一個參數(shù)傳入
// 所以調(diào)用vuex吧this指向vuex,并吧vue當參數(shù)傳入
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
installedPlugins.push(plugin);
return this
};
}
1.1 vuex的install方法
在源碼目錄的src目錄下的store.js文件里最下方有個install函數(shù),會調(diào)用它
debugger進入后
export function install (_Vue) { // _Vue是上面說的Vue作為第一個參數(shù) ,指針this指向Vuex
if (Vue && _Vue === Vue) {
// 如果你在開發(fā)環(huán)節(jié),你使用了兩次Vue.use(Vuex) 就會報下方的錯誤,提醒你vue只能被use一次,可以自行試試
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue) // 調(diào)用applyMixin方法
}
1.2 vuex的applyMixin方法
applyMixin方法在mixin.js文件里 同樣在src目錄下
export default function (Vue) {
const version = Number(Vue.version.split('.')[0]) // 獲取vue的版本
if (version >= 2) { // vue的版本是2.xx以上 執(zhí)行vue的mixin混入,該函數(shù)不懂用法請查看官方文檔,
// mixin合并混入操作,將vuexInit 方法混入到beforeCreate生命周期,意思就是當執(zhí)行beforeCreate周期的時候
// 會執(zhí)行vuexInit 方法
Vue.mixin({ beforeCreate: vuexInit })
} else { // 1.xxx版本太老了 現(xiàn)在基本不用,暫不講解,可以自行了解
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
/*
Vuex init hook, injected into each instances init hooks list.
/
function vuexInit () {
// 因為該方法是在beforeCreate下執(zhí)行,而beforeCreate的this指向為Vue 所以this === Vue
// 獲得vue里面的option配置,這里涉及到vue的源碼,以后再講vue ,
//所以這就是我們?yōu)槭裁匆趎ew Vue的時候,傳遞一個store進去的原因,
//因為只有傳進去,才能在options中獲取到store
/
var vm = new Vue({
el: "#app",
data() {return{}},
store
})
*/
const options = this.$options
// store injection
if (options.store) {
// 如果options對象中store有,代表是root ,如果options.store是函數(shù),執(zhí)行調(diào)用options.store()
// 如果是對象直接options.store 賦值給this.$stroe
// 這也就是我們?yōu)槭裁茨軌蛟赩ue項目中直接this.$store.disptach('xxx')的原因,從這里獲取
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 如果options.store為空,則判斷options.parent.$store有沒有 從父元素判斷有沒有store,
//從而保證子元素父元素公用一個store實例
this.$store = options.parent.$store
}
}
}
1.3 Vue.use(Vuex)總結(jié)
至此,Vue.use(Vuex)全部分析完成,總結(jié),就是Vue.use調(diào)用Vuex的install的方法,然后install使用mixin混入beforecreate生命周期中混入一個函數(shù),當執(zhí)行生命周期beforecreate的時候回執(zhí)行vuexInit 。你可以慢慢調(diào)試,所以要好好利用下方的調(diào)試按鈕,第二個按鈕執(zhí)行下一步,第三個進入方法,兩個配合使用。
2.new Vuex.Store
Vue.use(Vuex)已經(jīng)結(jié)束,再回到counter目錄下的store.js文件
export default new Vuex.Store({
state,
getters,
actions,
mutations
})
debugger進入,Vuex.Store方法在src目錄下的store.js文件下
export class Store {
constructor (options = {}) {
// 這里的options是在counter定義的 state,getters,actions,mutations當做參數(shù)傳進來
// Auto install if it is not done yet and window
has Vue
.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
if (!Vue && typeof window !== 'undefined' && window.Vue) {
// 掛載在window上的自動安裝,也就是通過script標簽引入時不需要手動調(diào)用Vue.use(Vuex)
install(window.Vue)
}
if (process.env.NODE_ENV !== 'production') {
// 開發(fā)環(huán)境 斷言,如果不符合條件 會警告,這里自行看
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.
)
}
const {
plugins = [],
strict = false
} = options
// store internal state
//下方是在Vuex的this上掛載一些對象,這里暫且不要知道他們要來干什么
// 因為是源碼分析,不要所有的代碼都清除,第一次源碼分析,你就只當他們是掛載對象,往下看
this._committing = false
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
// ModuleCollection代表模塊收集,形成模塊樹
this._modules = new ModuleCollection(options) //碰到第一個不是定義空對象,debugger進去,分析在下面
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) { // 綁定dispatch的指針為store
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) { // 綁定commit的指針為store
return commit.call(store, type, payload, options)
}
// strict mode
this.strict = strict
const state = this._modules.root.state // 獲取到用戶定義的state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
// 初始化root模塊 注冊getters,actions,mutations 子模塊
installModule(this, state, [], this._modules.root)
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
// 初始化vm 用來監(jiān)聽state getter
resetStoreVM(this, state)
// apply plugins
// 插件的注冊
plugins.forEach(plugin => plugin(this))
// 下方的是開發(fā)工具方面的 暫不提
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
}
2.1 new ModuleCollection
ModuleCollection函數(shù)在src目錄下的module目錄下的module-collection.js文件下
export default class ModuleCollection {
constructor (rawRootModule) { // rawRootModule === options
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
}
2.1.1 register()
register (path, rawModule, runtime = true) {
// register([],options,false)
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule) // 開發(fā)環(huán)境斷言,暫忽略
}
const newModule = new Module(rawModule, runtime)
if (path.length === 0) { // path長度為0,為根節(jié)點,將newModule 賦值為root
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
if (rawModule.modules) { // 如果存在子模塊,遞歸調(diào)用register,形成一棵樹
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2.1.2 new Module()
Module函數(shù)在同目錄下的modele.js文件下
export default class Module {
// new Module(options,false)
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule // 將options放到Module上 用_raModele上
const rawState = rawModule.state // 將你定義的state取出
// Store the origin module's state
// 如果你定義的state為函數(shù),調(diào)用函數(shù),為對象,則取對象 賦值為module上的state上
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
}
所以Module的this內(nèi)容為如下:
2.1.3 installModule
function installModule (store, rootState, path, module, hot) {
// installModule(Vuex,state,[],module) module是前面 new ModuleCollection產(chǎn)生的對象
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {
console.error([vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}
)
}
store._modulesNamespaceMap[namespace] = module
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (process.env.NODE_ENV !== 'production') {
if (moduleName in parentState) {
console.warn(
[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"
)
}
}
Vue.set(parentState, moduleName, module.state)
})
}
// 設(shè)置當前上下文
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 type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 注冊getter
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 逐一注冊子module
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
2.1.4 makeLocalContext
// 設(shè)置module的上下文,綁定對應(yīng)的dispatch、commit、getters、state
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
//noNamespace 為true 使用原先的 至于后面的 不知道干啥用的 后面繼續(xù)研究
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
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._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) => {
//noNamespace 為true 使用原先的
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
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
function getNestedState (state, path) {
return path.reduce((state, key) => state[key], state)
}
2.1.5 registerMutation
mutation的注冊,會調(diào)用下方方法,將wrappedMutationHandler函數(shù)放入到entry中
function registerMutation(store, type, handler, local) {
// entry和store._mutations[type] 指向同一個地址
var entry = store._mutations[type] || (store._mutations[type] = []);
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload);
});
}
2.1.6 forEachAction
action的注冊,會調(diào)用下方方法,將wrappedActionHandler函數(shù)放入到entry中
function registerAction(store, type, handler, local) {
var entry = store._actions[type] || (store._actions[type] = []);
// entry和store._actions[type]指向同一個地址
entry.push(function wrappedActionHandler(payload) {
var res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload);
if (!(0, _util.isPromise)(res)) {
res = Promise.resolve(res);
}
if (store._devtoolHook) {
return res.catch(function (err) {
store._devtoolHook.emit('vuex:error', err);
throw err;
});
} else {
return res;
}
});
}
因為entry和上面的_action[type],_mutations[type] 指向同一個地址,所以:
2.1.7 forEachGetter
getter的注冊,會調(diào)用下方方法
function registerGetter(store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
if (true) {
console.error('[vuex] duplicate getter key: ' + type);
}
return;
}
store._wrappedGetters[type] = function wrappedGetter(store) {
return rawGetter(local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
);
};
}
2.1.8 resetStoreVM
function resetStoreVM (store, state, hot) {
const oldVm = store._vm //將_vm用變量保存
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters // 獲取installModule方法完成的_wrappedGetters內(nèi)容
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
// 攔截get返回store._vm[key]中的值,即可以通過 this.$store.getters.xxx訪問值
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設(shè)置為true,取消所有日志警告等
Vue.config.silent = true
store._vm = new Vue({ // 將state,getter的值進行監(jiān)聽,從這里就可以看出getter其實就是采用的vue的computed
data: {
$$state: state
},
computed
})
// 恢復(fù)原先配置
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
// 若存在舊的實例,解除對state的引用,等dom更新后把舊的vue實例銷毀
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
看到這里,你應(yīng)該對vuex有初步的了解
// 這也是我們?yōu)槭裁茨苡迷L問到state,getter的原因
//this.store.dispatch('xxx') ,this.$store.dispatch('xxx')
1
2
相信你也有點亂,其實上面很多我沒講到的不是我不想講,是具體我也不知道干啥的,看源碼學(xué)習(xí)呢,看主要就行,后面理解多了,其他的就慢慢都會,你不可能剛開始看,就每一行,他寫的每一句的用途都知道是干什么的,只能先看主要方法,在慢慢琢磨,一步步來吧,別急,現(xiàn)在我來畫一個流程圖,更好的去理解吧。
2.1.9 流程圖
3.連貫
import Vue from 'vue'
import Counter from './Counter.vue'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(Counter)
})
當運行new Vue的時候,傳入了store,前面minix beforecreate,執(zhí)行到beforecreate鉤子時,會調(diào)用vueInit函數(shù),就可以在this.$store取到store對象了,因為options.store有值了 ,不為空,這樣就連貫到了,所以這就是為什么可以用this.$store取值。
藍藍設(shè)計的小編 http://www.wnxcall.com