原稿:语雀 · 【原理六】vuex 响应式 · 原目录:Vue › 【原理六】vuex 响应式

注入

  1. 首先在 vuex.js 中使用 applyMixin() 进行注入:调用 Vue.mixin(),在所有组件(全局混入)的 beforeCreate 钩子混入了 vuexInit(),因此之后创建的所有 vue 组件都会在 beforeCreate 钩子中执行 vuexInit()
  2. vuexInit() 执行时,会获取当前组件或父组件的 options 中的 $store 对象,当还未创建时则调用 store()
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    //...
  }

  function vuexInit () {
    const options = this.$options
    // store injection
    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
    }
  }
}

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);
  }

  {
    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)
  };

响应式

以上最重要的就是  resetStoreVM(this, state),它实现了 store.state 和 store.getters 的响应式,核心代码如下

var computed = {};
forEachValue(wrappedGetters, function (fn, key) {
  // use computed to leverage its lazy-caching mechanism
  // direct inline function use will lead to closure preserving oldVm.
  // using partial to return function with only arguments preserved in closure environment.
  computed[key] = partial(fn, store);
  Object.defineProperty(store.getters, key, {
    get: function () { return store._vm[key]; },
    enumerable: true // for local getters
  });
});

// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
store._vm = new Vue({
  data: {
    $$state: state
  },
  computed: computed
});

可以看到 store._vm 实际是一个 vue 实例,传入的 state 会成为这个 vue 实例 data 中的 $$state 属性,而 getters 定义为 store._vm 上的计算属性,

Object.defineProperty() 使得 store.getters

keykey

获取到 store._vm 上得计算属性

小结

当使用了 vuex 时 2. vuex 会通过 Vue.mixin() 在全局 beforeCreate() 中注入 vuexInit() 4. 根组件执行 vuexInit() 会调用 store() 创建 store 对象,它相当于状态存放的容器;子组件执行 vueInit() 则会获取父组件 $options 上的 store 对象,不同层级的组件将状态存在一处 store 中 6. 创建 store 时调用 resetStoreVM() 以实现 state 和 getter 的响应式: 8. 创建 vue 实例 store._vm,将 store.state 作为 store._vm 的 $$state 属性 10. 同时将 store.getters 封装为一个 computed 对象作为 store._vm 上的计算属性,利用 defineProperty(),使得 store.getters

keykey

实际被定向到 store._vm 上的计算属性