Vuex原理實現

Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

思考問題

  1. Vuex 只在更實例引入了,那麼它是如何在每個子組件中都能使用的?
  2. Vuex 是一個插件,為什麼更改了Vuex中的state,會觸發視圖的跟新?

vuex原理

vuex 是vue的狀態管理工具,目的是為了更方便實現多個組件之間的狀態共享

vuex的工作原理, Vuex 官方文檔 vuex

vuex實現

  1. 新建一個vuex文件,導出一個store對象

    let Vue;
    class Store {
    
    }
    const install = (_Vue) => {
      Vue = _Vue;
    }
    
    export default {
    // 這方法在use的時候默認會被調用
      install,
      Store
    }

     

  2. 使用混合,在創建組件之前,將vuex實例掛載到vue的實例上

    Vue.mixin({
    beforeCreate() {
      // 需要拿到store,給每個組件都增加 $store 屬性
      // 為什麼不直接給Vue.prototype 上增加?是因為可能會new 好多個Vue的實例,在別的實例上不需要store
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store;
      } else {
        // 這裡判斷一下,如果單獨創建了一個實例沒有parent
        this.$store = this.$parent && this.$parent.$store;
      }
    }
    });

     

  3. 獲取new Store傳入的對象

    class Store {
      constructor(options = {}) {
        // 將用戶的狀態放入到 store 中
        this.state = options.state;
    
        // 獲取計算屬性
        let getters = options.getters;
        this.getters = {};
        Object.keys(getters).forEach(getterName => {
          Object.defineProperty(this.getters, getterName, {
            get: () => {
              return getters[getterName](this.state);
            }
          });
        });
      }
    }
  4. state的數據變成響應式的數據

    class Store {
      constructor(options = {}) {
        // Vuex 的核心,定義了響應式變化,數據更新之後更新視圖
        this._vm = new Vue({
          data() {
            return {
              state: options.state
            };
          }
        });
      }
      // 類的屬性訪問器
      get state() {
        return this._vm.state;
      }
    }
  5. 通過觸發 mutations 更改狀態

    // 通過 this.commit() 觸發更改
    mutations: {
      syncAdd(state, payload) {
        state.age += payload;
      }
    }
    
    // 通過發佈訂閱的模式
    class Store {
      constructor(options = {}) {
        let mutations = options.mutations;
        this.mutations = {};
        Object.keys(mutations).forEach(mutationName => {
          // 訂閱所有的mutations
          this.mutations[mutationName] = (payload) => {
            // 內部的第一個參數是狀態
            mutations[mutationName](this.state, payload);
          }
        });
      }
      // 提交更改,會在當前的 store 上找到對應的函數執行
      // 發佈
      commit = (mutationName, payload) => { // 保證this
        this.mutations[mutationName](payload);
      }
    }
  6. 內部封裝的 forEach, 減少重複代碼

    const forEachValue = (obj, fn) => {
      Object.keys(obj).forEach(key => {
        fn(key, obj[key]);
      });
    };
    
    // 對上面的 getters 改造下
    forEachValue(getters, (gettersName, fn) => {
      Object.defineProperty(this.getters, getterName, {
        get: () => {
          return fn(this.state);
        }
      });
    });
    
    // 對上面的mutations 改造下
    forEachValue(mutations, (mutationName, fn) => {
      this.mutations[mutationName] = (payload) => {
        fn(this.state, payload);
      }
    })
  7. 通過觸發 action 異步跟新轉態

    action 異步提交更改,異步操作完之後提交到mutation中
    例:
    actions: {
      asyncMinus({ commit }, payload) {
        setTimeout(() => {
          commit('syncMinus', payload);
        }, 1000);
      }
    }
    mutations: {
      syncMinus(state, payload) {
        state.age -= payload;
      }
    }
    
    // 也是一個發佈訂閱模式
    class Store {
      constructor(options ={}) {
        let actions = options.actions;
        this.actions = {};
        forEachValue(actions, (actionName, fn) => {
          this.actions[actionName] = (payload) => {
            fn(this, payload);
          }
        });
      }
      dispatch = (actionName, payload) => {
        // 源碼里有一個變量,來控制是否是通過mutation 來更新的轉態,不是會拋個警告
        this.actions[actionName](payload);
      }
    } 

    vuex簡單實現

    let Vue;
    const forEachValue = (obj = {}, fn) => {
      return Object.keys(obj || {}).forEach(key => {
        fn(key, obj[key]);
      })
    }
    class Store {
      constructor(options = {}) {
        this._vm = new Vue({
          data() {
            return {
              state: options.state
            }
          }
        });
    
        let getters = options.getters;
        this.getters = {};
        forEachValue(getters, (getterName, fn) => {
          Object.defineProperty(this.getters, getterName, {
            get: () => {
              return fn(this.state);
            }
          });
        });
    
        // mutations
        let mutations = options.mutations;
        this._mutations = {};
        // 訂閱
        forEachValue(mutations, (mutationName, fn) => {
          this._mutations[mutationName] = (paylod) => {
            fn(this.state, paylod);
          }
        });
    
        // actions
        let actions = options.actions;
        this._actions = {};
        forEachValue(actions, (actionName, fn) => {
          this._actions[actionName] = (paylod) => {
            fn(this, paylod);
          }
        });
      }
      // 發佈
      commit = (mutationName, paylod) => {
        this._mutations[mutationName](paylod);
      }
      dispatch = (actionName, paylod) => {
        this._actions[actionName](paylod);
      }
      get state() {
        return this._vm.state;
      }
    }
    const install = (_Vue) => {
      Vue = _Vue;
      Vue.mixin({
        beforeCreate() {
          if (this.$options && this.$options.store) {
            this.$store = this.$options.store;
          } else {
            this.$store = this.$parent && this.$parent.$store;
          }
        }
      });
    }
    
    export default  {
      install,
      Store
    };

     

  8. modules的實現

    主要是將mosules裏面的數據格式化成我們想要的格式 js { _modules: { root: state: {__ob__: Observer} _children: {} _rawModule: {modules: {…}, state: {…}, getters: {…}, mutations: {…}, actions: {…} } }

    • 數據格式化
    // 在 Store 中定義modules
    this._modules = new ModuleCollection(options); // 把數據格式化成我們想要的結構
    
    class ModuleCollection {
      constructor(options) { // 模塊依賴的收集
        this.register([], options); // 註冊模塊,將模塊註冊成樹結構
      }
      register(path, rootModule) {
        let module = { // 將模塊格式化
          _rawModule: rootModule,
          _children: {},
          state: rootModule.state
        }
        if (path.length === 0) { // 如何是根模塊 將這個模塊掛載到根實例上
          this.root = module;
        } else {
          // 遞歸都用reduce方法, 通過_children 屬性進行查找
          let parent = path.slice(0, -1).reduce((root, current) => {
            return root._children[current];
          }, this.root);
          parent._children[path[path.length - 1]] = module;
        }
        // 看當前模塊是否有modules
        if (rootModule.modules) { // 如果有modules 開始重新註冊
          forEachValue(rootModule.modules, (moduleName, module) => {
            this.register(path.concat(moduleName), module);
          })
        }
      }
    }
    • 安裝模塊
    installModule(this, this.state, [], this._modules.root);
    
      // 安裝模塊
      const installModule = (store, rootState, path, rootModule) => {
        // 將state 掛載到根上
        if (path.length > 0) {
          let parent = path.slice(0, -1).reduce((root, current) => {
            return root[current];
          }, rootState);
          // vue 不能再對象上增加不存在的屬性,否則視圖不會更新
          // parent.path[path.length - 1] = rootModule.state;
          Vue.set(parent, path[path.length - 1], rootModule.state);
        }
        // getters
        let getters = rootModule._rawModule.getters;
        if (getters) {
          forEachValue(getters, (getterName, fn) => {
            Object.defineProperty(store.getters, getterName, {
              get() {
                // 讓getters 執行,將自己的狀態傳入
                return fn(rootModule.state); // 將對應的函數執行
              }
            });
          });
        }
        // mutations
        let mutations = rootModule._rawModule.mutations; // 拿到每個模塊里的mutations
        if (mutations) {
          forEachValue(mutations, (mutationName, fn) => {
            let mutations = store._mutations[mutationName] || [];
            mutations.push((paylod) => {
              fn.call(store, rootModule.state, paylod);
              // 發佈,讓所有的訂閱依次執行
              store._subscribes.forEach(fn => fn({ type: mutationName, paylod }, rootState))
            });
            store._mutations[mutationName] = mutations;
          });
        }
        // actions
        let actions = rootModule._rawModule.actions; // 拿到每個模塊里的mutations
        if (actions) {
          forEachValue(actions, (actionName, fn) => {
            let actions = store._actions[actionName] || [];
            actions.push((paylod) => {
              fn.call(store, store, paylod);
            });
            store._actions[actionName] = actions;
          });
        }
        // 循環掛載兒子
        forEachValue(rootModule._children, (moduleName, module) => {
          installModule(store, rootState, path.concat(moduleName), module);
        });
      }

     

    vuex完整實現

    let Vue;
    const forEachValue = (obj = {}, fn) => {
      return Object.keys(obj || {}).forEach(key => {
        fn(key, obj[key]);
      });
    }
    class ModuleCollection {
      constructor(options) { // 模塊依賴的收集
        this.register([], options); // 註冊模塊,將模塊註冊成樹結構
      }
      register(path, rootModule) {
        let module = { // 將模塊格式化
          _rawModule: rootModule,
          _children: {},
          state: rootModule.state
        }
        if (path.length === 0) { // 如何是根模塊 將這個模塊掛載到根實例上
          this.root = module;
        } else {
          // 遞歸都用reduce方法, 通過_children 屬性進行查找
          let parent = path.slice(0, -1).reduce((root, current) => {
            return root._children[current];
          }, this.root);
          parent._children[path[path.length - 1]] = module;
        }
        // 看當前模塊是否有modules
        if (rootModule.modules) { // 如果有modules 開始重新註冊
          forEachValue(rootModule.modules, (moduleName, module) => {
            this.register(path.concat(moduleName), module);
          })
        }
      }
    }
    // 安裝模塊
    const installModule = (store, rootState, path, rootModule) => {
      // 將state 掛載到根上
      if (path.length > 0) {
        let parent = path.slice(0, -1).reduce((root, current) => {
          return root[current];
        }, rootState);
        // vue 不能再對象上增加不存在的屬性,否則視圖不會更新
        // parent.path[path.length - 1] = rootModule.state;
        Vue.set(parent, path[path.length - 1], rootModule.state);
      }
      // getters
      let getters = rootModule._rawModule.getters;
      if (getters) {
        forEachValue(getters, (getterName, fn) => {
          Object.defineProperty(store.getters, getterName, {
            get() {
              // 讓getters 執行,將自己的狀態傳入
              return fn(rootModule.state); // 將對應的函數執行
            }
          });
        });
      }
      // mutations
      let mutations = rootModule._rawModule.mutations; // 拿到每個模塊里的mutations
      if (mutations) {
        forEachValue(mutations, (mutationName, fn) => {
          let mutations = store._mutations[mutationName] || [];
          mutations.push((paylod) => {
            fn.call(store, rootModule.state, paylod);
            // 發佈,讓所有的訂閱依次執行
            store._subscribes.forEach(fn => fn({ type: mutationName, paylod }, rootState))
          });
          store._mutations[mutationName] = mutations;
        });
      }
      // actions
      let actions = rootModule._rawModule.actions; // 拿到每個模塊里的mutations
      if (actions) {
        forEachValue(actions, (actionName, fn) => {
          let actions = store._actions[actionName] || [];
          actions.push((paylod) => {
            fn.call(store, store, paylod);
          });
          store._actions[actionName] = actions;
        });
      }
      // 循環掛載兒子
      forEachValue(rootModule._children, (moduleName, module) => {
        installModule(store, rootState, path.concat(moduleName), module);
      });
    }
    class Store {
      constructor(options = {}) {
        this._vm = new Vue({
          data() {
            return {
              state: options.state
            }
          }
        });
        this.getters = {};
        this._mutations = {};
        this._actions = {};
        this._subscribes = [];
        this._modules = new ModuleCollection(options); // 把數據格式化成一個想要的數據結構
    
        // 遞歸將結果分類
        // this 整個store
        // this.state 當前的根狀態,把模塊中的狀態放在根上
        // [] 是為了遞歸的初始值
        // this._modules.root 是為了從跟模塊開始安裝
        installModule(this, this.state, [], this._modules.root);
    
        // plugins
        if (!Array.isArray(options.plugins)) {
          throw new TypeError('plugins is not Array');
        }
        options.plugins.forEach(fn => fn(this));
      }
      // 發佈
      commit = (mutationName, paylod) => {
        this._mutations[mutationName].forEach(fn => fn(paylod));
      }
      dispatch = (actionName, paylod) => {
        this._actions[actionName].forEach(fn => fn(paylod));
      }
      // 訂閱所有的plugins
      subscribe = (fn) => {
        this._subscribes.push(fn);
      }
      get state() {
        return this._vm.state;
      }
    }
    const install = (_Vue) => {
      Vue = _Vue;
      Vue.mixin({
        beforeCreate() {
          if (this.$options && this.$options.store) {
            this.$store = this.$options.store;
          } else {
            this.$store = this.$parent && this.$parent.$store;
          }
        }
      });
    }
    
    export default  {
      install,
      Store
    };

     

Tags: