Vuex原理解析

語言: CN / TW / HK

theme: channing-cyan highlight: night-owl


這是我參與11月更文挑戰的第21天,活動詳情檢視:[2021最後一次更文挑戰](https://juejin.cn/post/7023643374569816095/ "https://juejin.cn/post/7023643374569816095/") > TIP 👉 **休對故人思故國,且將新火試新茶。詩酒趁年華。____蘇軾《望江南·超然臺作》**

前言

## Vuex 原理 #### vuex 核心原理分析 [原始碼地址](https://github.com/vuejs/vuex/tree/dev/src) 我們不直接硬生生的去分析原始碼,現在希望大家拋掉對 `vuex` 的所有記憶,讓我們回到幾年前`vuex`誕生的那個時間點,從頭開始去思考基於 `FLUX` 思想,如何打造一個我們自己的狀態管理工具 a. 站在`FLUX` 的角度去思考 在開發中面臨最多的場景是狀態重複但是不集中 ```vue // a.vue

{{ username }}

// b.vue

{{ username }}

/** * 如果 username 需要在每個元件都獲取一次,是不是很麻煩,雖然可以通過共同的父級傳入,但是不都是這種理想情況 */ ``` 這裡其實就出現了狀態重複的問題,在不同的元件中依賴了同樣的狀態,重複就會導致不對等的風險,基於 `FLUX` 的思想,我們設計的狀態管理將是中心化的工具,也就是集中式儲存管理應用的所有元件的狀態,大白話,就是將所有的狀態放在一個全域性的 `Tree` 結構中,集中放在一起的好處是可以有效避免重複的問題,也更好的管理,將狀態和檢視層解耦 > TJ 原來說過我的狀態管理就是 {},對中心化的管理工具來說,不就是這樣嘛😂 b. 如何去更改狀態 自由即代表了混亂,`FLUX` 推崇以一種可預測的方式發生變化,而且有且唯一一種,這樣的好處是所有的行為可預測,可測試,對於之後做個` dev-tool` 去除錯、時間旅行都很方便,現在的問題就是要去思考同步和非同步的問題了,為了區分的更清楚,我們定義兩種行為,`Actions` 用來處理非同步狀態變更(內部還是呼叫 `Mutations`),`Mutations` 處理同步的狀態變更,整個鏈路應該是一個閉環,單向的,完美契合 `FLUX` 的思想 「頁面 dispatch/commit」-> 「actions/mutations」-> 「狀態變更」-> 「頁面更新」-> 「頁面 dispatch/commit」... c. 如何和 `vue` 整合呢? 外掛呀~這樣可以和 `vue` 整合在一起,通過 `mixin` 將 `$store` 這樣的快速訪問 `store` 的快捷屬性注入到每一個 `vue` 例項中 > [開發 vue 外掛](https://cn.vuejs.org/v2/guide/plugins.html) d. 怎麼讓 `store` 是響應式的呢? 利用 `vue` `data` 裡的狀態是響應式的啊~ e. 開始擼碼 Step1 - store 註冊 ```js /** * store.js - store 註冊 */ let Vue // vue 外掛必須要這個 install 函式 export function install(_Vue) { // 拿到 Vue 的構造器,存起來 Vue = _Vue // 通過 mixin 注入到每一個vue例項 👉 https://cn.vuejs.org/v2/guide/mixins.html Vue.mixin({ beforeCreate: vuexInit }) function vuexInit () { const options = this.$options // 這樣就可以通過 this.$store 訪問到 Vuex 例項,拿到 store 了 if (options.store) { this.$store = typeof options.store === 'function' ? options.store() : options.store } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store } } } ``` Step2 - 響應式 ```js /** * store.js - 實現響應式 */ export class Store { constructor(options = {}) { resetStoreVM(this, options.state) } get state () { return this._vm._data.$$state } } function resetStoreVM(store, state) { // 因為 vue 例項的 data 是響應式的,正好利用這一點,就可以實現 state 的響應式 store._vm = new Vue({ data: { $$state: state } }) } ``` Step3 - 衍生資料 ```js /** * store.js - 衍生資料(getters) */ export class Store { constructor(options = {}) { const state = options.state resetStoreVM(this, state) // 我們用 getters 來收集衍生資料 computed this.getters = {} // 簡單處理一下,衍生不就是計算一下嘛,傳人 state _.forEach(this.getters, (name, getterFn) => { Object.defineProperty(this.getters, name, { get: () => getterFn(this.state) }) }) } get state () { return this._vm._data.$$state } } function resetStoreVM(store, state) { store._vm = new Vue({ data: { $$state: state } }) } ``` Step4 - Actions/Mutations ```js /** * store.js - Actions/Mutations 行為改變資料 */ export class Store { constructor(options = {}) { const state = options.state resetStoreVM(this, state) this.getters = {} _.forEach(options.getters, (name, getterFn) => { Object.defineProperty(this.getters, name, { get: () => getterFn(this.state) }) }) // 定義的行為,分別對應非同步和同步行為處理 this.actions = {} this.mutations = {} _.forEach(options.mutations, (name, mutation) => { this.mutations[name] = payload => { // 最終執行的就是 this._vm_data.$$state.xxx = xxx 這種操作 mutation(this.state, payload) } }) _.forEach(options.actions, (name, action) => { this.actions[name] = payload => { // action 專注於處理非同步,這裡傳入 this,這樣就可以在非同步裡面通過 commit 觸發 mutation 同步資料變化了 action(this, payload) } }) } // 觸發 mutation 的方式固定是 commit commit(type, payload) { this.mutations[type](payload) } // 觸發 action 的方式固定是 dispatch dispatch(type, payload) { this.actions[type](payload) } get state () { return this._vm._data.$$state } } function resetStoreVM(store, state) { store._vm = new Vue({ data: { $$state: state } }) } ``` Step5 - 分形,拆分出多個 Module ```js // module 可以對狀態模型進行分層,每個 module 又含有自己的 state、getters、actions 等 // 定義一個 module 基類 class Module { constructor(rawModule) { this.state = rawModule || {} this._rawModule = rawModule this._children = {} } getChild (key) { return this._children[key] } addChild (key, module) { this._children[key] = module } } // module-collection.js 把 module 收集起來 class ModuleCollection { constructor(options = {}) { this.register([], options) } register(path, rawModule) { const newModule = new Module(rawModule) if (path.length === 0 ) { // 如果是根模組 將這個模組掛在到根例項上 this.root = newModule } else { const parent = path.slice(0, -1).reduce((module, key) => { return module.getChild(key) }, this.root) parent.addChild(path[path.length - 1], newModule) } // 如果有 modules,開始遞迴註冊一波 if (rawModule.modules) { _.forEach(rawModule.modules, (key, rawChildModule) => { this.register(path.concat(key), rawChildModule) }) } } } // store.js 中 export class Store { constructor(options = {}) { // 其餘程式碼... // 所有的 modules 註冊進來 this._modules = new ModuleCollection(options) // 但是這些 modules 中的 actions, mutations, getters 都沒有註冊,所以我們原來的方法要重新寫一下 // 遞迴的去註冊一下就行了,這裡抽離一個方法出來實現 installModule(this, this.state, [], this._modules.root); } } function installModule(store, state, path, root) { // getters const getters = root._rawModule.getters if (getters) { _.forEach(getters, (name, getterFn) => { Object.defineProperty(store.getters, name, { get: () => getterFn(root.state) }) }) } // mutations const mutations = root._rawModule.mutations if (mutations) { _.forEach(mutations, (name, mutation) => { let _mutations = store.mutations[name] || (store.mutations[name] = []) _mutations.push(payload => { mutation(root.state, payload) }) store.mutations[name] = _mutations }) } // actions const actions = root._rawModule.actions if (actions) { _.forEach(actions, (name, action) => { let _actions = store.actions[name] || (store.actions[name] = []) _actions.push(payload => { action(store, payload) }) store.actions[name] = _actions }) } // 遞迴 _.forEach(root._children, (name, childModule) => { installModule(this, this.state, path.concat(name), childModule) }) } ``` Step6 - 外掛機制 ```js (options.plugins || []).forEach(plugin => plugin(this)) ``` 以上只是以最簡化的程式碼實現了 `vuex` 核心的 `state` `module` `actions` `mutations` `getters` 機制。 「歡迎在評論區討論」 #### 希望看完的朋友可以給個贊,鼓勵一下