关于javascript:Vuex源码解析

9次阅读

共计 9282 个字符,预计需要花费 24 分钟才能阅读完成。

1. 通过 Vue.use 办法调用 Vuex 中 install 装置 Vuex。而后创立 Vuex.Store 实例,并将实例增加根组件选项中。

Vue.use(Vuex);

const store = new Vuex.Store({
  actions,
  getters,
  state,
  mutations,
});

root = new Vue({
  el: "#app",
  store,
  template: "<App />",
  components: {App},
});

2. 装置 Vuex 作用是在组件 beforeCreate 生命周期中调用 vuexInit 办法,vuexInit 办法会将根组件中的 Store 实例绑定到每个组件实例上。能够通过 this.&dollar;store 获取 store 组件实例。

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;// 缓存 Vue 到以后模块
  applyMixin(Vue);// 混入 vuex
}

function applyMixin (Vue) { // 增加 vuexInit 办法到 vue 生命周期中 beforeCreate
  var version = Number(Vue.version.split('.')[0]);// 获取版本号

  if (version >= 2) {Vue.mixin({ beforeCreate: vuexInit});// 混入到组件的生命周期中(全局混入)} else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    var _init = Vue.prototype._init;
    Vue.prototype._init = function (options) {if ( options === void 0) options = {};

      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit;
      _init.call(this, options);
    };
  }

  function vuexInit () { // 将 Vuex.Store 实例增加到组件实例上(每个组件都会在 beforeCreate 中执行)var options = this.$options;
    // store injection
    if (options.store) {// 根组件,前提是 Store 实例增加到根组件实例上
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store;
    } else if (options.parent && options.parent.$store) {// 将 Vuex.Store 实例增加到子组件实例上
      this.$store = options.parent.$store;
    }
  }

3.installModule;resetStoreVM;

var Store = function Store(options) {
  ...
  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); // 缓存 actions
  this._actionSubscribers = []; // 缓存监听 action 事件
  this._mutations = Object.create(null); // 缓存 mutation
  this._wrappedGetters = Object.create(null); // 缓存 getter
  this._modules = new ModuleCollection(options); // 创立 ModuleCollection 实例,并将模块造成树形构造,节点为 Module 实例,将根模块实例保留在 root 属性上。this._modulesNamespaceMap = Object.create(null); // 寄存 module 实例
  this._subscribers = []; // 缓存监听 mutation 事件
  this._watcherVM = new Vue(); // 提供 vue.prototype.$watch 办法
  this._makeLocalGettersCache = Object.create(null); // 依照命名空间缓存 getters 对象

  // 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) {
    // 在 store 实例上增加 dispatch 办法
    return dispatch.call(store, type, payload);
  };
  this.commit = function boundCommit(type, payload, options) {
    // 在 store 实例上增加 commit 办法
    return commit.call(store, type, payload, options);
  };

  // strict mode
  this.strict = strict;

  var state = this._modules.root.state;

  // init root module.
  // this also recursively registers all sub-modules
  // and collects all module getters inside this._wrappedGetters
  installModule(this, state, [], this._modules.root);// 装置模块

  // initialize the store vm, which is responsible for the reactivity
  // (also registers _wrappedGetters as computed properties)
  resetStoreVM(this, state); // 设置响应式
  ...
};

4.installModule 装置模块;将子模块 state 增加到父模块 state 上,属性名为模块名;封装 mutation,action,getter,绑定对应的模块参数,并通过命名空间和名称绑定到 store 上。而后递归模块,实现子模块的装置。

function installModule (store, rootState, path, module, hot) {
  var isRoot = !path.length;
  var namespace = store._modules.getNamespace(path);// 返回字符串父子模块以“/”宰割,根模块返回空字符串

  // register in namespace map
  if (module.namespaced) {if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {console.error(("[vuex] duplicate namespace" + namespace + "for the namespaced module" + (path.join('/'))));
    }
    store._modulesNamespaceMap[namespace] = module;// 缓存非根模块且带有命名空间的模块映射
  }

  // set state
  if (!isRoot && !hot) {var parentState = getNestedState(rootState, path.slice(0, -1));
    var moduleName = path[path.length - 1];
    store._withCommit(function () {if (process.env.NODE_ENV !== 'production') {if (moduleName in parentState) { // 父模块 state 中不能呈现和子模块名雷同的属性
          console.warn(("[vuex] state field \"" + moduleName + "\" was overridden by a module with the same name at \""+ (path.join('.')) +"\"")
          );
        }
      }
      Vue.set(parentState, moduleName, module.state);// 以将子模块 state 增加到付模块 state 上,属性名为模块名
    });
  }

  var local = module.context = makeLocalContext(store, namespace, path);// 返回对象,蕴含 dispatch、commit,state,getter, 并保留在 module 属性上。module.forEachMutation(function (mutation, key) {
    var namespacedType = namespace + key;
    registerMutation(store, namespacedType, mutation, local);// 将原生 mutaions 作为回调,绑定参数, 只有本身模块的 state,绑定在 store 上
  });

  module.forEachAction(function (action, key) {
    var type = action.root ? key : namespace + key;
    var handler = action.handler || action;
    registerAction(store, type, handler, local);// 将原生 action 作为回调,绑定参数, 蕴含根模块数据和办法,保障 action 返回 promise,绑定在 store 上
  });

  module.forEachGetter(function (getter, key) {
    var namespacedType = namespace + key;
    registerGetter(store, namespacedType, getter, local);// 将原生 rawGetter 作为回调,绑定参数, 蕴含根模块 state 和 getter,绑定在 store 上
  });

  module.forEachChild(function (child, key) {// 递归装置子模块
    installModule(store, rootState, path.concat(key), child, hot);
  });
}

5.resetStoreVM。利用 Vue 响应式以及 computed 属性实现 vuex 的 state 响应式和 getter 性能。在 store 严格模式下,当不通过 commit 批改 state 值时,输入正告提醒。

定义 store.getters 对象属性的 get 办法,代理拜访存在 store._vm 组件实例上数据。

function resetStoreVM (store, state, hot) {
  var oldVm = store._vm;

  // bind store public getters
  store.getters = {};
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null);
  var wrappedGetters = store._wrappedGetters;
  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);// 封装 fn,将 store 作为 fn 参数
    Object.defineProperty(store.getters, key, {// 将 getter 代理到 store.getter 上,理论是从 store._vm 获取值
      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
  var silent = Vue.config.silent;
  Vue.config.silent = true;
  store._vm = new Vue({// 利用 vue 实现 data 和 getter 的响应式,将 state 和 getter 绑定到 store._vm 上
    data: {$$state: state},
    computed: computed
  });
  Vue.config.silent = silent;

  // enable strict mode for new vm
  if (store.strict) {enableStrictMode(store);// 当不通过 mutate 批改值时提醒谬误,通过 vue.$watch 办法创立 watcher,当值发生变化时告诉提醒 (store._committing 为 true 时不提醒)
  }

  if (oldVm) {if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(function () {oldVm._data.$$state = null;});
    }
    Vue.nextTick(function () {return oldVm.$destroy(); });
  }
}

6.vuex 提供了 mapState,mapMutation,mapGetters,mapActions 办法。

7.mapState 将会返回一个对象,对象的属性值对应一个办法,在办法中读取 state 和 getters,并在调用后返回对应的值。

var mapState = normalizeNamespace(function (namespace, states) {var res = {};
  if (process.env.NODE_ENV !== 'production' && !isValidMap(states)) {console.error('[vuex] mapState: mapper parameter must be either an Array or an Object');
  }
  normalizeMap(states).forEach(function (ref) {
    var key = ref.key;
    var val = ref.val;

    res[key] = function mappedState () {
      var state = this.$store.state;
      var getters = this.$store.getters;
      if (namespace) {var module = getModuleByNamespace(this.$store, 'mapState', namespace);
        if (!module) {return}
        state = module.context.state;
        getters = module.context.getters;
      }
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    };
    // mark vuex getter for devtools
    res[key].vuex = true;
  });
  return res
});

8.mapGetters 与 state 相似,对象办法返回对应 getters 值。

var mapGetters = normalizeNamespace(function (namespace, getters) {var res = {};
  if (process.env.NODE_ENV !== 'production' && !isValidMap(getters)) {console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object');
  }
  normalizeMap(getters).forEach(function (ref) {
    var key = ref.key;
    var val = ref.val;

    // The namespace has been mutated by normalizeNamespace
    val = namespace + val;
    res[key] = function mappedGetter () {if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {return}
      if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {console.error(("[vuex] unknown getter:" + val));
        return
      }
      return this.$store.getters[val]
    };
    // mark vuex getter for devtools
    res[key].vuex = true;
  });
  return res
});

9.mapMutations 返回对象,对象的属性值对应一个办法,调用该办法时执行对应模块的 commit(commit 绑定了对模块的参数)办法。

var mapMutations = normalizeNamespace(function (namespace, mutations) {var res = {};
  if (process.env.NODE_ENV !== 'production' && !isValidMap(mutations)) {console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object');
  }
  normalizeMap(mutations).forEach(function (ref) {
    var key = ref.key;
    var val = ref.val;

    res[key] = function mappedMutation () {var args = [], len = arguments.length;
      while (len--) args[len] = arguments[len];

      // Get the commit method from store
      var commit = this.$store.commit;
      if (namespace) {var module = getModuleByNamespace(this.$store, 'mapMutations', namespace);
        if (!module) {return}
        commit = module.context.commit;
      }
      return typeof val === 'function'
        ? val.apply(this, [commit].concat(args))
        : commit.apply(this.$store, [val].concat(args))
    };
  });
  return res
});

10.mapAction 与 mapMutations 相似,返回对象,对象属性值办法调用时,执行模块的 dispatch 办法并返回。

var mapActions = normalizeNamespace(function (namespace, actions) {var res = {};
  if (process.env.NODE_ENV !== 'production' && !isValidMap(actions)) {console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object');
  }
  normalizeMap(actions).forEach(function (ref) {
    var key = ref.key;
    var val = ref.val;// 被映射的 action 办法名(重命名调用),也可能是函数,第一个参数为 dispatch 办法,第二个参数是调用时传入的其余参数,能够实现在同一个办法中调用不同的 action

    res[key] = function mappedAction () {var args = [], len = arguments.length;
      while (len--) args[len] = arguments[len];

      // get dispatch function from store
      var dispatch = this.$store.dispatch;
      if (namespace) {var module = getModuleByNamespace(this.$store, 'mapActions', namespace);
        if (!module) {return}
        dispatch = module.context.dispatch;
      }
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    };
  });
  return res
});

11.Store 提供 subscribe 监听 state 值的变动,监听事件保留在实例_subscribers 变量上,当调用了 commit 更改 state 时,顺次调用监听事件。action 订阅实现和 state 订阅相似。

正文完
 0