手寫一版自己的 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,各種屬性,所以我們還需要把這些屬性頁全部寫好,具體實現如下:

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 已經可以正常運行啦!是不是很簡單!!!!!

但是,到此還遠遠不止!!!!!!

我們知道,原生的 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 的時候,傳遞一個數組,數組裏面是一個個的 中間件函數,每一個函數的第一個參數都是 倉庫實例本身,所以,我們在代碼裏就可以這樣寫

    // 處理 插件
    (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,
}

完啦,謝謝大家觀看學習!哈哈哈哈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章