手写一版自己的 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,
}

完啦,谢谢大家观看学习!哈哈哈哈

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