為什么需要Vuex
通常 Vue
項(xiàng)目中的數(shù)據(jù)通信雇毫,我們通過以下三種方式就可以解決,但是隨著項(xiàng)目多層嵌套的組件增加踩蔚,兄弟組件間的狀態(tài)傳遞非常繁瑣棚放,導(dǎo)致不斷的通過事件來變更狀態(tài),同步狀態(tài)多份拷貝寂纪,最后代碼難以維護(hù)席吴。于是尤大大開發(fā)了 Vuex
來解決這個(gè)問題赌结。
- 父?jìng)髯?
props
; - 子傳父
$emit
孝冒; -
eventBus
事件總線柬姚。
當(dāng)然中小 Vue
項(xiàng)目可以不使用 Vuex
,當(dāng)出現(xiàn)下面這兩種情況的時(shí)候我們就應(yīng)該考慮使用 Vuex
統(tǒng)一管理狀態(tài)了庄涡。
- 多個(gè)視圖依賴于同一狀態(tài)量承;
- 來自不同視圖的行為需要變更同一狀態(tài)。
使用Vuex
的優(yōu)點(diǎn)也很明顯:
- 方便全局通信穴店;
- 方便狀態(tài)緩存撕捍;
- 方便通過
vue-devtools
來進(jìn)行狀態(tài)相關(guān)的bug排查。
Vuex初使用
官方 Vuex
上有一張用于解釋 Vuex
的圖泣洞,但是并沒有給于清晰明確的注釋忧风。這里簡(jiǎn)單說下每塊的功能和作用,以及整個(gè)流程圖的單向數(shù)據(jù)量的流向球凰。
Vue Components
:Vue組件狮腿。HTML頁面上,負(fù)責(zé)接收用戶操作等交互行為呕诉,執(zhí)行dispatch
方法觸發(fā)對(duì)應(yīng)action
進(jìn)行回應(yīng)缘厢。dispatch
:操作行為觸發(fā)方法,是唯一能執(zhí)行action的方法甩挫。actions
:操作行為處理模塊贴硫。負(fù)責(zé)處理Vue Components接收到的所有交互行為。包含同步/異步操作伊者,支持多個(gè)同名方法英遭,按照注冊(cè)的順序依次觸發(fā)。向后臺(tái)API請(qǐng)求的操作就在這個(gè)模塊中進(jìn)行亦渗,包括觸發(fā)其他action
以及提交mutation
的操作贪绘。該模塊提供了Promise的封裝,以支持action的鏈?zhǔn)接|發(fā)央碟。commit
:狀態(tài)改變提交操作方法税灌。對(duì)mutation
進(jìn)行提交,是唯一能執(zhí)行mutation的方法亿虽。mutations
:狀態(tài)改變操作方法菱涤。是Vuex修改state的唯一推薦方法,其他修改方式在嚴(yán)格模式下將會(huì)報(bào)錯(cuò)洛勉。該方法只能進(jìn)行同步操作粘秆,且方法名只能全局唯一。操作之中會(huì)有一些hook暴露出來收毫,以進(jìn)行state的監(jiān)控等攻走。state
:頁面狀態(tài)管理容器對(duì)象殷勘。集中存儲(chǔ)Vue components
中data
對(duì)象的零散數(shù)據(jù),全局唯一昔搂,以進(jìn)行統(tǒng)一的狀態(tài)管理玲销。頁面顯示所需的數(shù)據(jù)從該對(duì)象中進(jìn)行讀取,利用Vue的細(xì)粒度數(shù)據(jù)響應(yīng)機(jī)制來進(jìn)行高效的狀態(tài)更新摘符。Vue組件
接收交互行為贤斜,調(diào)用dispatch
方法觸發(fā)action
相關(guān)處理,若頁面狀態(tài)需要改變逛裤,則調(diào)用commit
方法提交mutation
修改state
瘩绒,通過getters
獲取到state
新值,重新渲染Vue Components
带族,界面隨之更新锁荔。
總結(jié):
state
里面就是存放的我們上面所提到的狀態(tài)。mutations
就是存放如何更改狀態(tài)蝙砌。getters
就是從state
中派生出狀態(tài)堕战,比如將state
中的某個(gè)狀態(tài)進(jìn)行過濾然后獲取新的狀態(tài)。actions
就是mutation
的加強(qiáng)版拍霜,它可以通過commit
mutations中的方法來改變狀態(tài),最重要的是它可以進(jìn)行異步操作薪介。modules
顧名思義祠饺,就是當(dāng)用這個(gè)容器來裝這些狀態(tài)還是顯得混亂的時(shí)候,我們就可以把容器分成幾塊汁政,把狀態(tài)和管理規(guī)則分類來裝道偷。這和我們創(chuàng)建js模塊是一個(gè)目的,讓代碼結(jié)構(gòu)更清晰记劈。
關(guān)于Vuex的疑問
我們做的項(xiàng)目中使用Vuex勺鸦,在使用Vuex的過程中留下了一些疑問,發(fā)現(xiàn)在使用層面并不能解答我的疑惑目木。于是將疑問簡(jiǎn)單羅列换途,最近在看了 Vuex
源碼才明白。
- 如何保證
state
的修改只能在mutation
的回調(diào)函數(shù)中刽射? -
mutations
里的方法军拟,為什么可以修改state
? - 為什么可以通過
this.commit
來調(diào)用mutation
函數(shù)誓禁? -
actions
函數(shù)中context對(duì)象
懈息,為什么不是store實(shí)例
本身? - 為什么在
actions函數(shù)
里可以調(diào)用dispatch
或者commit
摹恰? - 通過
this.$store.getters.xx
辫继,是如何可以訪問到getter
函數(shù)的執(zhí)行結(jié)果的怒见?
Vuex源碼分析
針對(duì)以上疑問,在看Vuex源碼的過程中慢慢解惑了姑宽。
1. 如何保證 state
的修改只能在 mutation
的回調(diào)函數(shù)中遣耍?
在Vuex
源碼的 Store
類中有個(gè) _withCommit
函數(shù):
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
Vuex
中所有對(duì) state
的修改都會(huì)調(diào)用 _withCommit
函數(shù)的包裝,保證在同步修改 state 的過程中 this._committing
的值始終為 true
低千。當(dāng)我們檢測(cè)到 state
變化的時(shí)候配阵,如果 this._committing
不為 true
,則能查到這個(gè)狀態(tài)修改有問題示血。
2. mutations里的方法棋傍,為什么可以修改state?
在Vuex
實(shí)例化的時(shí)候难审,會(huì)調(diào)用 Store
瘫拣,Store
會(huì)調(diào)用 installModule
,來對(duì)傳入的配置進(jìn)行模塊的注冊(cè)和安裝告喊。對(duì) mutations
進(jìn)行注冊(cè)和安裝麸拄,調(diào)用了 registerMutation
方法:
/**
* 注冊(cè)mutation 作用同步修改當(dāng)前模塊的 state
* @param {*} store Store實(shí)例
* @param {*} type mutation 的 key
* @param {*} handler mutation 執(zhí)行的函數(shù)
* @param {*} local 當(dāng)前模塊
*/
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)
})
}
該方法對(duì)mutation方法進(jìn)行再次封裝,注意 handler.call(store, local.state, payload)
黔姜,這里改變 mutation
執(zhí)行的函數(shù)的 this
指向?yàn)?Store實(shí)例
拢切,local.state
為當(dāng)前模塊的 state
,payload
為額外參數(shù)秆吵。
因?yàn)楦淖兞?mutation
執(zhí)行的函數(shù)的 this
指向?yàn)?Store實(shí)例
淮椰,就方便對(duì) this.state
進(jìn)行修改。
3. 為什么可以通過 this.commit
來調(diào)用 mutation
函數(shù)纳寂?
在 Vuex 中主穗,mutation 的調(diào)用是通過 store 實(shí)例的 API 接口 commit 來調(diào)用的。來看一下 commit 函數(shù)的定義:
/**
*
* @param {*} _type mutation 的類型
* @param {*} _payload 額外的參數(shù)
* @param {*} _options 一些配置
*/
commit (_type, _payload, _options) {
// check object-style commit
// unifyObjectStyle 方法對(duì) commit 多種形式傳參 進(jìn)行處理
// commit 的載荷形式和對(duì)象形式的底層處理
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// 根據(jù) type 去查找對(duì)應(yīng)的 mutation
const entry = this._mutations[type]
// 沒查到 報(bào)錯(cuò)提示
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// 使用了 this._withCommit 的方法提交 mutation
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 遍歷 this._subscribers毙芜,調(diào)用回調(diào)函數(shù)忽媒,并把 mutation 和當(dāng)前的根 state 作為參數(shù)傳入
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'
)
}
}
this.commmit()
接收mutation的類型和外部參數(shù),在 commmit
的實(shí)現(xiàn)中通過 this._mutations[type]
去匹配到對(duì)應(yīng)的 mutation
函數(shù)腋粥,然后調(diào)用晦雨。
4. actions函數(shù)中context對(duì)象
,為什么不是store實(shí)例本身隘冲?
5. 為什么在actions函數(shù)
里可以調(diào)用 dispatch
或者 commit
金赦?
actions的使用:
actions: {
getTree(context) {
getDepTree().then(res => {
context.commit('updateTree', res.data)
})
}
}
在action的初始化函數(shù)中有這樣一段代碼:
/**
* 注冊(cè)actions
* @param {*} store 全局store
* @param {*} type action 類型
* @param {*} handler action 函數(shù)
* @param {*} local 當(dāng)前的module
*/
function registerAction (store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
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)
if (!isPromise(res)) {
res = Promise.resolve(res)
}
// store._devtoolHook 是在store constructor的時(shí)候執(zhí)行 賦值的
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
很明顯context對(duì)象是指定的,并不是store實(shí)例对嚼, const {dispatch, commit, getters, state, rootGetters,rootState } = context
context對(duì)象上掛載了:
- dispatch, 當(dāng)前模塊上的dispatch函數(shù)
- commit, 當(dāng)前模塊上的commit函數(shù)
- getters, 當(dāng)前模塊上的getters
- state, 當(dāng)前模塊上的state
- rootGetters, 根模塊上的getters
- rootState 根模塊上的state
6. 通過 this.$store.getters.xx
夹抗,是如何可以訪問到getter函數(shù)的執(zhí)行結(jié)果的?
在Vuex源碼的Store實(shí)例的實(shí)現(xiàn)中有這樣一個(gè)方法 resetStoreVM
:
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
Object.keys(wrappedGetters).forEach(key => {
const fn = wrappedGetters[key]
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key]
})
})
// ...
store._vm = new Vue({
data: { state },
computed
})
// ...
}
遍歷 store._wrappedGetters
對(duì)象纵竖,在遍歷過程中拿到每個(gè) getter
的包裝函數(shù)漠烧,并把這個(gè)包裝函數(shù)執(zhí)行的結(jié)果用 computed
臨時(shí)保存杏愤。
然后實(shí)例化了一個(gè) Vue實(shí)例
,把上面的 computed
作為計(jì)算屬性傳入已脓,把 狀態(tài)樹state
作為 data
傳入珊楼,這樣就完成了注冊(cè)。
我們就可以在組件中訪問 this.$store.getters.xxgetter
了度液,相當(dāng)于訪問了 store._vm[xxgetter]
厕宗,也就是在訪問 computed[xxgetter]
,這樣就訪問到 xxgetter
的回調(diào)函數(shù)了堕担。