Vuex源码解析

Vuex源码解析

  • Vuex的使用:只涉及最基础的挂载,具体api的使用,请自行学习。
      1. 引入Vuex
      1. 挂载到Vue上
      1. 创建一个store实例
      1. 把store实例挂载到vue实例上.就是放入main.js中的vue实例上
// 下面代码是在store文件下的index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(vuex)
export default new Vuex.Store({}) // 通常我们把实例Vuex的代码,放到store文件下。
// 下面代码是在main.js
import store from "./store";
let v = new Vue({
  router,
  store, // 把store放到Vue的options中,这样vuex就能把store挂载到每一个vue实例下的$store属性
  render: (h) => h(App),
}).$mount("#app");

Vuex的挂载流程以及挂载原理

  • 首先实现Vuex基础版本之前,先知道Vuex挂载到vue实例上的流程。
    • 第一步:通过Vue.use(vuex)挂载插件到Vue上。注意Vue.use()的使用一定 是在new Vue({})之前的
    • 第二步:执行插件或者执行插件中的install函数。 Vue.use会查看插件有没有暴露install函数,有的话,会执行插件中的install函数。如果没有的话就执行该插件(这种情况下:插件默认暴露出来的要是一个方法)。Vue.use()具体实现代码如下
    function initUse(Vue) {
      Vue.use = function (plugin) {
        var installedPlugins =
          // 这个this代表的是Vue实例
          // 首先判断Vue构造函数上有没有_installedPlugins属性
          // 这个属性代表Vue实例注册的插件
          // 如果没有的_installedPlugins属性的话,就给它创建并赋值为[]
          this._installedPlugins || (this._installedPlugins = []);
        //   查看Vue.use(xx)传进来的插件有没有注册过
        // 如果有注册过的话,返回Vue构造函数
        if (installedPlugins.indexOf(plugin) > -1) {
          return this;
        }
        // 如果没有注册过的话
        // 获取Vue.use(xxx,xxx,xxx)中的除第一个参数以外的参数
        var args = toArray(arguments, 1);
        // 然后追加一个this到第一个参数
        args.unshift(this);
        // 如果插件中存在install属性,且是方法的话就执行这个方法
        if (typeof plugin.install === "function") {
          // 执行方法,并使install指向插件本身
          plugin.install.apply(plugin, args);
        } else if (typeof plugin === "function") {
          // 直接执行
          plugin.apply(null, args);
        }
        // 然后在installedPlugins注册组件
        installedPlugins.push(plugin);
        // 返回Vue构造函数
        return this;
      };
    }
    // 将list转换为数组,并删除list的前start个元素
    function toArray(list, start) {
      start = start || 0;
      var i = list.length - start;
      var ret = new Array(i);
      while (i--) {
        ret[i] = list[i + start];
      }
      return ret;
    }
    
    • install执行的时候,第一个参数一定是Vue。这是在Vue.use()中处理的。而vuex中的install的功能就是通过Vue.mixin()混入一个beforeCreatd钩子函数,该钩子函数的作用就是判断main.js中有没有传入store对象。如果有的话就把store混入到所有vue实例的$store.这样我们就能在任何vue实例中使用$store
    export const install = function (_Vue) {
      Vue = _Vue;
      //   使用vue提供的方法  Vue.mixin混入 方法和数据
      Vue.mixin({
        beforeCreate() {
          let options = this.$options;
          //   console.log(this.$options.store);
          if (options.store) {
            this.$store = options.store;
          } else {
            this.$store = this.$parent && this.$parent.$store;
          }
        },
      });
    };
    
    • 第三步: 就是实例化一个store对象 export default new Vuex.Store({})
    • 第四部: 就是把这个store挂载到vue实例上。
    import store from "./store";
    let v = new Vue({
      router,
      store, // 把store放到Vue的options中,这样vuex就能把store挂载到每一个vue实例下的$store属性
      render: (h) => h(App),
    }).$mount("#app");
    

简易版Vuex的实现

  • 由以上Vuex挂载流程我们可以知道,引入Vuex时会执行install以及暴露一个Store类。
  • 这样我就通过index.js向外暴露一个Store类以及install函数
    import { Store, install } from "./store";
    export default {
      Store,
      install,
    };
    
  • 由以上index.js可知store.js下默认暴露Store, install

Store类的实现

  • 首先Store类,接收传递进来的options对象
  • state:store中state的处理是利用vue中data进行处理的。因为它是响应式的。我们通过this.$store.state读取数据的时候,实际走的时Store下的get state()方法得到vue实例下的data
  • gettersstore中getters的处理是利用vue的computed属性处理的。因为vuex希望使用getter的时候,如果getter中所依赖的state不变的话,不需要重复执行方法。所以就利用了computed缓存机制。通过遍历options.getters定义和getter同名的computed,然后在通过Object.defineProperty劫持store中的getters,使其读取getter的时候,实际读取的是vue实例下同名的comuted属性
  • mutations:vuex中mutations的调用,是通过commit函数去调用store中的mutations。vuex中通过遍历mutations,给store重新定义mutations。然后通过commit调用
    this._mutations[key] = (payload) => {
       fn.call(this, this.state, payload);
     };
     commit = (type, payload) => {
    this._mutations[type](payload);
    };
    
  • actions: vuex中actions的实现原理和mutations的实现原理一致。不过需要注意actions实际执行中的第一个参数应该是store实例,而不是state。这是和mutations是有区别的。
    this._actions = {};
    foreach(options.actions, (fn, key) => {
      this._actions[key] = (payload) => {
        fn.call(this, this, payload);
      };
    });
    dispatch = (type, payload) => {
       this._actions[type](payload);
     };
    
  • 简易版本的vuex具体代码如下:
    let Vue; // 这个Vue会在install赋值。这样我们就可以使用new Vue({})创建实例了
    const foreach = (obj = {}, cb) => {
      Object.keys(obj).forEach((key) => cb(obj[key], key));
    };
    class Store {
      constructor(options) {
        let state = options.state;
        this.getters = {};
        const computed = {};
        foreach(options.getters, (fn, key) => {
          computed[key] = () => {
            return fn(this.state);
          };
          Object.defineProperty(this.getters, key, {
            get: () => {
              return this._vm[key];
            },
          });
        });
        this._mutations = {};
        foreach(options.mutations, (fn, key) => {
          this._mutations[key] = (payload) => {
            fn.call(this, this.state, payload);
          };
        });
        this._actions = {};
        foreach(options.actions, (fn, key) => {
          this._actions[key] = (payload) => {
            fn.call(this, this, payload);
          };
        });
        this._vm = new Vue({
          data: {
            $$state: state,
          },
          computed,
        });
      }
      get state() {
        return this._vm._data.$$state;
      }
      commit = (type, payload) => {
        this._mutations[type](payload);
      };
      dispatch = (type, payload) => {
        this._actions[type](payload);
      };
    }
    
    const install = (_Vue) => {
      Vue = _Vue;
      //   使用vue提供的方法  Vue.mixin混入 方法和数据
      Vue.mixin({
        beforeCreate() {
          let options = this.$options;
          //   console.log(this.$options.store);
          if (options.store) {
            this.$store = options.store;
          } else {
            this.$store = this.$parent && this.$parent.$store;
          }
        },
      });
    };
    export { Store, install };
    

正式版vuex@3的实现

  • 正式版的vuex插件的挂载与简易版的一致,只是Store类的实现不一致
  • 把具有模块化的vuex实现分为下面几步:
    1. 我们通过new Vuex.Store({{options}) 初始化store实例
    2. 然后Store类中的constructor中通过调用ModuleCollection类进行模块收集,把用户传递的具有树形结构的options转换为具有树形结构的ModuleCollection实例,并且挂载到this.$store上去。我们可以通过this.$store._modules查看经过ModuleCollection处理过的数据类型。
    3. ModuleCollection中主要是递归调用register函数进行收集,register(path,module)函数接收两个参数,表示为path下添加子模块,path是一个数组。如['a','c'],表示为a模块添加一个子模块c。第一次register时,path为[]表示为根模块添加。ModuleCollection类下有一个getNameSpace函数,获取当前path的命名空间前缀。比如传递了一个['a','b'],那么它会根据a模块和b模块是否存在namespaced属性,有的话 返回值就是a/b/
    import { foreach } from "../../MVuex/utils";
    import Module from "./module";
    
    export default class ModuleCollection {
      constructor(options) {
        this.register([], options);
      }
      register(path, rootModule) {
        let newModule = new Module(rootModule);
        // 把当前注册的模块上添加一个属性,这个属性指向自己
        rootModule.rawModule = newModule;
        if (path.length == 0) {
          this.root = newModule;
        } else {
          // 下面就是通过reduce寻找父模块并把path参数的最后一项,添加到它的父级中去
          // 找到父级
          let parent = path.slice(0, -1).reduce((memo, current) => {
            // return memo._children[current];
            return memo.getChild(current);
          }, this.root);
          // 给父级添加子模块
          // parent._children[path[path.length - 1]] = newModule;
          parent.addChild(path[path.length - 1], newModule);
        }
        if (rootModule.modules) {
          foreach(rootModule.modules, (module, moduleName) => {
            // 这个时候register中的path就是 ['b'] ,['b','c']这种
            // ['b']代表root下的b模块
            // ['b','c']代表的就是b下的c模块
            this.register([...path, moduleName], module);
          });
        }
      }
      // 获取命名空间
      getNameSpace(path) {
        let root = this.root;
        return path.reduce((namespace, key) => {
          root = root.getChild(key);
    
          return namespace + (root.namespaced ? key + "/" : "");
        }, "");
      }
    }
    
    
    1. register函数中通过实例化Module类,创建一个一个子模块。Module类存在几个方法getChild:获取子元素就是获取module._chidlren addChild添加子模块,以上两个方法是在register调用的 。forEachMutation forEachAction forEachGetters forEachChild就是遍历Module的mutation actions getters children属性。在installModule中调用。
    // let newModule = {
    //   _raw: options,
    //   _children: {},
    //   state: options.state,
    // };
    
    import { foreach } from "../utils";
    
    export default class Module {
      constructor(rootModule) {
        this._rawModule = rootModule;
        this._children = {};
        this.state = rootModule.state;
      }
      // 获取当前模块的namespaced属性
      get namespaced() {
        return this._rawModule.namespaced;
      }
      // 获取_children属性
      getChild(key) {
        return this._children[key];
      }
      // 添加_children属性
      addChild(key, module) {
        this._children[key] = module;
      }
      forEachMutation(fn) {
        if (this._rawModule.mutations) {
          foreach(this._rawModule.mutations, fn);
        }
      }
      forEachAction(fn) {
        if (this._rawModule.actions) {
          foreach(this._rawModule.actions, fn);
        }
      }
      forEachGetters(fn) {
        if (this._rawModule.getters) {
          foreach(this._rawModule.getters, fn);
        }
      }
      forEachChild(fn) {
        foreach(this._children, fn);
      }
    }
    
    
    // 这是没有经过ModuleCollection类处理的结构
    state: {
        name: "home",
        age: 18,
      },
      modules: {
        a: {
          state: {},
          modules: {
            c: {
              state: {},
            },
          },
        },
        b: {
          state: {},
        },
      },
      // 下面是经过ModuleCollection处理过的数据类型
    root =	{
          _raw: rootModule, // rootModule就是原始的modules对象
          state: rootModule.state,
           _children: {
          	a: {
    	      _raw: rootModule,
    	      _children: {
    			c: {
    		      _raw: rootModule,
    		      _children: {},
    		      state: rootModule.state,
    		    };
    		},
    	      state: rootModule.state,
    	    };
          },
        };
    
    1. 经过上面过程之后,就把用户 new Vuex.Store({options})中的options变成了具有树形结构的ModuleCollection对象。并且该对象在store实例上通过_module属性展示
  1. 然后通过installModule()把module的属性定义到store上。与上面不同的是,上面只是把一个树形结构_module挂载到store上,而这个方法是把state,mutation,action这些属性挂载到store上。也是通过递归调用installModule实现.注意这个地方收集之后是没有模块结构的。相同名称的mutation,action放到了一个数组里面(前提是没有命名空间)

  2. 挂载之后我们调整commit.dispath函数。让其遍历store实例上的mutations以及actions.循环调用相同名称的mutation以及action

  3. 经过上面过程之后 mutation action getters都已经挂载到store实例上了。后面通过resetStoreVm创建vm实例让其挂载到store上。vm实例中处理state以及getters。当我们读取state的时候,其实读取的就是this.$store._vm._data.$$state.而我们读取getter的时候就是读取的vm实例上的computed属性

    	 import applyMixin from "./mixin";
      import ModuleCollection from "./module/module-collections";
      import { foreach } from "../MVuex/utils";
      let Vue;
      function getState(store, path) {
        return path.reduce((newState, current) => {
          return newState[current];
        }, store.state);
      }
      // 安装模块
      function installModule(store, rootState, path, module) {
        // module实例就是通过Module类创建的实例
        let namespace = store._modules.getNameSpace(path);
      
        // 先把各个子模块的state也以树形结构放到根模块的state上
        if (path.length > 0) {
          //如果是子模块就需要把子模块的状态定义到根模块上.也是具有树形结构的
          let parent = path.slice(0, -1).reduce((memo, current) => {
            return memo[current];
          }, rootState);
          Vue.set(parent, path[path.length - 1], module.state);
        }
      
        // 遍历当前模块的mutations,把mutation添加store实例上
        module.forEachMutation((mutation, type) => {
          // 判断store._mutations下有没有相同的mutation,有的话就push
          store._mutations[namespace + type] =
            store._mutations[namespace + type] || [];
          store._mutations[namespace + type].push((payload) => {
            store._withCommitting(() => {
              mutation.call(store, getState(store, path), payload);
            });
      
            store._subscribers.forEach((sub) => sub({ mutation, type }, store.state));
          });
        });
        // 遍历当前模块的actions,把mutation添加store实例上
        module.forEachAction((action, type) => {
          store._actions[namespace + type] = store._actions[namespace + type] || [];
          store._actions[namespace + type].push((payload) => {
            action.call(store, store, payload);
          });
        });
        // 遍历当前模块的getters,把mutation添加store实例上
        module.forEachGetters((getter, key) => {
          // getter不存在模块化,所有模块的getter都是定义到根模块上
          // 具有相同名字的getter会覆盖
          store._wrappedGetters[namespace + key] = function () {
            return getter(getState(store, path));
          };
        });
        // 安装子模块
        module.forEachChild((child, key) => {
          installModule(store, rootState, path.concat(key), child);
        });
      }
      
      function resetStoreVm(store, state) {
        let oldVm = store._vm;
        const wrappedGetters = store._wrappedGetters;
        let computed = {};
        store.getters = {};
        foreach(wrappedGetters, (fn, key) => {
          computed[key] = function () {
            return fn();
          };
          Object.defineProperty(store.getters, key, {
            get: () => store._vm[key],
          });
        });
      
        store._vm = new Vue({
          data: {
            $$state: state,
          },
          computed,
        });
        if (store.strict) {
          store._vm.$watch(
            () => store._vm._data.$$state,
            () => {
              console.log(store._committing, "数据改变了");
              console.assert(store._committing, "在mutation之外更改了状态");
            },
            { deep: true, sync: true }
          );
        }
        if (oldVm) {
          Vue.nextTick(() => {
            oldVm.$destroy();
          });
        }
      }
      class Store {
        constructor(options) {
          // 格式化用户传入的参数,格式化成树形结构,更直观一些,后续模块画也更好操作
          // 1. 将模块转化为一棵树
          this._modules = new ModuleCollection(options);
          // 2. 安装模块 将模块上的属性,定义到我们的store中
          let state = this._modules.root.state;
          this._mutations = {};
          this._actions = {};
          this._wrappedGetters = {};
          this._subscribers = [];
          this.strict = options.strict; //是否是严格模式
          this._committing = false;
          // this指当前store实例 state是指根state
          installModule(this, state, [], this._modules.root);
          // 将状态放到vue实例中
          resetStoreVm(this, state);
          // 如果存在插件的话就执行插件
          if (options.plugins) {
            options.plugins.forEach((fn) => {
              fn(this);
            });
          }
        }
        get state() {
          return this._vm._data.$$state;
        }
        commit = (type, payload) => {
          this._mutations[type].forEach((fn) => {
            fn(payload);
          });
        };
        dispatch = (type, payload) => {
          this._actions[type].forEach((fn) => {
            fn(payload);
          });
        };
        //动态注册模块的函数
        registerModule(path, module) {
          console.log("注册模块", path, module);
          if (typeof path == "string") path = [path];
          // 先注册模块,注册后store._module就有了该模块
          this._modules.register(path, module);
          // 安装模块
          installModule(this, this.state, path, module.rawModule);
          // 重新创建一个实例
          // 原因是新加的模块中的state以及getters并不是响应式的
          resetStoreVm(this, this.state);
        }
      
        //
        subscribe(fn) {
          this._subscribers.push(fn);
        }
        // 替换状态
        replaceState(newState) {
          this._withCommitting(() => {
            this._vm._data.$$state = newState;
          });
        }
        _withCommitting(fn) {
          console.log("数据改变之前", this._committing);
          let committing = this._committing;
          this._committing = true; //在函数调用前标识_committing为true
          fn();
          this._committing = committing; //在函数调用后标识_committing为false
          console.log("数据改变之后", this._committing);
        }
      }
      
      const install = (_Vue) => {
        Vue = _Vue;
        applyMixin(Vue);
      };
      
      export { Store, install };
    
    
  4. 经过以上过程我们的vuex就基本实现了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值