一般 Vue
項目中的數據通訊,咱們經過如下三種方式就能夠解決,可是隨着項目多層嵌套的組件增長,兄弟組件間的狀態傳遞很是繁瑣,致使不斷的經過事件來變動狀態,同步狀態多份拷貝,最後代碼難以維護。因而尤大大開發了 Vuex
來解決這個問題。javascript
props
;$emit
;eventBus
事件總線。固然中小 Vue
項目能夠不使用 Vuex
,當出現下面這兩種狀況的時候咱們就應該考慮使用 Vuex
統一管理狀態了。html
使用Vuex
的優勢也很明顯:vue
vue-devtools
來進行狀態相關的bug排查。官方 Vuex
上有一張用於解釋 Vuex
的圖,可是並無給於清晰明確的註釋。這裏簡單說下每塊的功能和做用,以及整個流程圖的單向數據量的流向。java
Vue Components
:Vue組件。HTML頁面上,負責接收用戶操做等交互行爲,執行 dispatch
方法觸發對應 action
進行迴應。vuex
dispatch
:操做行爲觸發方法,是惟一能執行action的方法。緩存
actions
:操做行爲處理模塊。負責處理Vue Components接收到的全部交互行爲。包含同步/異步操做,支持多個同名方法,按照註冊的順序依次觸發。向後臺API請求的操做就在這個模塊中進行,包括觸發其餘 action
以及提交 mutation
的操做。該模塊提供了Promise的封裝,以支持action的鏈式觸發。架構
commit
:狀態改變提交操做方法。對 mutation
進行提交,是惟一能執行mutation的方法。app
mutations
:狀態改變操做方法。是Vuex修改state的惟一推薦方法,其餘修改方式在嚴格模式下將會報錯。該方法只能進行同步操做,且方法名只能全局惟一。操做之中會有一些hook暴露出來,以進行state的監控等。異步
state
:頁面狀態管理容器對象。集中存儲 Vue components
中 data
對象的零散數據,全局惟一,以進行統一的狀態管理。頁面顯示所需的數據從該對象中進行讀取,利用Vue的細粒度數據響應機制來進行高效的狀態更新。函數
Vue組件
接收交互行爲,調用 dispatch
方法觸發 action
相關處理,若頁面狀態須要改變,則調用 commit
方法提交 mutation
修改 state
,經過 getters
獲取到 state
新值,從新渲染 Vue Components
,界面隨之更新。
總結:
state
裏面就是存放的咱們上面所提到的狀態。
mutations
就是存放如何更改狀態。
getters
就是從 state
中派生出狀態,好比將 state
中的某個狀態進行過濾而後獲取新的狀態。
actions
就是 mutation
的增強版,它能夠經過 commit
mutations中的方法來改變狀態,最重要的是它能夠進行異步操做。
modules
顧名思義,就是當用這個容器來裝這些狀態仍是顯得混亂的時候,咱們就能夠把容器分紅幾塊,把狀態和管理規則分類來裝。這和咱們建立js模塊是一個目的,讓代碼結構更清晰。
咱們作的項目中使用Vuex,在使用Vuex的過程當中留下了一些疑問,發如今使用層面並不能解答個人疑惑。因而將疑問簡單羅列,最近在看了 Vuex
源碼才明白。
state
的修改只能在 mutation
的回調函數中?mutations
裏的方法,爲何能夠修改 state
?this.commit
來調用 mutation
函數?actions
函數中context對象
,爲何不是 store實例
自己?actions函數
裏能夠調用 dispatch
或者 commit
?this.$store.getters.xx
,是如何能夠訪問到 getter
函數的執行結果的?針對以上疑問,在看Vuex源碼的過程當中慢慢解惑了。
state
的修改只能在 mutation
的回調函數中?在Vuex
源碼的 Store
類中有個 _withCommit
函數:
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
複製代碼
Vuex
中全部對 state
的修改都會調用 _withCommit
函數的包裝,保證在同步修改 state 的過程當中 this._committing
的值始終爲 true
。當咱們檢測到 state
變化的時候,若是 this._committing
不爲 true
,則能查到這個狀態修改有問題。
在Vuex
實例化的時候,會調用 Store
,Store
會調用 installModule
,來對傳入的配置進行模塊的註冊和安裝。對 mutations
進行註冊和安裝,調用了 registerMutation
方法:
/** * 註冊mutation 做用同步修改當前模塊的 state * @param {*} store Store實例 * @param {*} type mutation 的 key * @param {*} handler mutation 執行的函數 * @param {*} local 當前模塊 */
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方法進行再次封裝,注意 handler.call(store, local.state, payload)
,這裏改變 mutation
執行的函數的 this
指向爲 Store實例
,local.state
爲當前模塊的 state
,payload
爲額外參數。
由於改變了 mutation
執行的函數的 this
指向爲 Store實例
,就方便對 this.state
進行修改。
this.commit
來調用 mutation
函數?在 Vuex 中,mutation 的調用是經過 store 實例的 API 接口 commit 來調用的。來看一下 commit 函數的定義:
/** * * @param {*} _type mutation 的類型 * @param {*} _payload 額外的參數 * @param {*} _options 一些配置 */
commit (_type, _payload, _options) {
// check object-style commit
// unifyObjectStyle 方法對 commit 多種形式傳參 進行處理
// commit 的載荷形式和對象形式的底層處理
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// 根據 type 去查找對應的 mutation
const entry = this._mutations[type]
// 沒查到 報錯提示
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,調用回調函數,並把 mutation 和當前的根 state 做爲參數傳入
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的類型和外部參數,在 commmit
的實現中經過 this._mutations[type]
去匹配到對應的 mutation
函數,而後調用。
context對象
,爲何不是store實例自己?actions函數
裏能夠調用 dispatch
或者 commit
?actions的使用:
actions: {
getTree(context) {
getDepTree().then(res => {
context.commit('updateTree', res.data)
})
}
}
複製代碼
在action的初始化函數中有這樣一段代碼:
/** * 註冊actions * @param {*} store 全局store * @param {*} type action 類型 * @param {*} handler action 函數 * @param {*} local 當前的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的時候執行 賦值的
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
複製代碼
很明顯context對象是指定的,並非store實例, const {dispatch, commit, getters, state, rootGetters,rootState } = context
context對象上掛載了:
this.$store.getters.xx
,是如何能夠訪問到getter函數的執行結果的?在Vuex源碼的Store實例的實現中有這樣一個方法 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
對象,在遍歷過程當中拿到每一個 getter
的包裝函數,並把這個包裝函數執行的結果用 computed
臨時保存。
而後實例化了一個 Vue實例
,把上面的 computed
做爲計算屬性傳入,把 狀態樹state
做爲 data
傳入,這樣就完成了註冊。
咱們就能夠在組件中訪問 this.$store.getters.xxgetter
了,至關於訪問了 store._vm[xxgetter]
,也就是在訪問 computed[xxgetter]
,這樣就訪問到 xxgetter
的回調函數了。