0
点赞
收藏
分享

微信扫一扫

vuex3源码注释系列 /src/store.js

TiaNa_na 2022-03-16 阅读 30
import applyMixin from "./mixin";
import devtoolPlugin from "./plugins/devtool";
import ModuleCollection from "./module/module-collection";
import { forEachValue, isObject, isPromise, assert, partial } from "./util";

let Vue; // bind on install

/***
 * Vuex 使用的第二步,即 new Vuex.Store( options ) 的数据初始化过程。
 * 1、使用 _modulesNamespaceMap 来收集所有创建的 module 实例。
 * 2、使用 rootState(store._modules.root.state)来收集所有 module 实例的 state 数据。
 *
 * vuex: {
 *  _devtoolHook: devtoolHool //开发者工具
 *  
 * }
 * 
 * 
 * 关于 state:
 * ===>
 * 
 * 关于 getters:
 * ===>
 * 
 * 关于 mutations,actions的收集
 * ===>
 * 
 * 
 *
 * 关于 local 对象:
 * ===> local 表示查找“对应module”的dispatch,commit,getter,state;
 * ===> store 表示查找”root module“的dispatch,commit,getter,state;
 * 1、local 对象相当于 对应module实例的context对象,用于查找对应module的 dispatch,commit,getters,state。
 * 2、用户自定义的 action 方法第一个参数携带的参数为:
 *    {
        dispatch: local.dispatch,
        commit: local.commit,
        getters: local.getters,
        state: local.state,
        rootGetters: store.getters,
        rootState: store.state,
      },
 * 3、用户自定义的 mutation 方法第一个参数携带的参数为:
 * 4、用户自定义的 getter 方法有四个参数,分别是:
 *    (  
 *      local.state, // local state
        local.getters, // local getters
        store.state, // root state
        store.getters // root getters 
      )
 */
export class Store {
  /**
   * 关于 Object.create(null)
   * ===> Object.create(null)没有继承任何原型方法,也就是说它的原型链没有上一层。
   * ===> 相关分析文章: https://juejin.cn/post/6844903589815517192
   */

  constructor(options = {}) {
    // 条件1: !Vue  ==> 如果创建 Vuex.Store() 对象时,没有经过 Vue.use(Vuex)将 Vue 实例保存。
    // 条件2: typeof window !== 'undefined' 表示是浏览器环境。
    // 条件3: window.Vue 表示 window 上有挂在 vue 实例。
    if (!Vue && typeof window !== "undefined" && window.Vue) {
      //那么就使用 window.Vue 来走 vuex.install 的挂载流程。
      install(window.Vue);
    }

    /**
     * __DEV__ 不是一个真实存在的变量。它在JS代码编译阶段,会被一个常量来替换,通常在 development 下是 true,在 production 模式下是 false
     */
    if (__DEV__) {
      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.`
      );
    }

    /**
     * 从 options 中获取 plugins 插件数组, strict 严格模式属性。
     */
    const { plugins = [], strict = false } = options;

    //用来标记是否通过 commit(xxxx, payload) 方式来出发的 state 状态的更新。
    //this._committing 是 store 中的一个内部变量。
    this._committing = false;

    //创建一个 _actions 对象。
    this._actions = Object.create(null);
    this._actionSubscribers = [];

    //创建一个 _mutations 对象。
    this._mutations = Object.create(null);
    //创建一个 _wrappedGetters 对象。

    this._wrappedGetters = Object.create(null);
    //创建 modules 对象。
    /***
     * 1、此时已经将options数据转换成了 module 实例结果。
     *    moduleCollection 结构为: {
     *        root: rootModule
     *    }
     *    module实例结构为: {
     *        runtime: runtime,
     *        state: rawModule.state
     *        _children: {
     *          user: {
     *            runtime: runtime,
     *            state:   对应rawModule.state,
     *            __children: {
     *              xxxxxx
     *            }
     *          },
     *          info: {
     *            runtime: runtime,
     *            state:   对应rawModule.state,
     *            __children: {
     *              xxxxxx
     *            }
     *          }
     *        }
     *    }
     * __modules 指向 moduleCollection 实例。
     */
    this._modules = new ModuleCollection(options);
    //常见命名空间(namespace)map。这个是用来存储已经创建的 module 实例的。
    this._modulesNamespaceMap = Object.create(null);

    //创建订阅者队列。
    this._subscribers = [];
    //vue 实例。但是没有设置数据源
    this._watcherVM = new Vue();
    /**
     * _wrappedGetters
     * _makeLocalGettersCache
     */
    //创建一个 Getters 的缓存
    this._makeLocalGettersCache = Object.create(null);

    //在下面的函数中,函数作用域中的 this 并不指向 store, 所以用了个 store 对象指向本身。 类似于 let that = this;
    const store = this;
    //获取 Store 类中的 dispath, commit 方法。
    const { dispatch, commit } = this;
    //对 store 本身的 dispatch 方法进行装饰增强。主要用于保证 dispatch 内的 this,永远是指向 store。
    this.dispatch = function boundDispatch(type, payload) {
      return dispatch.call(store, type, payload);
    };
    //对 store 本身的 commit 方法进行装饰增强。主要用于保证 commit 内的 this,永远是指向 store。
    this.commit = function boundCommit(type, payload, options) {
      return commit.call(store, type, payload, options);
    };

    //获取严格模式,默认是 false。
    // 可以在 new Vuex.Store( { strict: true } ) 方式来设置
    this.strict = strict;

    //获取根 modules 上的 state 对象。
    const state = this._modules.root.state;

    /**
     * 递归的形式,将 root module,以及所有的 children module 进行初始化和注册。
     * 1、初始化 root module。
     * 2、递归注册所有的 children modules。
     * 3、收集所有 modules 的 getters 放到 this._wrappedGetters 中。
     */
    installModule(this, state, [], this._modules.root);

    /**
     * 初始化 store vm 响应式对象。同时注册 __wrappedGetters 为 vm 的计算属性。
     */
    resetStoreVM(this, state);

    //应用 Vuex.Store( { plugins: [ xxxx, xxx ] } ) 中注册的插件。
    plugins.forEach((plugin) => plugin(this));

    const useDevtools =
      options.devtools !== undefined ? options.devtools : Vue.config.devtools;
    if (useDevtools) {
      devtoolPlugin(this);
    }
  }

  /**
   * store.state的取值
   */
  get state() {
    // resetStoreVM() 方法时,会创建 vue 实例,且会把 state 作为 vue中data的属性;
    //   会把 getters 转换为 computed。
    return this._vm._data.$$state;
  }

  /**
   * 能通过 $store.state 获取属性,但是不能对 $store.state 设置值。
   */
  set state(v) {
    if (__DEV__) {
      assert(
        false,
        `use store.replaceState() to explicit replace store state.`
      );
    }
  }

  /**
   * 被外部调用的 commit 方法。 this.$store.commit( "/user/info/setName", { ... }, options );
   *    第一个参数: store的 namespace 对应的值,用来在 store._mutation 中取 wrappedMutationHandler 方法。
   *    第二个参数: payload 表示携带的数据。
   *    第三个参数: options 已经不再使用。
   *
   * 其中 _type 可以是字符串,也可以是对象。
   *    如果是字符串,则形式为: "/user/infi/setName";
   *    如果是不为null的对象,则去 _type.type 作为 commit 的第一个参数, _type 自身作为第二个参数, payload 作为第三个参数。
   *
   * 1、mutation 对应的订阅队列为: _subscribers。
   * 2、同一个 key, 可以存在多个 mutation 方法。
   */
  commit(_type, _payload, _options) {
    //检查_type的数据类型,如果是字符串,则什么都不处理。
    //   如果是对象,且存在 type.type,则将 type.type 作为type。type转为 payload, payload转为 options。
    const { type, payload, options } = unifyObjectStyle(
      _type,
      _payload,
      _options
    );

    //type就是命名空间名称, payload 携带的数据。
    const mutation = { type, payload };
    //store中通过 installModule 存储的 mutation。 entry 是一个数组,允许相同key,存储多个 mutation 方法。
    const entry = this._mutations[type];
    //当 options 中不存在一个 mutaions 时,则 entry 不会被初始化为 [].
    if (!entry) {
      //如果开发环境下,则报红。
      if (__DEV__) {
        console.error(`[vuex] unknown mutation type: ${type}`);
      }
      return;
    }

    //this._withCommit 主要是提供一个 committing 的环境。用于判断 state 中的属性值,是否是通过 this.$store.commit() 的方式进行更改。
    this._withCommit(() => {
      //遍历执行当前 type 对应的所有 commit 方法。
      entry.forEach(function commitIterator(handler) {
        //定义执行 用户定义的 commit 的包装方法 function wrappedMutationHandler(payload) {}
        //   而在 wrappedMutationHandler 会强制让 this 绑定为 store, 且多传入一个当前 module对一个的 state 对象。
        handler(payload);
      });
    });

    //store中的发布订阅模式下的 订阅函数,会被调用。
    //   第一个参数:mutation 是 { type, payload } 数据对象。
    //   第二个参数是 this._vm.$$data.state。
    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      .forEach((sub) => sub(mutation, this.state));

    //明显在 commit 中传递 options 参数,没有被使用了。
    if (__DEV__ && options && options.silent) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
          "Use the filter functionality in the vue-devtools"
      );
    }
  }

  /**
   * 被外部调用的 dispatch 方法。 this.$store.dispatch( "/user/info/setName", { ... }, options );
   *  第一个参数 _type 也如同 commit 一样,既可以是对象,也可以是字符串。
   *  第二个参数 _payload 表示是携带的参数。
   *
   *
   * 其中 _type 可以是字符串,也可以是对象。
   *    如果是字符串,则形式为: "/user/infi/setName";
   *    如果是不为null的对象,则去 _type.type 作为 commit 的第一个参数, _type 自身作为第二个参数, payload 作为第三个参数。
   *
   *
   * 1、dispatch 对应的订阅队列为 _actionSubscribers。
   * 2、同一个 key, 可以存在多个 action 方法。
   */
  dispatch(_type, _payload) {
    //如果 type 是字符串,则 type = _type; payload = _payload;
    //如果 type 是不为null的对象,则 type=_type.type; payload=_type;
    const { type, payload } = unifyObjectStyle(_type, _payload);

    const action = { type, payload };
    const entry = this._actions[type];
    //如果不存在一个 action 方法,则开发模式下直接报红
    if (!entry) {
      if (__DEV__) {
        console.error(`[vuex] unknown action type: ${type}`);
      }
      return;
    }
    w;
    //判断 action 的订阅对象。
    //  如果订阅对象存在 before 方法, 那么就调用该订阅对象的 before 方法。
    try {
      this._actionSubscribers
        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
        .filter((sub) => sub.before)
        .forEach((sub) => sub.before(action, this.state));
    } catch (e) {
      if (__DEV__) {
        console.warn(`[vuex] error in before action subscribers: `);
        console.error(e);
      }
    }

    //因为 action 都是 promise 方法。所以使用 Promise.all 来保证所有的异步都执行完成,才返回结果。
    //Promise.all 返回的结果是一个 promise 对象。 且 promise.then( res=>{...} ),这个 res是一个数组。
    const result =
      entry.length > 1
        ? Promise.all(entry.map((handler) => handler(payload)))
        : //如果当前key只有一个 mutation,则执行执行。
          entry[0](payload);

    return new Promise((resolve, reject) => {
      //result 是一个 promise,通过 then 用于获取数据结果。
      result.then(
        (res) => {
          //如果对 action 进行了订阅的对象,存在 after 方法,则调用 after 方法。
          try {
            this._actionSubscribers
              .filter((sub) => sub.after)
              .forEach((sub) => sub.after(action, this.state));
          } catch (e) {
            if (__DEV__) {
              console.warn(`[vuex] error in after action subscribers: `);
              console.error(e);
            }
          }
          resolve(res);
        },
        (error) => {
          //如果对 action 进行了订阅的对象,存在 error 方法,在在result产生异常的时候调用 error 方法。
          try {
            this._actionSubscribers
              .filter((sub) => sub.error)
              .forEach((sub) => sub.error(action, this.state, error));
          } catch (e) {
            if (__DEV__) {
              console.warn(`[vuex] error in error action subscribers: `);
              console.error(e);
            }
          }
          reject(error);
        }
      );
    });
  }

  subscribe(fn, options) {
    return genericSubscribe(fn, this._subscribers, options);
  }

  subscribeAction(fn, options) {
    const subs = typeof fn === "function" ? { before: fn } : fn;
    return genericSubscribe(subs, this._actionSubscribers, options);
  }

  watch(getter, cb, options) {
    if (__DEV__) {
      assert(
        typeof getter === "function",
        `store.watch only accepts a function.`
      );
    }
    return this._watcherVM.$watch(
      () => getter(this.state, this.getters),
      cb,
      options
    );
  }

  replaceState(state) {
    this._withCommit(() => {
      this._vm._data.$$state = state;
    });
  }

  /**
   * 注册 Module
   */
  registerModule(path, rawModule, options = {}) {
    if (typeof path === "string") path = [path];

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`);
      assert(
        path.length > 0,
        "cannot register the root module by using registerModule."
      );
    }

    this._modules.register(path, rawModule);
    installModule(
      this,
      this.state,
      path,
      this._modules.get(path),
      options.preserveState
    );
    // reset store to update getters...
    resetStoreVM(this, this.state);
  }

  /*
   * 卸载 Module
   */
  unregisterModule(path) {
    if (typeof path === "string") path = [path];

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`);
    }

    this._modules.unregister(path);
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1));
      Vue.delete(parentState, path[path.length - 1]);
    });
    resetStore(this);
  }

  /*
   * 判断是否有对应路径的 Module
   */
  hasModule(path) {
    if (typeof path === "string") path = [path];

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`);
    }

    return this._modules.isRegistered(path);
  }

  hotUpdate(newOptions) {
    this._modules.update(newOptions);
    resetStore(this, true);
  }

  /*
    利用 js 单线程环境,为某一代码片段之行提供一个环境变量。
      1、当开始 commit 时,则设置 store._committing 为 true。
      2、然后执行 commit() 方法。
      3、执行完成之后,将 store._committing 设置为 false。
  */
  _withCommit(fn) {
    const committing = this._committing;
    this._committing = true;
    fn();
    this._committing = committing;
  }
}


function genericSubscribe(fn, subs, options) {
  if (subs.indexOf(fn) < 0) {
    options && options.prepend ? subs.unshift(fn) : subs.push(fn);
  }
  return () => {
    const i = subs.indexOf(fn);
    if (i > -1) {
      subs.splice(i, 1);
    }
  };
}

function resetStore(store, hot) {
  store._actions = Object.create(null);
  store._mutations = Object.create(null);
  store._wrappedGetters = Object.create(null);
  store._modulesNamespaceMap = Object.create(null);

  const state = store.state;
  // init all modules
  installModule(store, state, [], store._modules.root, true);
  // reset vm
  resetStoreVM(store, state, hot);
}

function resetStoreVM(store, state, hot) {
  //旧的 vm 实例。
  const oldVm = store._vm;

  //初始化 store.getters 对象。
  store.getters = {};
  //初始化本地的 getters 缓存。
  store._makeLocalGettersCache = Object.create(null);
  //_wrappedGetters 存储有所有用户自定义getter的容器。
  const wrappedGetters = store._wrappedGetters;
  //
  const computed = {};

  //fn: 是 wrappedGetters 的 value。
  forEachValue(wrappedGetters, (fn, key) => {
    // 使用 comuted 来利用它的延迟加载机制
    // 直接使用内联函数将会导致闭包保留旧的 vm。
    // 使用 partial 返回只保留在闭包环境中的参数的函数。
    computed[key] = partial(fn, store);
    //在 store.getters 中定义与 wrappedGetters 相同的属性key(key 是一个完整的namespace)。
    Object.defineProperty(store.getters, key, {
      //get方法从 vm 中获取值。
      get: () => 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
  const silent = Vue.config.silent;

  /**
   * 1、Vue.config.silent = true 的作用是用来取消 vue 所有的日志与警告。
   * 2、仅仅在 使用 { data: {}, computed: {}  } 创建 vue 实例的过程中消除日志和警告。
   */
  Vue.config.silent = true;
  //创建vue实例,且 options 中含有 data,与compted。
  //  其中 state,仅仅是 root module 实例的 state 数据。
  store._vm = new Vue({
    data: {
      $$state: state,
    },
    computed,
  });

  Vue.config.silent = silent;

  /*
     如果开启了严格模式,那么会进行非 mutation 修改 state 数据的检查。
  */
  if (store.strict) {
    enableStrictMode(store);
  }

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

/**
 *  installModule() 函数:
 *    第一个参数:store: Vuex.Store() 实例。
 *    第二个参数:rootState: options 中最外层的 state 属性。递归之后,rootState 会包含所有子 Module 的 state。
 *    第三个参数:path:  默认是空数组。递归之后就是保存当前module的key数组。 ["user", "info", "name"]
 *    第四个参数:module: 默认是 this.modules.root; 表示当前的 module。
 */
function installModule(store, rootState, path, module, hot) {
  //如果数组是空,则此时为 root module。如果不为空,则path数组为 祖先-父亲-自己的key组成的数组。
  const isRoot = !path.length;

  //组成一个完整的命名空间名称,如果当前module的options没有配置 { namespace: false },则该module的key不计入 namespace 中。
  //获取命名空间, 比如 path为 ["namespace1", "namespace2", "namespace3"] ==> "/namespace1/namespace2/namespace3"
  const namespace = store._modules.getNamespace(path);

  /**
   * 下面的代码主要是使用 store._modulesNamespaceMap 以完整的 namespace 作为 key, 来收集所有创建的 module 实例。
   */
  // module.namespace 就是 options数据对应的子 rawModule 的属性 namespace,值为 true 或者 false。
  //   如果 module.namespace 值为 true,则表示开启了命名空间。(子 rawModule建议配置 namespace=true)
  if (module.namespaced) {
    //如果当前的 namespace 已经被 _modulesNamespaceMap 收集过,且是开发环境,则报错提示,但是依然采用后面注册的 namespace 的 module, 覆盖之前注册的 module。
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(
        `[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join(
          "/"
        )}`
      );
    }
    //使用 _modulesNamespaceMap 来收集所有创建的 module 实例。这个 namespace 的值为 "/namespace1/namespace2/namespace3";
    //将 module 在 命名空间map 中注册。同一个namespace时,新的覆盖旧的。
    store._modulesNamespaceMap[namespace] = module;
  }
  /***********************************************************************/

  /*
    下面的代码主要是用来将所有 module 的 state 数据收集,并且组成树形结构。
    parentState : {
       //当前module 的 state
       moduleName:  curState: {
          age: xxx,
          name: xxx,
          childModuleName: childState: {
            xxxx
          }
       },
       //父 module 的 state。
       user: {
         xxxx
       }
    }
  */
  //如果当前 module 不是 root module 实例。
  if (!isRoot && !hot) {
    //根据 path 数组的 key,找到父 module 实例的 state。
    const parentState = getNestedState(rootState, path.slice(0, -1));
    //获取当前 module 的 key。
    const moduleName = path[path.length - 1];

    //在 commit 环境去更改 state 属性.
    //1、 执行 Vue.set(parentState, moduleName, module.state); 之前 设置 committing = true;
    //2、 执行 Vue.set(parentState, moduleName, module.state);
    //3、 执行 Vue.set(parentState, moduleName, module.state); 之后 设置 committing = false;
    store._withCommit(() => {
      if (__DEV__) {
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join(
              "."
            )}"`
          );
        }
      }
      /*
       * 将当前module的state数据,响应式的加入到父state。key为模块名。
       * parentState: {
       *   "user": curState
       * }
       */
      Vue.set(parentState, moduleName, module.state);
    });
  }
  /***********************************************************************/

  /*
     module.context: 设置当前 module 的上下文环境。
      ==> 根据 namespace 查找到对应在 store._getters, store._state 中存储的对应方法。
      ==>        以及对应 namespace 的 type, 正确调用 store.dispatch, store.commit。 
     {
        dispatch: ( _type, _payliad, _options ){ ... }
        commit: (_type, _payload, _options){ ... },
        getters: 
     }
  */
  const local = (module.context = makeLocalContext(store, namespace, path));

  /**
   * 当前 Module 实例的 rawModule 数据中的 mutations 对象进行遍历。
   * const namespacedType = namespace + key; 形式为: “/user/info/updateAge”, 其中 updateAge 为 mutation 名。
   */
  module.forEachMutation((mutation, key) => {
    //namespacedType格式为: “/user/info/updateInfo”
    const namespacedType = namespace + key;
    registerMutation(store, namespacedType, mutation, local);
  });

  /*
    当前 Module 实例的 rawModule 数据中的 actions 对象进行遍历。
    {
      actons: {
        updateUserInfo(){
          root: true,
          handler: function(){
            xxxxx
          }
        },
        udpateUserName(){
          xxxxx
        }
      }
    }
  */
  module.forEachAction((action, key) => {
    //如果设置了 action.root 为 true,则直接使用 key 作为 $store._actions 的存储key。
    const type = action.root ? key : namespace + key;
    //如果 action 是一个对象,则从 action.handler 中获取 action 方法。
    //    否则action本身就是一个 function。
    const handler = action.handler || action;
    registerAction(store, type, handler, local);
  });

  /* 
     当前 Module 实例的 rawModule 数据中的 getters 对象进行遍历。
  */
  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key;
    registerGetter(store, namespacedType, getter, local);
  });

  //
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot);
  });
}

/**
 * makeLocalContext: 用于查找指定的 namespace 下能否找到对应的 Module 下的 state, getters, commit, dispatch。
 * store:    new Vuex.Store() 实例。
 * namespace: 当前module转换成的命名空间,比如: “/user/info”。
 * path:      对应为 ["user", "info"]
 */
function makeLocalContext(store, namespace, path) {
  //如果命名空间的长度为空。出现 namespace 为 “” 的两种条件:
  //1、根 module 的 namespace 为空。
  //2、祖-父-自己 的 namespace 都是为空。
  const noNamespace = namespace === "";

  /**
   * 1、actions, mutations 都是通过 namespace 作为 key 被 store._actions, store._mutations 存储。
   *    所以如果 namespaces 为空,则当作属性直接存在了 store._action, store._mutations 中, 而不是 _children 中。
   */
  const local = {
    dispatch: noNamespace
      ? store.dispatch
      : /*
          参数: 外部传入
        */
        (_type, _payload, _options) => {
          //对传入的 _type 进行处理,如果是字符串就不处理;
          //    如果 _type 是不为null的对象,则取 _type.type 作为 key。
          const args = unifyObjectStyle(_type, _payload, _options);
          const { payload, options } = args;
          //获取外部传入的 key。
          let { type } = args;

          // options 不存在或者 options.root 不存在的时候,就将 key 转化为 完整的命名空间名称。
          if (!options || !options.root) {
            //如果是通过 local.dispatch, 或者 this.context.dispatch ,则 path 是当前的module对应的key就行。
            type = namespace + type;
            //如果是开发环境,且 store._actions 中不存在对应的 key-value.
            if (__DEV__ && !store._actions[type]) {
              console.error(
                `[vuex] unknown local action type: ${args.type}, global type: ${type}`
              );
              return;
            }
          }

          //主要是将通过 local.dispatch, this.context.dispatch 方式调用 action 的行为矫正。
          //将type转化为正确的 namespace,来查找 store._actions 中的 wrappedActionHandler
          return store.dispatch(type, payload);
        },

    commit: noNamespace
      ? store.commit
      : (_type, _payload, _options) => {
          //对传入的 _type 进行处理,如果是字符串就不处理;
          // 如果 _type 是不为null的对象,则取 _type.type 作为 key。
          const args = unifyObjectStyle(_type, _payload, _options);

          const { payload, options } = args;
          let { type } = args;

          // options 不存在或者 options.root 不存在的时候,就将 key 转化为 完整的命名空间名称。
          if (!options || !options.root) {
            //如果是通过 local.dispatch, 或者 this.context.dispatch ,则 path 是当前的module对应的key就行。
            type = namespace + type;
            //如果是开发环境,且 store._mutations 中不存在对应的 key-value.
            if (__DEV__ && !store._mutations[type]) {
              console.error(
                `[vuex] unknown local mutation type: ${args.type}, global type: ${type}`
              );
              return;
            }
          }
          //主要是将通过 local.commit, this.context.commit 方式调用 action 的行为矫正。
          //将 type 转化为正确的 namespace,来查找 store._mutation 中的 wrappedMutationHandler
          store.commit(type, payload, options);
        },
  };

  /**
   * getters 和 state 必须是懒加载方式获取的,因为它们将会在 vm 更新的时候被改变。
   */
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace),
    },
    state: {
      /*
         因为 state 的数据是没有被收集到 store 的,所以只能够通过 path 来逐级查找。
         installModule(){} 方法中只搜集了所有module中的 mutations, actions, getters, 并没有 state.

         也就是说: 找子 module 的 state 数据,只能通过 path 来找。
                  找子 module 的 getters, mutations, actions 数据,则通过 namespace 来找。
      */
      get: () => getNestedState(store.state, path),
    },
  });
  return local;
}

function makeLocalGetters(store, namespace) {
  //_makeLocalGettersCache 中不存在该 namespace 的 getter。
  if (!store._makeLocalGettersCache[namespace]) {
    const gettersProxy = {};
    const splitPos = namespace.length;
    Object.keys(store.getters).forEach((type) => {
      //
      if (type.slice(0, splitPos) !== namespace) return;

      // extract local getter type
      const localType = type.slice(splitPos);

      // Add a port to the getters proxy.
      // Define as getter property because
      // we do not want to evaluate the getters in this time.
      Object.defineProperty(gettersProxy, localType, {
        get: () => store.getters[type],
        enumerable: true,
      });
    });
    store._makeLocalGettersCache[namespace] = gettersProxy;
  }

  return store._makeLocalGettersCache[namespace];
}

/**
 * 1、将 mutation 注册到 stote._mutations 中, store._mutations 是一个 map 对象。
 * 2、用户定义的 mutation(){} 的第一个参数,只有当前子 module 对应的 state。
 * 第一个参数:store: 指的是 new Vuex.Store() 实例。
 * 第二个参数:type:   为 namespacedType, 形式为: “/user/info/updateAge”。
 * 第三个参数:handler: mutations 中属性对应的 value,就 mutation 函数。
 * 第四个参数:local:
 *
 */
function registerMutation(store, type, handler, local) {
  //1、store._mutations 是一个 map。
  //2、store._mutations 中的 key 对应的 value,是一个数组。表明可以允许多个
  //    同名 key 的 mutation 函数存在。
  //3、用户自定义的 mutation 方法,会被装饰增强为
  //     function wrappedMutationHandler( payload ){ ... }
  //store._mutations["/user/info/updateAge"] 的值是个数组。
  const entry = store._mutations[type] || (store._mutations[type] = []);
  //对用户定义的 mutation 进行装饰,保证 mutation 内部的this,一定指向 store 实例。
  /*
     我们定义的mutation,在被调用的时候,只能接受一个参数。
     payload,负载,表示要传递过来的参数。
   */
  entry.push(function wrappedMutationHandler(payload) {
    //mutation中函数的第一个参数是 state, 第二个参数是 payload。
    handler.call(store, local.state, payload);
  });
}

/***
 * 1、将 Action 注册到 store._actions 中,其中 store._actions 是一个 map。
 * 2、如果 action 是一个对象,且 root=true,则key,就不是完整命名空间的名称,而是“/”的最后一截。
 * 3、用户定义的 action, 会被包装为 function wrappedActionHandler(payload){...}。
 * 4、用户定义的 action(){ }, 第一个参数包含如下属性:
 *      { 本module的   dispatch,
 *        本module的   commit,
 *        本module的   getters,
 *        本module的   state,
 *        rootModule的 getters,
 *        rootModule的 state
 *      }
 *    第二个参数 payload 为 数据。
 */
function registerAction(store, type, handler, local) {
  //如果 store._actions[type] 不存在,则初始化为 []。
  const entry = store._actions[type] || (store._actions[type] = []);
  //同一个key,可以存在多个 action 方法。
  entry.push(function wrappedActionHandler(payload) {
    //用户自定义的 action 开始执行。
    let res = handler.call(
      store,
      {
        dispatch: local.dispatch,
        commit: local.commit,
        getters: local.getters,
        state: local.state,
        rootGetters: store.getters,
        rootState: store.state,
      },
      payload
    );

    //如果当前的 action 不是一个 promise 方法(即异步方法)。
    if (!isPromise(res)) {
      //则包装成为一个 promise 对象。
      res = Promise.resolve(res);
    }

    // devtool 工具出发 vuex:error
    if (store._devtoolHook) {
      //则promiseshying catch 方法进行异常捕获处理。
      return res.catch((err) => {
        store._devtoolHook.emit("vuex:error", err);
        throw err;
      });
    } else {
      //将 mutation 调用的结果返回,返回的结果是个 promise 对象。
      return res;
    }
  });
}

/*
  1、相同key的mutation可以注册多个,但是getter的只允许最先的注册。
  2、用户定义的getter,实际上接受了四个参数。{
      本module的state,
      本module的getters,
      根module的state,
      根module的getters
  }
  3、用户定义的getter,会被包装为 function wrappedGetter(){ ... }
*/
function registerGetter(store, type, rawGetter, local) {
  //判断是否重复注册 getter。
  if (store._wrappedGetters[type]) {
    if (__DEV__) {
      console.error(`[vuex] duplicate getter key: ${type}`);
    }
    return;
  }
  //将所有的 getter 全部收集到 _wrappedGetters 对象中。
  store._wrappedGetters[type] = function wrappedGetter(store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    );
  };
}

/*
  watch对象的建立:
  1、第一个参数: 要监听的数据,即 state。
  2、第二个参数: 当数据发生变化时,会触发该方法的调用;如果此时 _committing 属性为 false,则没有根据单向数据流的方式进行数据更新。
  3、第三个参数: 配置参数。 deep=true,表示监听 state 中的所有属性以及子孙属性。
*/
function enableStrictMode(store) {
  //新增加一个观察者
  store._vm.$watch(
    //第一个参数就是 state 数据。
    function () {
      return this._data.$$state;
    },
    //第二个参数
    () => {
      //如果是开发者模式,如果 store._committing 为false,则报红。
      if (__DEV__) {
        assert(
          store._committing,
          `do not mutate vuex store state outside mutation handlers.`
        );
      }
    },

    //深度监听 state 对象的所有属性,以及子属性。
    { deep: true, sync: true }
  );
}

/**
 * 获取嵌套的 state
 * path 去除了最后一项。
 *
 * ["namespace1", "namespace2", "namespace3"].reduce( ( state, key )=>state[key] )
 */
function getNestedState(state, path) {
  return path.reduce((state, key) => state[key], state);
}

/*
  unifyObjectStyle: 将对象类型进行统一。

*/
function unifyObjectStyle(type, payload, options) {
  //如果type是不为null的对象类型。(字符串的类型就是string),则去type.type 作为命名空间字符串。
  if (isObject(type) && type.type) {
    //则将type认为是 携带的数据 payload。
    options = payload;
    //payload 则认为是 配置参数 options。
    payload = type;
    //将 type.type 作为命名空间。
    type = type.type;
  }

  //开发模式下,判断如果 type 不是字符串类型,则报警告提示type不是string类型。
  if (__DEV__) {
    assert(
      typeof type === "string",
      `expects string as the type, but found ${typeof type}.`
    );
  }

  return { type, payload, options };
}

/***
 * Vuex 走的第一步,就是这个方法,通过 Vue.use( vuex ) 时,会调用 vuex.install( _Vue ) 方法。
 */
export function install(_Vue) {
  //对同一个vue进行重复使用了 vue.use(Vuex), 开发环境内报错误提示。
  if (Vue && _Vue === Vue) {
    if (__DEV__) {
      console.error(
        "[vuex] already installed. Vue.use(Vuex) should be called only once."
      );
    }
    return;
  }

  //在 Vuex.Store 中记录 Vue 类对象。
  Vue = _Vue;
  //在每一个 vue 实例被创建的时候,混合一个 Vuex.Store() 实例。
  //对应位置为 ./mixin.js 文件。
  applyMixin(Vue);
}

举报

相关推荐

Dubbo 3.x源码分析系列 - 基础篇

0 条评论