美文网首页
vuex源码解读&简易实现

vuex源码解读&简易实现

作者: 唐井儿_ | 来源:发表于2020-02-06 17:36 被阅读0次

    源码解读

    开始之前,先贴个大纲:

    首先,我们从使用方法入手,一步步来看

    // store.js
    import Vue from 'vue';
    import Vuex from 'vuex';
    
    Vue.use(Vuex);
    
    export default new Vuex.Store({
        state: {
            msg: ''
        },
        getters: {
            msgPlus: state => state.msg + ' plus'
        },
        mutations: {
            setMsg (store, data) {
                store.msg = data;
            }
        },
        actions: {
            asyncChangeMsg ({commit}, data) {
                setTimeout(() => {
                    commit('setMsg', data);
                }, 2000)
            }
        }
    })
    
    
    // main.js
    import Vue from 'vue';
    import App from 'App.vue';
    import store from './store';
    
    new Vue({
        render(h)=> h(App),
        store,
    }).$mount('#app')
    
    
    // App.vue
    <template>
        <div>
            msg: {{$store.state.msg}} <br />
            msgPlus: {{$store.getters.msgPlus}}
        </div>
    </template>
    
    <script>
    export default {
        created () {
            this.$store.dispatch('asyncChangeMsg', 22)
        }
    }
    </script>
    

    接下来

    1. 从store.js文件入手。Vue.use(Vuex): 据官网介绍,此方法用来安装插件,插件必须提供install方法,且会将Vue作为参数传入;
    2. 于是,查看导出vuex的地方,找到./node_modules/vuex/dist/vuex.esm.js,就是源码部分了。注:后缀esm(EcmaScript Module)表示es6模块版;
    3. 此文件底部,查看导出部分,确存在install方法
    var index_esm = {
      Store: Store,
      install: install,
      version: '3.1.2',
      mapState: mapState,
      mapMutations: mapMutations,
      mapGetters: mapGetters,
      mapActions: mapActions,
      createNamespacedHelpers: createNamespacedHelpers
    };
    
    export default index_esm;
    
    1. 查看install实现,传入的参数是真正的Vue
    var Vue; // bind on install
    function install (_Vue) {
      // 防止重复 
      if (Vue && _Vue === Vue) {
        if (process.env.NODE_ENV !== 'production') {
          console.error(
            '[vuex] already installed. Vue.use(Vuex) should be called only once.'
          );
        }
        return
      }
      Vue = _Vue;
      applyMixin(Vue);
    }
    
    1. 上述代码段先是防止重复调用Vue.use方法,随后调用applyMixin方法
    function applyMixin (Vue) {
      var version = Number(Vue.version.split('.')[0]);
      if (version >= 2) {
        // 在每个Vue实例的beforeCreated生命周期时,执行vuexInit方法
        Vue.mixin({ beforeCreate: vuexInit });      
      } else {
          // ...
      }
    }
    
    1. 上述代码段,根据不同vue版本分别处理,这里暂且只看2.x及以上版本。
      Vue.mixin方法:据官网介绍,全局混入,影响注册后创建的每个Vue实例。此处作用是,在每个Vue实例的beforeCreated生命周期时,执行vuexInit方法。
      下边看vuexInit方法做了什么?
    function vuexInit () {
      // this表示该Vue实例,$options是实例初始化选项
      var options = this.$options;
      // store injection
      // 从options中取store,若存在,则赋值到this.$store上;否则,从父级取得$store进行赋值
      // this.$options.parent与this.$parent等价
      if (options.store) {
        this.$store = typeof options.store === 'function'
          ? options.store()
          : options.store;
      } else if (options.parent && options.parent.$store) {
          this.$store = options.parent.$store;
      }
    }
    
    1. 上述代码段,从main.js入口实例Vue时,初始化传入的store开始,一级级传到子组件,于是每个组件上都有了$store属性。
      于是,我们可以在每个组件上,通过this.$store进行操作,例如App.js中的this.$store.state.msg/this.$store.dispatch等

    了解了Vue.use(Vuex)之后,再次回到store.js文件,接下来看实例化Store对象过程中发生了什么?注意一点:

    // 成员访问的优先级高于new,于是先执行Vuex.Store,而后执行实例化
    new Vuex.Store({})
    
    1. 首先贴出Store方法的定义:
    var Store = function Store (options) {
      var this$1 = this;
      if ( options === void 0 ) options = {};
    
      if (!Vue && typeof window !== 'undefined' && window.Vue) {
        install(window.Vue);
      }
    
      if (process.env.NODE_ENV !== 'production') {
        assert(Vue, "must call Vue.use(Vuex) before creating a store instance.");
        assert(typeof Promise !== 'undefined', "vuex requires a Promise polyfill in this browser.");
        assert(this instanceof Store, "store must be called with the new operator.");
      }
    
      var plugins = options.plugins; if ( plugins === void 0 ) plugins = [];
      var strict = options.strict; if ( strict === void 0 ) strict = false;
    
      // store internal state
      this._committing = false;
      this._actions = Object.create(null);
      this._actionSubscribers = [];
      this._mutations = Object.create(null);
      this._wrappedGetters = Object.create(null);
      this._modules = new ModuleCollection(options);
      this._modulesNamespaceMap = Object.create(null);
      this._subscribers = [];
      this._watcherVM = new Vue();
      this._makeLocalGettersCache = Object.create(null);
    
      // bind commit and dispatch to self
      var store = this;
      var ref = this;
      var dispatch = ref.dispatch;
      var commit = ref.commit;
      this.dispatch = function boundDispatch (type, payload) {
        return dispatch.call(store, type, payload)
      };
      this.commit = function boundCommit (type, payload, options) {
        return commit.call(store, type, payload, options)
      };
    
      // strict mode
      this.strict = strict;
    
      var state = this._modules.root.state;
      
      // 模块相关
      installModule(this, state, [], this._modules.root);
    
      resetStoreVM(this, state);
    
      // apply plugins
      plugins.forEach(function (plugin) { return plugin(this$1); });
    
      var useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools;
      if (useDevtools) {
        devtoolPlugin(this);
      }
    };
    
    1. installModule方法为模块相关,暂且不看,先来看看比较重要的resetStoreVM方法:
    function resetStoreVM (store, state, hot) {
      var oldVm = store._vm;
    
      // bind store public getters
      store.getters = {};
      // reset local getters cache
      store._makeLocalGettersCache = Object.create(null);
      
      // _wrappedGetters会收集所有getters方法 -- mynote
      var wrappedGetters = store._wrappedGetters;
      
      // 将getters的方法包装一层后,收集到computed对象中 -- mynote
      // 使用Object.defineProperty注册store.getters,使得每次取值时,从store._vm中取。原因是下方进行new Vue传入computed后,computed的值会放到实例上边 -- mynote
      var computed = {};
      forEachValue(wrappedGetters, function (fn, key) {
        computed[key] = partial(fn, store);
        Object.defineProperty(store.getters, key, {
          get: function () { return store._vm[key]; },
          enumerable: true // for local getters
        });
      });
    
      var silent = Vue.config.silent;
      Vue.config.silent = true;
      
      // 将state值 和 getters值分别传入一个new Vue中初始化成data和computed,使其具有响应式特性    -- mynote
      store._vm = new Vue({
        data: {
          $$state: state
        },
        computed: computed
      });
      // ...
    }
    
    1. 参考mynote部分,总的来说,此方法的作用是,借用Vue,使得state值和getters值具有响应式特征

    再来看看,commit是如何触发mutations的?

    Store.prototype.commit = function commit (_type, _payload, _options) {
      var this$1 = this;
        
      // check object-style commit
      var ref = unifyObjectStyle(_type, _payload, _options);
        var type = ref.type;
        var payload = ref.payload;
        var options = ref.options;
    
      var mutation = { type: type, payload: payload };
      var entry = this._mutations[type];
      if (!entry) {
        if (process.env.NODE_ENV !== 'production') {
          console.error(("[vuex] unknown mutation type: " + type));
        }
        return
      }
      // 批量触发mutation
      this._withCommit(function () {
        entry.forEach(function commitIterator (handler) {
          handler(payload);
        });
      });
      // 通知订阅者
      this._subscribers.forEach(function (sub) { return sub(mutation, this$1.state); });
    
      if (
        process.env.NODE_ENV !== 'production' &&
        options && options.silent
      ) {
        console.warn(
          "[vuex] mutation type: " + type + ". Silent option has been removed. " +
          'Use the filter functionality in the vue-devtools'
        );
      }
    };
    

    简易实现

    // src下,新建vuex.js文件,之前引入vuex的地方改为引用此文件,其他地方无需修改
    let Vue;
    
    class Store {
      constructor (options) {
        this.vm = new Vue({
          data: {state: options.state}
        })
        this.state = this.vm.state;
        this.mutations = options.mutations;
        this.actions = options.actions;
      }
      commit (eventName, data) {
        this.mutations[eventName](this.state, data)
      }
      dispatch (eventName, data) {
        this.actions[eventName](this, data)
      }
    }
    
    const install = (_Vue) => {
      Vue = _Vue;
      Vue.mixin({
        beforeCreate () {
          if (this.$options && this.$options.store) {
            this.$store = this.$options.store;
          } else {
            this.$store = this.$parent && this.$parent.$store;
          }
        }
      })
    }
    
    export default {
      Store,
      install
    }
    

    完整项目可参考

    趁着空闲,记录下学习的知识,有待进一步完善...

    相关文章

      网友评论

          本文标题:vuex源码解读&简易实现

          本文链接:https://www.haomeiwen.com/subject/layyxhtx.html