Appearance
状态管理与中间件设计模式
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:
| 对比项 | Vuex | Pinia |
|---|---|---|
| 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() 重置初始值
三、前端框架中间件机制深度解析
优秀框架都提供一种插件机制,让开发者可以干预中间环节。本节从 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 链式调用 | 线性链(请求→核心→响应) |
| Vuex | Action 订阅 | before/after 回调 | AOP 切面(前置+后置) |
| Redux | 中间件 | 高阶函数 compose 包装 dispatch | 从右往左嵌套执行 |
| Koa | 洋葱模型 | 递归 dispatch + async/await | 洋葱圈(请求进入+响应返回) |