Skip to content

状态管理与中间件设计模式

Vuex 源码 · Pinia · Axios 拦截器 · Vuex Action 订阅 · Redux 中间件 · Koa 洋葱模型


一、Vuex 源码核心流程

安装(Vue.use):混入 beforeCreate 钩子,保证每个组件通过 this.$store 访问 store

Store 构造函数

new Vuex.Store(options)
  → new ModuleCollection(options)  // 构建模块树
  → installModule()                // 注册所有模块的 mutation/action/getter
  → resetStoreVM()                 // 使 state 响应式,getters 变为计算属性

commit 与 dispatch

  • commit:查找 _mutations[type]_withCommit() 执行 mutation → 通知 subscribers
  • dispatch:查找 _actions[type] → 执行所有 handler → 多个同名 action 用 Promise.all

响应式实现

js
store._vm = new Vue({
  data: { $$state: state },  // state 变为响应式
  computed                    // getters 作为计算属性
})

二、Pinia 状态管理

2021年11月,尤大宣布 Pinia 正式成为 Vue 官方状态库(即 Vuex 5)

Pinia vs Vuex

对比项VuexPinia
mutations去掉,actions 可直接修改 state
模块嵌套 modules无嵌套,通过组合 store 实现
TypeScript支持较弱完整 TS 支持

定义 Store

js
export const useUserStore = defineStore('user', {
  state: () => ({ count: 0, name: 'Jerry' }),
  getters: { doubleCount: (state) => state.count * 2 },
  actions: {
    updateData(newData) {
      this.name = newData.name          // 直接修改
      this.$patch({ count: newData.count })  // 批量修改
    }
  }
})

修改 state 方式:直接修改 store.count++$patch 对象/函数形式、$reset() 重置初始值


三、前端框架中间件机制深度解析

来源:https://juejin.cn/post/6844904039608500237

优秀框架都提供一种插件机制,让开发者可以干预中间环节。本节从 4 个框架的实现解析中间件设计模式。

1. Axios 拦截器(Promise 链)

原理:将每个拦截器构造为 promise.then(resolved, rejected) 的参数,运行时按 Promise 链依次执行。

执行顺序

请求拦截器2 → 请求拦截器1 → axios核心请求 → 响应拦截器1 → 响应拦截器2

精简实现

js
axios.interceptors = { request: [], response: [] };

axios.useRequestInterceptor = (resolved, rejected) => {
  axios.interceptors.request.push({ resolved, rejected });
};

axios.useResponseInterceptor = (resolved, rejected) => {
  axios.interceptors.response.push({ resolved, rejected });
};

axios.run = config => {
  const chain = [{ resolved: axios, rejected: undefined }];

  // 请求拦截器 unshift 到头部(后注册的在前)
  axios.interceptors.request.forEach(interceptor => chain.unshift(interceptor));
  // 响应拦截器 push 到尾部
  axios.interceptors.response.forEach(interceptor => chain.push(interceptor));

  let promise = Promise.resolve(config);
  while (chain.length) {
    const { resolved, rejected } = chain.shift();
    promise = promise.then(resolved, rejected);
  }
  return promise;
};

特点:请求阶段可任意修改 config,响应阶段可灵活处理 response。


2. Vuex Action 订阅(AOP 切面)

原理:提供 before / after 回调,在 action 执行前后插入逻辑(类似 AOP 面向切面编程)。

js
store.subscribeAction({
  before: (action, state) => { console.log(`before action ${action.type}`); },
  after: (action, state) => { console.log(`after action ${action.type}`); }
});

精简实现

js
class Vuex {
  state = {};
  action = {};
  _actionSubscribers = [];

  constructor({ state, action }) {
    this.state = state;
    this.action = action;
  }

  dispatch(action) {
    // 前置监听
    this._actionSubscribers.forEach(sub => sub.before(action, this.state));
    const { type, payload } = action;
    // 执行 action
    this.action[type](this.state, payload).then(() => {
      // 后置监听
      this._actionSubscribers.forEach(sub => sub.after(action, this.state));
    });
  }

  subscribeAction(subscriber) {
    this._actionSubscribers.push(subscriber);
  }
}

设计权衡:Vuex 将 type/payload/state 暴露给外部,但不提供 commit 方法,约束了插件能力(所有 state 修改应通过 mutations)。


3. Redux 中间件(高阶函数组合)

核心:compose 函数

js
function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
// compose(fn1, fn2, fn3)(args) => fn1(fn2(fn3(args)))

本质:用高阶函数不断包装 dispatch,返回强化后的 dispatch。

js
// 中间件示例:日志
const typeLogMiddleware = dispatch => {
  return ({ type, ...args }) => {
    console.log(`type is ${type}`);
    return dispatch({ type, ...args });  // 调用原始 dispatch
  };
};

// createStore 中应用中间件
function createStore(reducer, middlewares) {
  let currentState;
  function dispatch(action) { currentState = reducer(currentState, action); }
  function getState() { return currentState; }
  dispatch({ type: "INIT" });  // 触发初始状态

  let enhancedDispatch = dispatch;
  if (middlewares) {
    enhancedDispatch = compose(...middlewares)(dispatch);
  }
  return { dispatch: enhancedDispatch, getState };
}

执行顺序:中间件从右往左执行(compose(fn1, fn2) → fn1(fn2(dispatch)))


4. Koa 洋葱模型(递归 next)

特点:每个中间件既可掌管请求进入,也可掌管响应返回。外层中间件可影响内层的请求和响应阶段。

核心实现

js
function composeMiddlewares(middlewares) {
  return function wrapMiddlewares(ctx) {
    let index = -1;
    function dispatch(i) {
      index = i;
      const fn = middlewares[i];
      if (!fn) return Promise.resolve();
      return Promise.resolve(
        fn(ctx, () => dispatch(i + 1))  // next = dispatch下一个
      );
    }
    return dispatch(0);
  };
}

class Koa {
  constructor() { this.middlewares = []; }
  use(middleware) { this.middlewares.push(middleware); }
  start({ req }) {
    const composed = composeMiddlewares(this.middlewares);
    const ctx = { req, res: undefined };
    return composed(ctx);
  }
}

洋葱执行流程

中间件1 请求阶段
  → 中间件2 请求阶段
    → 中间件3 业务处理
  ← 中间件2 响应阶段(可拿到 ctx.res)
← 中间件1 响应阶段(可 catch 全局错误)

典型应用

js
// 第1层:全局错误处理
app.use(async (ctx, next) => {
  try { await next(); }
  catch (error) { console.log(`[koa error]: ${error.message}`); }
});

// 第2层:日志中间件
app.use(async (ctx, next) => {
  console.log(`req is ${JSON.stringify(ctx.req)}`);
  await next();
  console.log(`res is ${JSON.stringify(ctx.res)}`);  // next后可拿到业务层写入的ctx.res
});

// 第3层:核心业务
app.use(async (ctx, next) => {
  ctx.res = { code: 200, result: `success` };
  await next();
});

5. 四种中间件机制对比

框架机制核心原理执行模型
Axios拦截器Promise 链式调用线性链(请求→核心→响应)
VuexAction 订阅before/after 回调AOP 切面(前置+后置)
Redux中间件高阶函数 compose 包装 dispatch从右往左嵌套执行
Koa洋葱模型递归 dispatch + async/await洋葱圈(请求进入+响应返回)