诗号:六道同坠,魔劫万千,引渡如来。

/img/bdx/yiyeshu-001.jpg

/img/vue3/vue-vuex4.svg

  1. state, 所有子模块的 state 会以模块名嫁接到 rootState 上

    如:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    var root = {
      state: {count: 0},
      actions: {},
      mutations: {},
      // ...
      modules: {
        m1: {
          state: { num: 0 }
        },
        m2: {
          state: { n: 0 }
        }
      }
    }
    
    // 经过 constructor -> installModule 处理之后
    root.state = { count: 0, m1: { num: 0 }, m2: { n: 0 } }
    

    实现部分:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
     function installModule(store, rootState, path, module, hot) {
       const isRoot = !path.length;
       const namespace = store._modules.getNamespace(path);
    
       // ...
    
       // set state 就这一部分针对 state 的处理
       // getNestedState 是个 recude -> state 取出当前 module 的 state 对象
       if (!isRoot && !hot) {
         const parentState = getNestedState(rootState, path.slice(0, -1));
         const moduleName = path[path.length - 1];
         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(
                   "."
                 )}"`
               );
             }
           }
           parentState[moduleName] = module.state;
         });
       }
       // ...
     }
    
  2. actions, 所有子模块的 actions 都会被击中到 this.actions

    如:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    var root = {
      actions: {
        decre: ({ commit }) => commit("decre"),
      },
      modules: {
        m1: {
          actions: {
            decre: ({ commit }) => commit("decrement"),
            incre: ({ commit }) => commit("increment"),
          },
        },
      },
    };
    
    // 经过 constructor -> installModule -> forEachActions 之后
    this.actions = {
      // 0 -> commit decre, 1 -> commit decrement
      decre: [decre1, decre2],
      incre: [incre1], // 0 - commit increment
    };
    

    所以当调用 dispatch decre 的时候 root 和 module m1 的 decre action都会被调用, 且所有的 action 会被封装成一个函数,该函数无论如何都会返回一个 Promise 实例而 在这些 actions 被调用的时候是通过 Promise.all 来调用的,因此无论是async action 还是 sync action 都会当做是 async 来执行。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    
    function installModule(store, rootState, path, module, hot) {
      // ...
      // ... actions 收集部分
      module.forEachAction((action, key) => {
        const type = action.root ? key : namespace + key;
        const handler = action.handler || action;
        registerAction(store, type, handler, local);
      });
      // ...
    }
    
    // registerAction
    function registerAction(store, type, handler, local) {
      const entry = store._actions[type] || (store._actions[type] = []);
      entry.push(function wrappedActionHandler(payload) {
        let res = handler.call(
          store,
          {
            dispatch: local.dispatch,
            commit: local.commit,
            getters: local.getters,
            state: local.state,
            rootGetters: store.getters,
            rootState: store.state,
          },
          payload
        );
        if (!isPromise(res)) {
          // 这里确保了返回值一定是个 promise
          res = Promise.resolve(res);
        }
        if (store._devtoolHook) {
          return res.catch((err) => {
            store._devtoolHook.emit("vuex:error", err);
            throw err;
          });
        } else {
          return res;
        }
      });
    }
    
    // module.js -> forEachAction
    function forEachAction(fn) {
      if (this._rawModule.actions) {
        forEachValue(this._rawModule.actions, fn);
      }
    }
    
  3. dispatch action

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    
     function dispatch(_type, _payload) {
       // check object-style dispatch
       const { type, payload } = unifyObjectStyle(_type, _payload);
    
       const action = { type, payload };
       // 取出所有模块的 type 类型 actions
       const entry = this._actions[type];
       if (!entry) {
         if (__DEV__) {
           console.error(`[vuex] unknown action type: ${type}`);
         }
         return;
       }
    
       // 这里执行 before 钩子函数,在执行 actions 之前做的事情
       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);
         }
       }
    
       // 这里将所有的 actions[type] 放到 Promise.all 中执行,意味着所有的
       // action 无论同步异步的都执行完成了之后才会 settled
       const result =
         entry.length > 1
           ? Promise.all(entry.map((handler) => handler(payload)))
           : entry[0](payload);
    
       // 最后返回 promise 完成 promise combo 链,注意这里面
       // 包含了 after 和 error 两个钩子函数的触发动作
       return new Promise((resolve, reject) => {
         result.then(
           (res) => {
             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) => {
             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);
           }
         );
       });
     }
    
  4. plugins 的用法

    plugins 的注册发生在 installModule 之后,因此在此时可以拿到所有模块的 state 数据,根据用例 modules.spec.js 的使用范例,在插件中可以进行 actions 的 before 和 after 钩子注册。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // store.js -> constructor -> installModule 之后 ->
    
    // 执行插件
    plugins.forEach((plugin) => plugin(this));
    
    // 使用示例, modules.spec.js
    const store = new Vuex.Store({
      actions: {
        [TEST]: () => Promise.resolve(),
      },
      plugins: [
        (store) => {
          store.subscribeAction({
            before: beforeSpy,
            after: afterSpy,
          });
        },
      ],
    });
    
  5. before & after & error hooks

    注册: store.subscribeAction({ before: fn, after: fn, error: fn })

    执行:

    dispatch(type, payload)

    -> sub.before(action, this.state)

    -> Promise.all action

    -> sub.after(action, this.state)/sub.error(action, this.state, error)

    (2) [{…}, Proxy]
      0: {type: "increment", payload: PointerEvent}
      1: Proxy {count: 1, m1: {…}}
      "before"
    (2) [{…}, Proxy]
      0: {type: "increment", payload: PointerEvent}
      1: Proxy {count: 1, m1: {…}}
      "after"
    

    源码调用时机:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
    function dispatch(_type, _payload) {
      // check object-style dispatch
      // ...
    
      try {
        // 1. 执行 before
        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) {
        // ...
      }
    
      // 2. 执行 actions
      const result =
        entry.length > 1
          ? Promise.all(entry.map((handler) => handler(payload)))
          : entry[0](payload);
    
      return new Promise((resolve, reject) => {
        result.then(
          (res) => {
            try {
              // 3. after, 所有 action 执行完成且无异常情况
              this._actionSubscribers
                .filter((sub) => sub.after)
                .forEach((sub) => sub.after(action, this.state));
            } catch (e) {
              /*...*/
            }
            resolve(res);
          },
          (error) => {
            try {
              // 4. error, 如果有 action rejected 会触发 error
              this._actionSubscribers
                .filter((sub) => sub.error)
                .forEach((sub) => sub.error(action, this.state, error));
            } catch (e) {
              /*...*/
            }
            reject(error);
          }
        );
      });
    }
    

从注册和使用两点进行简要分析:

注册阶段:

注册时会将 store 对象用 ModuleCollection 类进行封装,这个类完成与 Module 相关的 注册、注销、和更新操作,而 Module 类完成具体的 children 相关的增删改查操作,而 Store 类是重点部分,里面包含状态相关的操作比如 mutations/actions/getters 等操 作的封装,以及提供 dispatch,commit 等修改状态的函数。

注册时,先注册根模块,然后递归检测 modules 对子模块进行注册,在模块注册过程中主 要有几个步骤: 更新 root state, 收集 actions、mutations、getters ,这里收集的原 则拿 actions 为例,不管是根模块还是子模块也无论模块层级嵌套多深,最后所有的 actions 都可以在实例的 this.actions 中找到。

使用阶段:

使用时的原则是只能通过 dispatch(type, payload) 来派发 ACTION ,内部实现找到对 应 actions 触发执行,而 action 的执行也是通过 commit mutation 来完成,因此使用原 则严格遵守: dispatch -> action -> commit -> mutation -> state ,而不能直接使用 commit 或 mutation 。

在使用是,执行阶段会依次触发 before -> action -> after/error 。

可通过 store.subscribeAction(fn)/* 等于 { before: fn }*/

store.subscribeAction({before:fn, after:fn1, error: fn2})

取注册钩子函数,这个动作可以在 createStore() 之后得到时候直接调用,也可以使用 插件的形式 plugins: [store => store.subscribeAction(fn)] 去完成,因为插件会在 new Store() 构造函数中在 installModule() 安装模块之后得到执行,所以目的和结 果是一 样的。