数据获取 API root state 的获取是从 store.state 获取,如果有 module,那么就从 store.state.a 中获取,其中 a 是 module 的 key 获取 module 的数据本质上通过 module 名的 path 去访问到当前 module 的 state 一般获取 state 是通过 store.getters 方法,执行 store.getters,…

数据获取 API

  1. root state 的获取是从 store.state 获取,如果有 module,那么就从 store.state.a 中获取,其中 a 是 module 的 key
  2. 获取 module 的数据本质上通过 module 名的 path 去访问到当前 module 的 state
  3. 一般获取 state 是通过 store.getters 方法,执行 store.getters,实际上执行 rawGetter 方法,其中 rawGetter 就是在 store.getters 定义的方法
// getter的注册是在installModule阶段
function registerGetter(store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    return;
  }
  store._wrappedGetters[type] = function wrappedGetter(store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    );
  };
}

数据存储 API

  • mutation
    1. vuex 中存储数据是对 state 的更改,vuex 显式的更改 state,只有 mutation 一种形式
    2. mutation 的初始化是在 installModule 阶段
    3. wrappedMutationHandler 关于执行定义的 mutation 函数
    4. mutation 必须是同步函数
function registerMutation(store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = []);
  entry.push(function wrappedMutationHandler(payload) {
    handler.call(store, local.state, payload);
  });
}
  • action
    1. 因为 mutation 必须是同步函数,如果需要异步更改 state,store 中有一个 actions,提供了异步更改 state
    2. actions 本质是提交的 mutation,mutation 再 commit,所以 actions 中可以是任何异步操作
    3. actions 的注册过程是在 installModule 阶段
function registerAction(store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = []);
  entry.push(function wrappedActionHandler(payload, cb) {
    let res = handler.call(
      store,
      {
        dispatch: local.dispatch,
        commit: local.commit,
        getters: local.getters,
        state: local.state,
        rootGetters: store.getters,
        rootState: store.state
      },
      payload,
      cb
    );
  });
}
  • vuex 中提供了两种信号机制用于修改 state
    1. commit 触发的是 mutation 回调,dispatch 触发的是 action 回调
    2. commit 无返回值,而 dispatch 返回的是一个 Promise
export default class Store {
  commit(_type, _payload, _options) {
    const { type, payload, options } = unifyObjectStyle(_type, _payload, _options);
    const mutation = { type, payload };
    const entry = this._mutations[type];
    this._withCommit(() => {
      entry.forEach(function commitIterator(handler) {
        handler(payload);
      });
    });
    this._subscribers.forEach((sub) => sub(mutation, this.state));
  }

  dispatch(_type, _payload) {
    const { type, payload } = unifyObjectStyle(_type, _payload);
    const action = { type, payload };
    const entry = this._actions[type];
    this._actionSubscribers.forEach((sub) => sub(action, this.state));
    return entry.length > 1 ? Promise.all(entry.map((handler) => handler(payload))) : entry[0](payload);
  }
}

语法糖 API

  • mapGetters
    1. mapGetter 访问的是$store 实例上的 getters 属性,也就是 this.$store.getters[val],在 vue 组件中可以放在计算属性中
    2. 每个 getters 支持 namespace,用于获取指定 module 的 getters
export const mapGetters = normalizeNamespace((namespace, getters) => {
  const res = {};
  normalizeMap(getters).forEach(({ key, val }) => {
    val = namespace + val;
    res[key] = function mappedGetter() {
      return this.$store.getters[val];
    };
    res[key].vuex = true;
  });
  return res;
});
  • mapMutations
    1. mapMutations 支持传入额外的参数,作为提交 mutation 的 payload,最终执行的是 store.commit 方法
    2. 每一个 commit 对应一个 namespace,用作指定的 module 的 commit
export const mapMutations = normalizeNamespace((namespace, mutations) => {
  const res = {};
  normalizeMap(mutations).forEach(({ key, val }) => {
    res[key] = function mappedMutation(...args) {
      let commit = this.$store.commit;
      if (namespace) {
        const 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;
});
  • mapActions
    1. mapActions 与 mapMutations 相似,知识把 commit 变成了 dispatch
    2. 同样也支持 namespace,用户访问指定 module 的 dispatch
export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {};
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction(...args) {
      let dispatch = this.$store.dispatch;
      if (namespace) {
        const module = getModuleByNamespace(this.$store, "mapActions", namespace);
        dispatch = module.context.dispatch;
      }
      return typeof val === "function" ? val.apply(this, [dispatch].concat(args)) : dispatch.apply(this.$store, [val].concat(args));
    };
  });
  return res;
});

动态注入模块 API

  1. 用于动态注入模块,主要是通过 resetStoreVM 重新实例化 store._vm
registerModule (path, rawModule, options = {}) {
  resetStoreVM(this, this.state)
}
function resetStoreVM(store, state, hot) {
  store.getters = {};
  const wrappedGetters = store._wrappedGetters;
  const computed = {};
  // 遍历getter配置后,生成computed属性
  forEachValue(wrappedGetters, (fn, key) => {
    computed[key] = () => fn(store);
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true
    });
  });
  const silent = Vue.config.silent;
  Vue.config.silent = true;
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  });
  Vue.config.silent = silent;
}

插件

  1. 在 src/plugins 有两个文件 logger.js 和 devtool.js,
  2. logger.js 提供了一个日志打印功能,在控制台打印何时触发什么方法
  3. devtool.js 提供了 1、触发vuex组件初始化的hook;2、提供“时空穿梭”:state操作的前进后退;3、mutation被执行时,提供被触发的mutation函数和当前state状态
const devtoolHook = typeof window !== "undefined" && window.__VUE_DEVTOOLS_GLOBAL_HOOK__;

export default function devtoolPlugin(store) {
  // ...
  // 触发vuex组件初始化的hook
  devtoolHook.emit("vuex:init", store);
  // 提供“时空穿梭”:state操作的前进后退
  devtoolHook.on("vuex:travel-to-state", (targetState) => {
    store.replaceState(targetState);
  });
  // mutation被执行时,提供被触发的mutation函数和当前state状态
  store.subscribe((mutation, state) => {
    devtoolHook.emit("vuex:mutation", mutation, state);
  });
}

对 vuex 的一些思考

  1. vuex 对外提供的一些方法和机制
    1. this.$store.dispatch:触发 actions 的方法,返回 promise
    2. this.$store.commit: 触发 mutation 的方法,无返回值
    3. state: 存储组件状态,挂载在 vue 中的 data 中,是一个响应式对象
    4. getters: state 的读取方式,在读取的时候,可以修改 state
    5. actions: 本质是触发 commit,commit 再出触发 mutation,其中 actions 里面可以做任何异步操作
    6. mutation: 推荐修改 state 唯一方法
  2. vuex 是的 store 是如何注入的

    通过 mixin 的方式把 store 注入到 vue 的$store中,在组件中可以使用this.$store 访问到 store

  3. vuex 中的设计最精巧的部分是什么

    vuex 中最巧妙的是 module 的实现,为了保证 store 的不臃肿,vuex 提出了 module 的概念,即把 store 分成若干个 module,每一个 module 可以看做是一个 store,拥有自己的 state,getters,mutation,actions,甚至子 module,并且给每一个模块根据 path 拼接一个 namespace,和设置 makelocalContext 本地上下文,保证了 module 的独立性,访问 module 的 state 时,要根据 namespace 访问

  4. vuex 如何区分是直接修改 state 还是通过 mutation 修改 state

    在严格模式下会有一个 watcher 去观测 state,直接修改会报警告,直接修改并没有修改 store._committing,所以判断_committing 如果为 false 则为直接修改,为 true 则说明是通过 mutation 修改

  5. “时空穿梭”的插件的原理

    时空穿梭的原理是先提前缓存下 state change,然后使用 store.replaceState(targetState)方法,最终执行 this._vm._data.$$state = targetState,把当前 state 替换成缓存中的某个时刻的 state

  6. vuex 有何“缺陷”
    1. actions 不易理解,刚接触 vuex 会认为 actions 和 mutations 更改 state 的原理一致,实际上 actions 最终走的还是 mutations,这难免容易产生误导
    2. vuex 推荐使用 mutation 修改 state,但是除 mutation 外,在非严格模式,还可以直接绕开 mutations 修改 state,比如在 vue 组件中通过 this.$store.state.[name]更改 state,在工作中,我也发现很多人这样做

暂无评论