手寫一版本身的 VUEX

既然 VUEX 能夠 use,內部一定是有一個 install 方法,因此咱們先要實現一個install方法,當咱們用的時候,每個組件上面都有一個this.$store屬性,裏面包含了狀態倉庫裏面的state,mutations, actions, getters,因此咱們也須要在每一個組件上都掛載一個$store屬性,具體實現以下:
let Vue = null;
export function install(_Vue) {
  // 爲了防止重複註冊
  if (Vue !== _Vue) {
    Vue = _Vue
  }
  Vue.mixin({
    beforeCreate() {
      const options = this.$options;
      if (options.store) {
        this.$store = options.store;
      } else if (options.parent && options.parent.$store) {
        this.$store = options.parent.$store;
      }
    }
  })
}
爲了後面的緩緩,咱們須要封裝一個本身的循環方法,用來簡化代碼操做
const forEach = (obj, cb) => {
  Object.keys(obj).forEach(key => {
    cb(key, obj[key]);
  })
}
到此咱們就能夠正常的引入並use(vuex)啦,可是此時咱們並無去初始化倉庫,由於原聲的vuex還須要去 new Vuex.Store(),能夠傳入一些初始化參數,好比state、mutations、actions、getters,既然能 new ,說明這是一個 類,因此咱們如今去寫 Store 這個類,具體實現以下:
export class Store {
  constructor(options = {}) {
    // TODO....
  }
}
好了,這個時候頁面就不會報錯啦,並且也能夠經過 this.$store.state.xxx 取到state中的值了,可是原生的state從新設置會引起視圖更新,因此還須要把store中的state設置成響應式的,具體實現以下:
export class Store {
  constructor(options = {}) {
    this.vm = new Vue({
      data(){
        return {
          state: options.state
        }
      }
    })
  },
  // 類的屬性訪問器
  get state() {
    return this.vm.state
  }
}
到此咱們在頁面中設置值就能夠引起視圖更新啦,由於咱們已經把全部的數據變成了雙向綁定,只要視圖更改就會引起視圖更新

這個時候,咱們在初始化store的時候還傳入了mutations、actions、getters,這些數據都尚未處理,因此如今咱們就能夠優先處理這些數據,並且在$store屬性上還有 commit,dispatch,各類屬性,因此咱們還須要把這些屬性頁所有寫好,具體實現以下:javascript

export class Store {
  constructor(options = {}) {
    this.vm = new Vue({
      data(){
        return {
          state: options.state
        }
      }
    })
    
    this.getters = {};
    this.mutations = {};
    this.actions = {};

    // 處理 getters 響應式數據
    let getters = options.getters;
    forEach(getters,(getterName, fn)=>{
      Object.defineProperty(this.getters, getterName, {
        get: () => {
          return fn(this.state)
        }
      })
    })

    // 獲取全部的同步的更新操做方法
    let mutations = options.mutations; 
    forEach(mutations,(mutationName, fn) => {
      this.mutations[mutationName] = (payload) => {
        fn(this.state, payload)
      }
    });

    // 獲取全部的異步的更新操做方法
    let actions = options.actions; 
    forEach(actions,(actionName, fn) => {
      this.actions[actionName] = (payload) => {
        fn(this, payload);
      }
    })
  }


  commit = (type, payload) => {
    this.mutations[type](payload)
  }

  dispatch = (type, payload) => {
    this.actions[type](payload)
  }

  get state() {
    return this.vm.state
  }

}

到此,基本的vuex 已經能夠正常運行啦!是否是很簡單!!!!!vue

可是,到此還遠遠不止!!!!!!java

咱們知道,原生的 vuex 中還有modules 屬性,裏面能夠嵌套任意層state,mutations,actions,getters,因此咱們還須要處理這個屬性
看原生的vuex屬性中有個_modules 屬性,是一個樹結構

因此咱們也仿照原生,生成一個__modules樹結構的對象
// 格式化 _modules
class ModuleCollection {
  constructor(options) {
    // 註冊模塊 將模塊註冊成樹結構
    this.register([], options);
  }
  register(path, rootModule) {
    let module = { // 將模塊格式化
      _rawModule: rootModule,
      _chidlren: {},
      state: rootModule.state
    }
    if (path.length == 0) {
      // 若是是根模塊 將這個模塊掛在到根實例上
      this.root = module;
    } else {
      // 遞歸都用reduce方法
      // 經過 _children 屬性進行查找
      let parent = path.slice(0, -1).reduce((root, current) => {
        return root._chidlren[current]
      }, this.root)
      parent._chidlren[path[path.length - 1]] = module
    }
    // 看當前模塊是否有modules , 若是有modules 開始從新再次註冊
    if (rootModule.modules) {
      forEach(rootModule.modules, (moduleName, module) => {
        this.register(path.concat(moduleName), module)
      })
    }
  }
}
而後在 Store 類中把 _modules 屬性掛載到實例上
Store類中:
    // 把數據格式化成一個 想要的樹結構
    this._modules = new ModuleCollection(options);
到此,咱們把modules都已經處理成了想要的結構,可是各個模塊下的屬性都沒有掛載處理,因此咱們還須要遞歸掛載各個模塊的屬性,因此上面寫的處理mutations、atcions、getters屬性的方法都須要從新去寫,具體實現以下:
/**
 * @explain { 安裝模塊 }
 *    @param { store }  整個store 
 *    @param { rootState }  當前的根狀態
 *    @param { path }  爲了遞歸來建立的
 *    @param { rootModule }  從根模塊開始安裝
 */

const installModule = (store, rootState, path, rootModule) => {
  if (path.length > 0) {
    // [a]
    // 是兒子,兒子要找到爸爸將本身的狀態 放到上面去
    let parent = path.slice(0, -1).reduce((root, current) => {
      return root[current]
    }, rootState)
    // vue 不能在對象上增長不存在的屬性 不然不會致使視圖更新
    Vue.set(parent, path[path.length - 1], rootModule.state);
    // {age:1,a:{a:1}}
    // 實現了 查找掛在數據格式
  }
  // 如下代碼都是在處理  模塊中 getters actions mutation
  let getters = rootModule._rawModule.getters;
  if (getters) {
    forEach(getters, (getterName, fn) => {
      Object.defineProperty(store.getters, getterName, {
        get() {
          return fn(rootModule.state); // 讓對應的函數執行
        }
      });
    })
  }
  let mutations = rootModule._rawModule.mutations;
  if (mutations) {
    forEach(mutations, (mutationName, fn) => {
      let mutations = store.mutations[mutationName] || [];
      mutations.push((payload) => {
        fn(rootModule.state, payload);
        // 發佈 讓全部的訂閱依次執行
        store._subscribes.forEach(fn => fn({ type: mutationName, payload }, rootState));
      })
      store.mutations[mutationName] = mutations;
    })
  }
  let actions = rootModule._rawModule.actions;
  if (actions) {
    forEach(actions, (actionName, fn) => {
      let actions = store.actions[actionName] || [];
      actions.push((payload) => {
        fn(store, payload);
      })
      store.actions[actionName] = actions;
    })
  }
  // 掛載兒子
  forEach(rootModule._chidlren, (moduleName, module) => {
    installModule(store, rootState, path.concat(moduleName), module)
  })
}
而後在 Store 類中執行此處理函數,完勝掛載
/**
     * @explain { 安裝模塊 }
     *    @param { this }  整個store 
     *    @param { this.state }  當前的根狀態
     *    @param { [] }  爲了遞歸來建立的
     *    @param { this._modules.root }  從根模塊開始安裝
     */
    installModule(this, this.state, [], this._modules.root);
此時,咱們本身寫的 vuex 就能夠徹底正常運行啦,能夠隨意書寫modules,state,mutations,actions,getters,是否是很酷!!!!!!哈哈哈哈

可是咱們還有一個 plugins 屬性還沒實現,具體這塊的實現很是簡單,由於咱們在使用 plugins 的時候,傳遞一個數組,數組裏面是一個個的 中間件函數,每個函數的第一個參數都是 倉庫實例自己,因此,咱們在代碼裏就能夠這樣寫vuex

// 處理 插件
    (options.plugins || []).forEach(plugin => plugin(this));
到此,咱們的 vuex 就實現啦,是否是很簡單,哈哈哈哈哈哈哈

下面是全部的代碼:

const forEach = (obj, cb) => {
  Object.keys(obj).forEach(key => {
    cb(key, obj[key]);
  })
}

let Vue = null;
export function install(_Vue) {
  if (Vue !== _Vue) {
    Vue = _Vue
  }
  Vue.mixin({
    beforeCreate() {
      const options = this.$options;
      if (options.store) {
        this.$store = options.store;
      } else if (options.parent && options.parent.$store) {
        this.$store = options.parent.$store;
      }
    }
  })
}

/**
 * @explain { 安裝模塊 }
 *    @param { store }  整個store 
 *    @param { rootState }  當前的根狀態
 *    @param { path }  爲了遞歸來建立的
 *    @param { rootModule }  從根模塊開始安裝
 */

const installModule = (store, rootState, path, rootModule) => {
  if (path.length > 0) {
    // [a]
    // 是兒子,兒子要找到爸爸將本身的狀態 放到上面去
    let parent = path.slice(0, -1).reduce((root, current) => {
      return root[current]
    }, rootState)
    // vue 不能在對象上增長不存在的屬性 不然不會致使視圖更新
    Vue.set(parent, path[path.length - 1], rootModule.state);
    // {age:1,a:{a:1}}
    // 實現了 查找掛在數據格式
  }
  // 如下代碼都是在處理  模塊中 getters actions mutation
  let getters = rootModule._rawModule.getters;
  if (getters) {
    forEach(getters, (getterName, fn) => {
      Object.defineProperty(store.getters, getterName, {
        get() {
          return fn(rootModule.state); // 讓對應的函數執行
        }
      });
    })
  }
  let mutations = rootModule._rawModule.mutations;
  if (mutations) {
    forEach(mutations, (mutationName, fn) => {
      let mutations = store.mutations[mutationName] || [];
      mutations.push((payload) => {
        fn(rootModule.state, payload);
        // 發佈 讓全部的訂閱依次執行
        store._subscribes.forEach(fn => fn({ type: mutationName, payload }, rootState));
      })
      store.mutations[mutationName] = mutations;
    })
  }
  let actions = rootModule._rawModule.actions;
  if (actions) {
    forEach(actions, (actionName, fn) => {
      let actions = store.actions[actionName] || [];
      actions.push((payload) => {
        fn(store, payload);
      })
      store.actions[actionName] = actions;
    })
  }
  // 掛載兒子
  forEach(rootModule._chidlren, (moduleName, module) => {
    installModule(store, rootState, path.concat(moduleName), module)
  })
}


class ModuleCollection { // 格式化
  constructor(options) {
    // 註冊模塊 將模塊註冊成樹結構
    this.register([], options);
  }
  register(path, rootModule) {
    let module = { // 將模塊格式化
      _rawModule: rootModule,
      _chidlren: {},
      state: rootModule.state
    }
    if (path.length == 0) {
      // 若是是根模塊 將這個模塊掛在到根實例上
      this.root = module;
    } else {
      // 遞歸都用reduce方法
      // 經過 _children 屬性進行查找
      let parent = path.slice(0, -1).reduce((root, current) => {
        return root._chidlren[current]
      }, this.root)
      parent._chidlren[path[path.length - 1]] = module
    }
    // 看當前模塊是否有modules , 若是有modules 開始從新再次註冊
    if (rootModule.modules) {
      forEach(rootModule.modules, (moduleName, module) => {
        this.register(path.concat(moduleName), module)
      })
    }
  }
}


export 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);

    /**
     * @explain { 安裝模塊 }
     *    @param { this }  整個store 
     *    @param { this.state }  當前的根狀態
     *    @param { [] }  爲了遞歸來建立的
     *    @param { this._modules.root }  從根模塊開始安裝
     */
    installModule(this, this.state, [], this._modules.root);

    // 處理 插件
    (options.plugins || []).forEach(plugin => plugin(this));
  }

  subscribe(fn){
    this._subscribes.push(fn);
  }

  commit = (type, payload) => {
    this.mutations[type].forEach(cb => cb(payload))
  }

  dispatch = (type, payload) => {
    this.actions[type].forEach(cb => cb(payload))
  }

  get state() {
    return this.vm.state
  }

}

export default {
  install,
  Store,
}

完啦,謝謝你們觀看學習!哈哈哈哈數組

相關文章
相關標籤/搜索