最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入Vuex原理:实现一个简易Vuex

    正文概述 掘金(早上吃包子)   2021-01-28   445

    一.简介

    • 通过唯一的入口mutation 更改 state

    • Vuex 不能单独使用 依赖 vue

    下面让我们自己动手实现一个vuex吧。

    二.实现 Vuex 模块

    1.install 方法

    当调用install方法的时候,传入当前版本Vue的构造函数,我把传入的\_Vue 保存在 Vue 变量,并且把它导出 这样的话,我当前用的 vue 和写项目的 vue 版本一致

    let Vue;
    export class Store {
        constructor(options){}
    }
    
    export const install = (_Vue) =>{
        Vue = _Vue;
        //...
    }
    

    2.mixin 方法

    qs:怎么让所有组件都能访问到store对象?

    所有组件都能执行的方法,Vue.mixin({beforeCreate}),拿到 store 挂载到自己的身上 拿到根组件的 store,将它共享给每个组件

    Vue.mixin({
        beforeCreate() {
          let options = this.$options;
          if (options.store) {
            this.$store = options.store;
          } else {
            if (this.$parent && this.$parent.$store) {
              this.$store = this.$parent.$store;
            }
          }
        },
      });
    

    3.实现 state

    qs:改变 store.state,怎么通知视图更新? 创建一个 vue 实例 把 store.state 放到 data 上 属性访问器 访问$store.state代理到this._vm.$$state

    import { Vue } from "./install";
    
    class Store {
      constructor(options) {
        //用户组件中使用的$store 等价于这里的this
        let { state, mutation, actions, moudle, strict, getters } = options;
    
        this._vm = new Vue({
          data: {
            $$state: state,
          },
        });
      }
      get state(){
          return this._vm._data.$$state
      }
    }
    
    export default Store;
    

    因为下面用多次遍历 所以我们在这边写一个遍历的方法,方便后续操作

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

    4.实现 getters

    vuex 里的 getters 就相当于 vue 的 computed 默认不执行,取值才执行,具有缓存。 把 getters 上的数据定义到 computed 上 同时对 把 getters 取值代理到 computed 上

    this.getters = {};
    const computed = {}
    _forEach(options.getters, (fn, key) => {
        computed[key] = () => {
            return fn(this.state);
        }
        Object.defineProperty(this.getters,key,{
            get:()=> this._vm[key]
        })
    });
    this._vm = new Vue({
        data: {
            $$state: state,
        },
        computed // 利用计算属性实现缓存
    });
    
    
    

    5.实现 mutations

    把用户传入的 muations 遍历赋值到 Storemuationscommit 调用 muations 的方法

    export class Store {
        constructor(options) {
            this.mutations = {};
            _forEach(options.mutations, (fn, key) => {
                this.mutations[key] = (payload) => fn.call(this, this.state, payload)
            });
        }
        commit = (type, payload) => {
            this.mutations[type](payload);
        }
    }
    

    6.实现 actions

    把用户传入的 actions 遍历赋值到Storeactions dispatch 调用 actions 的方法

    export class Store {
        constructor(options) {
            this.actions = {};
            _forEach(options.actions, (fn, key) => {
                this.actions[key] = (payload) => fn.call(this, this,payload);
            });
        }
        dispatch = (type, payload) => {
            this.actions[type](payload);
        }
    }
    

    三.实现模块机制

    nameSpaced 可以解决子模块和父模块的命名冲突文件,相当于增加了一个命名空间

    1.格式化用户数据 options

    对应的 moudle 放入对应的 children 下 格式化成如下格式 用树的结构来注册模块父子关系

    this.root={
      _raw:用户定义的模块,
      state:当前模块自己的状态,
      _children:{
        a:{
          _raw:用户定义的模块,
          state:当前模块自己的状态,
          _children:{
    //子模块列表
          }
        }
      }
    }
    

    path.length=0,注册为根模块 再递归注册根模块的 modules

    class ModuleCollection {
      constructor(options) {
        this.root = null;
        this.register([], options);
      }
      register(path, rootModule) {
        let newModule = {
          _raw: rootModule,
          _children: {},
          state: rootModule.state,
        };
        if (path.length == 0) {
          this.root = newModule;
        } else {
          let parent = path.slice(0, -1).reduce((memo, current) => {
            return memo._children[current];
          }, this.root);
          parent._children[path[path.length - 1]] = newModule;
        }
        if (rootModule.modules) {
          forEach(rootModule.modules, (module, key) => {
            this.register(path.concat(key), module);
          });
        }
      }
    }
    

    2.抽离模块类

    export default class Module {
      constructor(rawModule) {
        this._raw = rawModule;
        this._children = {};
        this.state = rawModule.state;
      }
      getChild(childName) {
        return this._children[childName];
      }
      addChild(childName, module) {
        this._children[childName] = module;
      }
    }
    

    3.安装模块

    没有 namespace 的时候, 遍历格式化后的数据 把 getters 都放在根上 mutations acrions 合并成数组

    this._actions = {};
    this._mutations = {};
    this._wrappedGetters = {};
    // 安装模块
    installModule(this, state, [], this._modules.root);
    
     addChild(childName, module) {
        this._children[childName] = module;
      }
      forEachGetter(cb) {
        this._raw.getters && _forEach(this._raw.getters, cb);
      }
      forEachMutation(cb) {
        this._raw.mutations && _forEach(this._raw.mutations, cb);
      }
      forEachAction(cb) {
        this._raw.actions && _forEach(this._raw.actions, cb);
      }
      forEachChildren(cb) {
        this._children && _forEach(this._children, cb);
      }
    
    function installModule(store, rootState, path, root) {
      if (path.length > 0) {
        let parent = path.slice(0, -1).reduce((memo, current) => {
          return memo[current];
        }, rootState);
        // parent[path[path.length - 1]] = root.state;
        //对象直接新增属性不能导致重新更新视图,要通过Vue.set
        Vue.set(parent, path[path.length - 1], root.state);
        console.log(rootState, "rootState");
      }
    
        root.forEachGetter((fn, key) => {
        store.wrapperGetters[key] = function() {
          return fn.call(store, root.state);
        };
      });
      root.forEachMutation((fn, key) => {
        store.mutations[key] = store.mutations[key] || [];
        store.mutations[key].push((payload) => {
          return fn.call(store, root.state, payload);
        });
      });
      root.forEachAction((fn, key) => {
        store.actions[key] = store.actions[key] || [];
        store.actions[key].push((payload) => {
          return fn.call(store, store, payload);
        });
      });
      root.forEachChildren((child, key) => {
        installModule(store, rootState, path.concat(key), child);
      });
    }
    
     commit = (mutationName, payload) => {
        this.mutations[mutationName] &&
          this.mutations[mutationName].forEach((fn) => fn(payload));
      };
      dispatch = (actionName, payload) => {
        this.actions[actionName] &&
          this.actions[actionName].forEach((fn) => fn(payload));
      };
    

    4.定义 state 和 computed

    function resetStoreVM(store, state) {
        const computed = {};
        store.getters = {};
        const wrappedGetters = store._wrappedGetters
        _forEach(wrappedGetters, (fn, key) => {
            computed[key] = () => {
                return fn(store.state);
            }
            Object.defineProperty(store.getters, key, {
                get: () => store._vm[key]
            })
        });
        store._vm = new Vue({
            data: {
                $$state: state,
            },
            computed
        });
    }
    

    5.实现命名空间

    nameSpaced 可以解决子模块和父模块的命名冲突文件,相当于增加了一个命名空间

    如果没有 nameSpaced 默认 getters 会被定义到父模块上

    import { forEachValue } from '../util';
    import Module from './module';
    export default class ModuleCollection {
        getNamespace(path) {
            let module = this.root
            return path.reduce((namespace, key) => {
                module = module.getChild(key);
                console.log(module)
                return namespace + (module.namespaced ? key + '/' : '')
            }, '');
        }
    }
    export default class Module {
        get namespaced(){
            return !!this._rawModule.namespaced;
        }
    }
    
    function installModule(store, rootState, path, root) {
    + let namespace = store._modules.getNamespace(path);
      if (path.length > 0) {
        let parent = path.slice(0, -1).reduce((memo, current) => {
          return memo[current];
        }, rootState);
        Vue.set(parent, path[path.length - 1], root.state);
      }
    
      root.forEachGetter((fn, key) => {
    +    store.wrapperGetters[namespace + key] = function() {
          return fn.call(store, root.state);
        };
      });
      root.forEachMutation((fn, key) => {
    +    store.mutations[namespace + key] = store.mutations[namespace + key] || [];
    +    store.mutations[namespace + key].push((payload) => {
          return fn.call(store, root.state, payload);
        });
      });
      root.forEachAction((fn, key) => {
    +    store.actions[namespace + key] = store.actions[namespace + key] || [];
    +    store.actions[namespace + key].push((payload) => {
          return fn.call(store, store, payload);
        });
      });
      root.forEachChildren((child, key) => {
        installModule(store, rootState, path.concat(key), child);
      });
    }
    
    

    6.注册模块

     registerModule(path, module) {
        if (typeof path == "string") path = [path];
        this._modules.register(path, module);
        installModule(this, this.state, path, module.newModule);
        restVM(this, this.state);
      }
    
    function restVM(store, state) {
      let oldVm = store._vm;
      const computed = {};
      store.getters = {};
      forEach(store.wrapperGetters, (getter, key) => {
        computed[key] = getter;
        Object.defineProperty(store.getters, key, {
          get: () => store._vm[key],
        });
      });
      store._vm = new Vue({
        data: {
          $$state: state,
        },
        computed,
      });
      if (oldVm) {
        //销毁上次创建的实例
        Vue.nextTick(() => oldVm.$destroy());
      }
    }
    

    四.实现插件功能

    store = new Vuex.Store({
      plugins: [logger(), persists("localStorage")],
    )}
    

    1.实现 logger

    function logger() {
      return function(store) {
        let prevState = JSON.stringify(store.state);
        store.subscribe((mutation, state) => {
          console.log("prevState:", prevState);
          console.log("mutation:", mutation);
          console.log("currentState:", JSON.stringify(state));
          prevState = JSON.stringify(state);
        });
      };
    }
    

    2.实现 persists 持久化插件

    function persists() {
      return function(store) {
        let localState = JSON.parse(localStorage.getItem("VUEX:STATE"));
        if (localState) {
          store.replaceState(localState);
        }
        store.subscribe((mutation, rootState) => {
          // 这里需要做一个节流  throttle lodash
          localStorage.setItem("VUEX:STATE", JSON.stringify(rootState));
        });
      };
    }
    

    3.实现 plugins、subscribe、replaceState

    class Store {
      constructor(options) {
        this._modules = new ModuleCollection(options);
        installModule(this, state, [], this._modules.root);
       //...
    +    if (options.plugins) {
          options.plugins.forEach((plugin) => plugin(this));
        }
      }
    +  subscribe(fn) {
        this._subscribes.push(fn);
      }
    +  replaceState(state){
    	this._vm._data.$$state = state;
    }
      //...
    }
    
    function installModule(store, rootState, path, root) {
      let namespace = store._modules.getNamespace(path);
      //...
      root.forEachMutation((fn, key) => {
        store.mutations[namespace + key] = store.mutations[namespace + key] || [];
        store.mutations[namespace + key].push((payload) => {
         fn.call(store, root.state, payload);
    +    store._subscribes.forEach((fn) =>
            fn({ type: namespace + key, payload }, rootState)
          );
    
        });
      });
     //...
    }
    
    

    4. 获取最新状态

    function getNewState(store, path) {
      return path.reduce((memo, current) => {
        return memo[current];
      }, store.state);
    }
    
    function installModule(store, rootState, path, root) {
      let namespace = store._modules.getNamespace(path);
    //...
      root.forEachGetter((fn, key) => {
        store.wrapperGetters[namespace + key] = function() {
    +      return fn.call(store, getNewState(store, path));
        };
      });
      root.forEachMutation((fn, key) => {
        store.mutations[namespace + key] = store.mutations[namespace + key] || [];
        store.mutations[namespace + key].push((payload) => {
    +     fn.call(store, getNewState(store, path), payload);
          store._subscribes.forEach((fn) =>
            fn({ type: namespace + key, payload }, store.state)
          );
        });
      });
    }
    

    五.辅助函数

    1.mapState 实现

    function mapState(stateList) {
      let obj = {};
      stateList.forEach((stateName) => {
        obj[stateName] = function() {
          return this.$store.state[stateName];
        };
      });
      return obj;
    }
    

    2.mapGetters 实现

    function mapGetters(gettersList) {
      let obj = {};
      gettersList.forEach((getterName) => {
        obj[getterName] = function() {
          return this.$store.getters[getterName];
        };
      });
      return obj;
    }
    

    3.mapMutations 实现

    function mapMutations(mutationList) {
      let obj = {};
      mutationList.forEach((mutationName) => {
        obj[mutationName] = function(payload) {
          this.$store.commit(mutationName, payload);
        };
      });
      return obj;
    }
    

    4.mapActions 实现

    function mapActions(actionList) {
      let obj = {};
      actionList.forEach((actionName) => {
        obj[actionName] = function(payload) {
          this.$store.dispatch(actionName, payload);
        };
      });
      return obj;
    }
    

    六.区分 mutation 和 action

    严格模式strict: true,没有通过 mutation 修改 state 时,vuex 会提示如下错误

    我们通过内部维护一个变量_committing,来判断是不是在 mutaion 中更改的

     _withCommitting(fn) {
        this._committing = true;
        fn(); //函数是同步的,获取_committing为true,如果fn是异步的,那么获取_committing为false
        this._committing = false;
      }
    
    if (store.strict) {
        store._vm.$watch(
          () => store._vm._data.$$state,
          () => {
            console.assert(
              store._committing,
              "'在mutation之外更改了状态'"
            );
          },
          { deep: true, sync: true }
        );
      }
    
    store._withCommitting(() => {
      Vue.set(parent, path[path.length - 1], module.state);
    })
    
    replaceState(newState) { \
      this._withCommitting(() => {
          this._vm._data.$$state = newState;
      })
    }
    

    下载网 » 深入Vuex原理:实现一个简易Vuex

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元