解析源碼前,我們先來簡單的了解下Vuex喂分。
Vuex 是為 Vue 量身定做的狀態(tài)管理狀態(tài)管理模式锦庸,目的是解決多個組件共享狀態(tài)帶來的復(fù)雜數(shù)據(jù)流難以為維護(hù)的問題。簡單的說蒲祈,就是在多個組件間更工程化甘萧、簡潔明了的共享數(shù)據(jù)處理。
一梆掸、項(xiàng)目目錄
首先扬卷,讓我先瀏覽下項(xiàng)目目錄。如下:
|— src 源代碼
|— module 模塊相關(guān)
|—module-collection.js
|—module.js
|— plugins
|— devtool.js
|— logger.js
|— helpers.js 輔助函數(shù)
|— index.esm.js 入口文件酸钦,使用ES Module
|— index.js 入口文件
|— mixin.js 混淆
|— store.js 核心模塊
|— util.js 工具函數(shù)
二怪得、入口
接著,我們從入口文件 index.js 開始卑硫,逐級的去了解 Vuex 的構(gòu)建徒恋。
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 這里導(dǎo)出了一個 object欢伏,之中包含了核心模塊Store入挣,裝載模塊方法install,版本version硝拧,輔助函數(shù)mapState径筏、mapMutations、mapGetters障陶、mapActions滋恬,命名空間輔助函數(shù)createNamespacedHelpers。
三抱究、注入安裝
我們的都知道恢氯,Vue.js提供了Vue.use方法用來給Vue.js安裝插件,通過調(diào)用插件內(nèi)部的 install 方法來安裝的(如果插件是對象)。
我們來看這里 install 的實(shí)現(xiàn)勋拟。
function install (_Vue) {
if (Vue && _Vue === Vue) { // 檢測 Vue 是否存在遏暴,且和已安裝的是否相同,防止重復(fù)安裝
if (process.env.NODE_ENV !== 'production') { // 判斷是開發(fā)環(huán)境指黎,輸出日志到控制臺
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue // 保存當(dāng)前 Vue
applyMixin(Vue) // 調(diào)用 applyMixin 將 Vuex 混入 Vue
}
讓我們再來看下 applyMixin朋凉,其實(shí)在mixin.js 中只實(shí)現(xiàn)了一個混淆,就是將 vuex 混淆注入 vue醋安。
export default function (Vue) {
const version = Number(Vue.version.split('.')[0]) // 獲取 vue 的版本號首位
if (version >= 2) {
// 如果 vue 版本2.0以上杂彭,調(diào)用vue.mixin,在 beforeCreate 的時(shí)候執(zhí)行 vuexInit 注入 vuex
Vue.mixin({ beforeCreate: vuexInit })
} else {
// 如果 vue 1.x 的版本吓揪,將 vuexInit 放入 vue 的 _init 方法中
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
function vuexInit () {
const options = this.$options
// 獲取實(shí)例上的 store
if (options.store) {
// 如果 store 是個函數(shù)亲怠,執(zhí)行它獲得 store
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 當(dāng)前組件上沒有 store,就從父組件上獲取 store
this.$store = options.parent.$store
}
}
}
這里對于 vue 做了區(qū)分柠辞,在 vue 2 以上的版本調(diào)用 Vue.mixin 直接混入到 beforeCreate 鉤子中团秽,1.0 放入 _init 方法中。
在 vuexInit 方法中叭首,獲取當(dāng)前組件 store习勤,如果不存在再從父組件獲取,保證了 store 的一致性焙格,同時(shí)把 _this.$store 指向跟 store 實(shí)例图毕。
四、Store
(1)構(gòu)造函數(shù)
constructor (options = {}) {
// 判斷注入vuex
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
// 非生產(chǎn)環(huán)境輸出日志
if (process.env.NODE_ENV !== 'production') {
// 判斷 Vuex 是否已混入 Vue
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
// 判斷是否支持 Promise
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
// 判斷是否需要使用 new 關(guān)鍵字來創(chuàng)建 Store 實(shí)例
assert(this instanceof Store, `store must be called with the new operator.`)
}
const {
plugins = [], // Vuex 的插件方法眷唉,接收 Store 作為唯一參數(shù)
strict = false // 嚴(yán)格模式予颤,在嚴(yán)格模式下,通過提交 mutation 之外的方法改變 state 都會報(bào)錯
} = options
this._committing = false // 是否正在commit
this._actions = Object.create(null) // actions
this._actionSubscribers = [] // actions 訂閱者
this._mutations = Object.create(null) // mutations
this._wrappedGetters = Object.create(null) // getters
this._modules = new ModuleCollection(options) // modules收集器
this._modulesNamespaceMap = Object.create(null) // 根據(jù)命名空間收集modules
this._subscribers = [] // 訂閱者
this._watcherVM = new Vue() // 觀察者 Vue 實(shí)例
// 把 commit 和 dispatch 綁定到 store
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)
}
// 嚴(yán)格模式(嚴(yán)格模式下冬阳,state 只能通過提交 mutations 來更改蛤虐,否則會拋出錯誤)
this.strict = strict
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
// 初始化根模塊,遞歸注冊依賴的模塊肝陪,收集所有模塊getters放進(jìn)_wrappedGetters
installModule(this, state, [], this._modules.root)
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
// 初始化store vm驳庭,注冊 getters 和計(jì)算屬性。
resetStoreVM(this, state)
// 調(diào)用插件
plugins.forEach(plugin => plugin(this))
// devtool插件
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
可以看出來见坑,構(gòu)造器函數(shù)主要做了這幾件事情:
1嚷掠、聲明基礎(chǔ)變量
2捏检、創(chuàng)建 modules 收集器 荞驴、調(diào)用 installModule 初始化并且遞歸注冊子模塊
3、調(diào)用 resetStoreVM 來使 Store 具有“響應(yīng)式”
(2)模塊的加工
首先贯城,我們先來看看 ModuleCollection 是怎樣收集保存為 _modules 的熊楼。
export default class ModuleCollection {
constructor (rawRootModule) {
// 注冊模塊
this.register([], rawRootModule, false)
}
// 根據(jù) path 來獲取模塊
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
// 根據(jù) path 來獲取命名空間
getNamespace (path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key)
return namespace + (module.namespaced ? key + '/' : '')
}, '')
}
// 更新 modules
update (rawRootModule) {
update([], this.root, rawRootModule)
}
// 注冊方法
register (path, rawModule, runtime = true) {
if (process.env.NODE_ENV !== 'production') {
// 判斷模塊是否符合規(guī)范
assertRawModule(path, rawModule)
}
// 實(shí)例化
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
// 創(chuàng)建根模塊
this.root = newModule
} else {
// 獲取父模塊
const parent = this.get(path.slice(0, -1))
// 把子模塊掛載到父模塊上
parent.addChild(path[path.length - 1], newModule)
}
// 遞歸的注冊關(guān)聯(lián)模塊
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
// 注銷子模塊
unregister (path) {
const parent = this.get(path.slice(0, -1))
const key = path[path.length - 1]
if (!parent.getChild(key).runtime) return
parent.removeChild(key)
}
}
可以看到,ModuleCollection 主要是將傳入的 options 加工為可用的 _modules 模塊,提供了一些方法鲫骗。
主要做了以下四件事:
1犬耻、判斷模塊是否符合規(guī)范
2、封裝模塊执泰,并遞歸的封裝子模塊掛載到父模塊上枕磁,把所有模塊種成一顆_modules樹
3、提供一些模塊可用方法
接著术吝,我們再看看一個 module 是怎樣實(shí)例化的吧计济。
export default class Module {
constructor (rawModule, runtime) {
// 是否正在處理
this.runtime = runtime
// 創(chuàng)建 _children 容器,保存子模塊
this._children = Object.create(null)
// 保存未處理的模塊
this._rawModule = rawModule
// 保存此模塊state
const rawState = rawModule.state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
// 是否有命名空間
get namespaced () {
return !!this._rawModule.namespaced
}
// 添加子模塊
addChild (key, module) {
this._children[key] = module
}
// 移除子模塊
removeChild (key) {
delete this._children[key]
}
// 獲取子模塊
getChild (key) {
return this._children[key]
}
// 更新保存的_rawModule
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 (fn) {
forEachValue(this._children, fn)
}
// 遍歷getters
forEachGetter (fn) {
if (this._rawModule.getters) {
forEachValue(this._rawModule.getters, fn)
}
}
// 遍歷actions
forEachAction (fn) {
if (this._rawModule.actions) {
forEachValue(this._rawModule.actions, fn)
}
}
// 遍歷mutations
forEachMutation (fn) {
if (this._rawModule.mutations) {
forEachValue(this._rawModule.mutations, fn)
}
}
}
主要做了以下幾件事:
1排苍、定義 _rawModule 沦寂、state 等基礎(chǔ)屬性
2、定義 namespaced 屬性來獲取模塊的 namespaced淘衙,判斷是否有命名空間
3传藏、提供模塊的一些基礎(chǔ)方法,添加彤守、刪除毯侦、獲取和更新
4、提供遍歷的工具方法
(3)模塊的注冊
平時(shí)我們使用 mutations具垫,actions 的時(shí)候叫惊,只需向 commit/dispatch 傳入一個字符串和載荷,就可以更改 state做修。
那么霍狰,vuex 是如何做到的呢。這就輪到 installModule 出馬了饰及。
// installModule 是一個私有方法蔗坯,并不在原型上。
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length // 判斷根模塊燎含,path 不存在即根模塊
const namespace = store._modules.getNamespace(path) // 獲取模塊的命名空間
if (module.namespaced) {
// 定義模塊的命名空間映射Map
store._modulesNamespaceMap[namespace] = module
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
// 將子模塊的 state 掛載到父模塊上
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}
// 獲取執(zhí)行上下文
const local = module.context = makeLocalContext(store, namespace, path)
// 注冊 mutaions宾濒,actions,getters
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
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)
})
}
installModule的意義是初始化根模塊然后遞歸的初始化所有模塊屏箍,并且收集模塊樹的所有g(shù)etters绘梦、actions、mutations赴魁、以及state卸奉。
主要做了這些事:
1、根據(jù)命名空間收集 getters颖御、actions榄棵、mutations
2、獲取執(zhí)行上下文來注冊 getters、actions疹鳄、mutations(下面詳說)
3拧略、遞歸的注冊子模塊的 getters、actions瘪弓、mutations
(4)dispatch 和 commit 的實(shí)現(xiàn)
我們都知道垫蛆,dispatch 和 commit 有兩種提交風(fēng)格:
// 以 commit 為例
// 標(biāo)準(zhǔn)風(fēng)格
this.$store.commit('myMutation',{name : 'Shein'});
// 對象風(fēng)格
this.$store.commit({type: 'myMutation',name : 'Shein'});
所以第一步要統(tǒng)一我們的參數(shù)
function unifyObjectStyle (type, payload, options) {
// 判斷我們傳入的第一個參數(shù)是否為對象,
if (isObject(type) && type.type) {
options = payload // options 設(shè)為第二個參數(shù)
payload = type // 載荷設(shè)置為第一個參數(shù)
type = type.type // mutation 為 type 值
}
if (process.env.NODE_ENV !== 'production') {
// mutation 方法名不為 string 時(shí)腺怯,拋出錯誤
assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
}
return { type, payload, options }
}
現(xiàn)在月褥,我們來看看 commit 函數(shù)
commit (_type, _payload, _options) {
// 校正參數(shù)并定義
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
const entry = this._mutations[type] // 獲取 mutation 入口
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => {
// 遍歷入口,執(zhí)行 mutation
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
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'
)
}
}
commit 首先矯正了參數(shù)瓢喉,然后再去通過 this._mutations[type] 獲取命名空間中所有的 mutations 進(jìn)行遍歷宁赤,執(zhí)行 handle。
那么 dispatch 呢栓票?
dispatch (_type, _payload) {
// 同樣是先校正參數(shù)
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
const entry = this._actions[type] // 獲得入口
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
// 訂閱者
this._actionSubscribers
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
const result = entry.length > 1 // 入口大于1决左,使用Promist.all
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
return res
})
}
與 commit 大同小異,不同的是走贪,在多個同名 actions 存在時(shí)候佛猛,內(nèi)部是調(diào)用 Promise.all 并發(fā)執(zhí)行的。
看完了 commit 和 dispatch 是怎么觸發(fā) mutation 和 action 之后坠狡,不禁要問继找,mutation 和 action 是如何執(zhí)行的,內(nèi)部又是如何調(diào)用的呢逃沿?
以 mutation 為例婴渡,再讓我們回頭看看 mutations 是如何注冊的。
module.forEachMutation((mutation, key) => {
// 遍歷并注冊模塊內(nèi)部的 mutaions
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
我們把 registerMutation 方法抓出來看一下
function registerMutation (store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler (payload) {
handler.call(store, local.state, payload)
})
}
首先通過 type凯亮,獲取到了所有同名方法的入口(沒有的話會新建一個入口)边臼,再將執(zhí)行方法 handler push 到入口中,這里使用了 call 方法假消,把 this 指向到 store. mutation 接收兩個參數(shù)柠并,分別是 state 和 payload。
我們再來看下 action 的注冊富拗。
function registerAction (store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload, cb) {
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)
if (!isPromise(res)) {
res = Promise.resolve(res) // 非 promise 轉(zhuǎn)為 promise
}
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
和 commit 不同臼予,action 的第一個參數(shù)會傳入 context ,所以在 registerAction 中會通過 local 把dispatch 等方法傳進(jìn)來啃沪。其次粘拾,如果 actions 不是 Promise,會強(qiáng)制轉(zhuǎn)為 promise谅阿,是為了多個同名 action 并發(fā)的時(shí)候半哟,使用 Promise.all() 來處理。
那么問題來了签餐,local 有什么用寓涨?是怎么定義的呢?
把視線拉回 local 定義的地方氯檐。
const local = module.context = makeLocalContext(store, namespace, path)
…………
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
// 不啟用命名空間戒良,走向 store 的dispatch,啟用命名空間冠摄,走向模塊內(nèi)部 dispatch糯崎。
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
// 判斷 options.root 不存在,為分發(fā)的 action 加上命名空間
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 和 dispatch 同理
commit: noNamespace ? store.commit : (_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._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河泳,存在命名空間則通過 makeLocalGetters 代理到模塊內(nèi)部的 getters
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
// 獲取到模塊內(nèi)部 state
get: () => getNestedState(store.state, path)
}
})
return local
}
local 里會判斷是否有命名空間沃呢,有的話,返回模塊內(nèi)部的 dispatch拆挥、commit 和 getters薄霜,state 固定返回模塊的 state。
我們再來看看 makeLocalGetters 怎么獲取局部 getters纸兔。
function makeLocalGetters (store, namespace) {
// 定義代理對象
const gettersProxy = {}
// 定義切割點(diǎn)
const splitPos = namespace.length
Object.keys(store.getters).forEach(type => {
// skip if the target getter is not match this namespace
// 跳過不匹配的getter
if (type.slice(0, splitPos) !== namespace) return
// 獲取局部 getter
const localType = type.slice(splitPos)
// 定義代理對象 localType
Object.defineProperty(gettersProxy, localType, {
get: () => store.getters[type],
enumerable: true
})
})
// 返回代理對象
return gettersProxy
}
創(chuàng)建局部的getters就是一個代理的過程惰瓜,在使用模塊內(nèi)使用(沒有加上命名空間的)getters的名字,會被代理到汉矿,store實(shí)例上那個真正的(全名的)getters崎坊。
最后,來看一下 getNestedState
function getNestedState (state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}
比較簡單了洲拇,通過 path 獲取當(dāng)前模塊的 state奈揍。
local 到此已經(jīng)定義完畢,那么它是用來做什么的呢赋续。
- 注冊 mutations
function registerMutation (store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler (payload) {
handler.call(store, local.state, payload)
})
}
在注冊 mutation 的時(shí)候打月,我們傳入的是模塊內(nèi)部的state。
2.注冊 actions
function registerAction (store, type, handler, local) {
……
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)
……
}
action 中傳入的 context蚕捉,都會被代理到模塊本身的 action 上奏篙。
- 注冊getters
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) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}
在 getters 中,我們可以任意的組裝數(shù)據(jù)迫淹,既可以獲取局部的 state 秘通、getters ,也可以獲取全局的敛熬。
到此肺稀,模塊相關(guān)的已經(jīng)告一段落。接下來我們來看下 Vuex 怎么依賴Vue核心實(shí)現(xiàn)數(shù)據(jù)的“響應(yīng)式化”应民。
(5)響應(yīng)式更新數(shù)據(jù) resetStoreVM
function resetStoreVM (store, state, hot) {
// 保存舊實(shí)例
const oldVm = store._vm
// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
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 enviroment.
// 遍歷 wrappedGetters 话原,為每個 getters 設(shè)置 get夕吻,映射到 store.vm[key]
computed[key] = partial(fn, store)
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
/* Vue.config.silent暫時(shí)設(shè)置為true的目的是在new一個Vue實(shí)例的過程中不會報(bào)出一切警告 */
const silent = Vue.config.silent
Vue.config.silent = true
/* 這里new了一個Vue對象,運(yùn)用Vue內(nèi)部的響應(yīng)式實(shí)現(xiàn)注冊state以及computed*/
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
/* 使能嚴(yán)格模式繁仁,保證修改store只能通過mutation */
if (store.strict) {
enableStrictMode(store)
}
if (oldVm) {
/* 解除舊vm的state的引用涉馅,以及銷毀舊的Vue對象 */
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())
}
}
resetStoreVM首先會遍歷wrappedGetters,使用Object.defineProperty方法為每一個getter綁定上get方法黄虱,這樣我們就可以在組件里訪問this.$store.getter.test就等同于訪問store._vm.test稚矿。
之后Vuex采用了new一個Vue對象來實(shí)現(xiàn)數(shù)據(jù)的“響應(yīng)式化”,運(yùn)用Vue.js內(nèi)部提供的數(shù)據(jù)雙向綁定功能來實(shí)現(xiàn)store的數(shù)據(jù)與視圖的同步更新捻浦。
這兩步執(zhí)行完以后晤揣,我們就可以通過this.$store.getter.test訪問vm中的test屬性了。
五朱灿、總結(jié)
Vuex 主要原理構(gòu)建的解析就到此結(jié)束啦昧识。
總的來說,Vuex 的代碼不是很多盗扒。但麻雀雖小滞诺,五臟俱全,里面的設(shè)計(jì)模式還是十分優(yōu)秀环疼,值得我們學(xué)習(xí)的习霹。我總結(jié)幾點(diǎn),在項(xiàng)目中可能會遇到的情況吧(其實(shí)文檔里大都有寫- -):
1炫隶、模塊內(nèi)提交 mutation 和 action淋叶,需要更改root狀態(tài),或者提交root的commit伪阶,傳入options: {root: true}煞檩。
2、沒有聲明嚴(yán)格模式的情況下栅贴,state 可以被直接修改的斟湃,需要注意??,聲明了嚴(yán)格模式后檐薯,任何非提交 mutation 的對 state 都會拋出錯誤凝赛,這能保證所有的狀態(tài)變更都能被調(diào)試工具跟蹤到。
3坛缕、分發(fā) action 依然會存在“競態(tài)”的問題墓猎,需要注意業(yè)務(wù)邏輯的先后,之所以要分發(fā) action 赚楚,是為了每個 mutation 提交后會立即得到一個新的狀態(tài)毙沾。
4、如果不設(shè)置namespaced: true宠页,不同模塊相同的 mutation 可以同時(shí)觸發(fā)左胞,相同模塊同名 mutation 后面的會覆蓋前面的寇仓。actions 同理。
還有烤宙,最好不要用 360 查殺 Vuex 的項(xiàng)目遍烦。會報(bào)毒!C爬谩H橛洹兄淫!
ENDING....