手写一版本身的 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,
}

完啦,谢谢你们观看学习!哈哈哈哈数组

相关文章
相关标签/搜索