• 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");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 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;
            }
          },
        });
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 第三步: 就是实例化一个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");
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    简易版Vuex的实现

    • 由以上Vuex挂载流程我们可以知道,引入Vuex时会执行install以及暴露一个Store类。
    • 这样我就通过index.js向外暴露一个Store类以及install函数
      import { Store, install } from "./store";
      export default {
        Store,
        install,
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 由以上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);
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 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);
       };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 简易版本的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 };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65

    正式版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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      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);
        }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      // 这是没有经过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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      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 };
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131
      • 132
      • 133
      • 134
      • 135
      • 136
      • 137
      • 138
      • 139
      • 140
      • 141
      • 142
      • 143
      • 144
      • 145
      • 146
      • 147
      • 148
      • 149
      • 150
      • 151
      • 152
      • 153
      • 154
      • 155
      • 156
      • 157
      • 158
      • 159
      • 160
      • 161
      • 162
      • 163
      • 164
      • 165
      • 166
      • 167
      • 168
      • 169
      • 170
    4. 经过以上过程我们的vuex就基本实现了

  • 相关阅读:
    java 企业工程管理系统软件源码 自主研发 工程行业适用
    做个清醒的程序员之努力工作为哪般
    服务器迁移踩的坑
    Python代码大全,海量代码任你下载
    tkinter-TinUI-xml实战(7)PDF分页与合并
    Java方法的使用
    [NCTF2019]Fake XML cookbook XML注入
    MySQL安装—解压版
    重磅博文:可以找我咨询问题了
    Redis面经
  • 原文地址:https://blog.csdn.net/mengweizhao/article/details/127643223