衆所周知,Vuex 是 Flux 架構的一種實現。Flux 清晰確立了數據管理場景下各類職能單位,其主要準則有:javascript
突變
單元進行變動Vuex 也是牢牢圍繞這些準則開發的,經過 store
類提供 Flux 模式的核心功能。在知足架構的基本要求以外,則進一步設計了許多便利的措施:html
Vue.$watch
方法,實現數據流網上已經有不少解析的文章,不必贅述。本文僅就 中心化、信號機制、數據流 三個點的實現上展開,討論一下 Vuex 實現上的缺陷。vue
在Vuex中,store
整合了全部功能,是對外提供的主要接口,也是Flux模式下的數據管理中心。經過它,Vuex 主要對外提供了:java
dispatch、commit
subscribe
replaceState
registerModule、unregisterModule
hotUpdate
官方實現的 store 很是複雜,耦合了許多邏輯。簡便起見,咱們刨除各類旁路邏輯,只關注Flux架構的中心化
、信號控制
機制,能夠總結出一份很是簡單的實現:webpack
export default class Store { constructor(options) { this._state = options.state; this._mutations = options.mutations; } get state() { return this._state; } commit(type, payload) { this._mutations[type].apply(this, [this.state].concat([...payload])); } }
這是理解 Vuex 的核心,整份代碼只有兩個邏輯:git
_state
屬性實現中心化、自包含數據中心層。dispatch
方法,回調觸發事先註冊的_mutations
方法。這份代碼有不少問題,舉例來講:github
這些設計問題,在Vuex中一樣存在,這與Vue.$watch
機制有很是密切的關係(見下文),我的認爲這是極其不嚴謹的。web
Vuex 提供了兩個與信號有關的接口,其源碼可簡略爲:vuex
export default class Store { ... commit (_type, _payload, _options) { ... const entry = this._mutations[type] this._withCommit(() => { entry.forEach(function commitIterator (handler) { handler(payload) }) }) this._subscribers.forEach(sub => sub(mutation, this.state)) ... } dispatch (_type, _payload) { ... const entry = this._actions[type] return entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) } ... }
二者之間的不一樣在於:架構
dispatch
觸發的是 action
回調;commit
觸發的 mutation
回調。dispatch
返回 Promise;commit
無返回值。這樣的設計意圖,主要仍是職責分離,action 單元用於描述 發生了什麼;mutation用於修改數據層狀態state。Vuex 用類似的接口,將二者放置在相同的地位上,這一層接口設計其實存在弊病:
commit
mutationimmutable
的,並且在 action 中容許修改 state
雖然確實提高了便利性,但對初學者而言,可能致使以下反模式:
因爲沒有確切有效的機制防止錯誤,在使用Vuex的過程當中,須要很是很是警戒;須要嚴謹正確地使用各類職能單元;或者以規範填補設計上的缺陷。
這裏的數據流是指從 Vuex 的 state 到 Vue 組件的props/computed/data
等狀態單元的映射,即如何在組件中獲取state。Vuex 官方推薦使用 mapGetter、mapState 接口實現數據綁定。
該函數很是簡單,代碼邏輯可梳理爲:
export const mapState = normalizeNamespace((namespace, states) => { const res = {} ... normalizeMap(states).forEach(({ key, val }) => { res[key] = function mappedState() { ... return typeof val === 'function' ? val.call(this, state, getters) : state[val] } }) ... return res })
mapState 直接讀取 state 對象的屬性。值得注意的一點是,res[key]
通常做爲函數掛載在外部對象,此時函數的this
指向掛載的 Vue 組件。
該函數一樣很是簡單,其代碼邏輯爲:
export const mapGetters = normalizeNamespace((namespace, getters) => { const res = {} normalizeMap(getters).forEach(({ key, val }) => { res[key] = function mappedGetter() { ... return this.$store.getters[val] } ... }) return res })
mapGetter 訪問的則是組件掛載是 $store
實例的 getters 屬性。
Vuex 的 getter屬性 與 Vue 的computed屬性在各方面的特性都很是類似,實際上,getter 正是基於 computed 實現的。其核心邏輯有:
function resetStoreVM(store, state, hot) { ... store.getters = {} const wrappedGetters = store._wrappedGetters const computed = {} // 遍歷 getter 配置,生成 computed 屬性 forEachValue(wrappedGetters, (fn, key) => { computed[key] = () => fn(store) Object.defineProperty(store.getters, key, { // 獲取 vue 實例屬性 get: () => store._vm[key], enumerable: true // for local getters }) }) // 新建 Vue 實例,專門用於監聽屬性變動 store._vm = new Vue({ data: { $$state: state }, computed }) ... }
從代碼能夠看出,Vuex 將整個 state 對象託管到vue實例的data屬性中,以此換取Vue的整個 watch
機制。而getter屬性正是經過返回實例的 computed 屬性實現的,這種實現方式,不可謂不精妙。問題則是:
watch
機制是基於屬性讀寫函數實現的,若是直接替換根節點,會致使各類子屬性回調失效,即不可能實現immutable
特性Vuex 給我最大的感受是:便利,一樣的功能有各類不一樣語義的邏輯單元處理,職責分離方面作的很是好,若是嚴格遵循規範的話,確實能很是好的組織代碼;接口也很簡明易懂,對開發者很是友好。從用戶數量、影響力等方面來看,無疑是一個很是偉大的框架。這裏提出來的一些觀點固然也是見仁見智的,目的不外乎拋磚引玉而已。