说说对 Redux 中间件的理解?常用的中间件有哪些?
一、是什么
Redux 中间件是在 dispatch 一个 action 到 action 到达 reducer 之间的扩展点。它提供了一种机制来拦截、修改、延迟、替换或副作用化 action 的处理过程。
dispatch(action) → Middleware1 → Middleware2 → ... → Reducer → newState
没有中间件时,dispatch(action) 会同步地将 action 传给 reducer。中间件打破了这个同步约束,让我们可以在这个链路中做任何事情:打日志、发异步请求、条件拦截等。
二、中间件的函数签名
Redux 中间件采用三层柯里化(curried function)结构:
const myMiddleware = (storeAPI) => (next) => (action) => {
// storeAPI: { getState, dispatch }
// next: 调用链中的下一个中间件(或 reducer)
// action: 被 dispatch 的 action
// 在 action 到达 reducer 之前做事
console.log('dispatching', action);
// 调用下一个中间件
const result = next(action);
// action 已经到达 reducer 之后做事
console.log('next state', storeAPI.getState());
return result;
};
三个参数的含义:
- storeAPI:包含
getState() 和 dispatch(),可以读取当前状态或 dispatch 新的 action
- next:调用链中下一个中间件的 dispatch 函数;如果是最后一个中间件,则指向原始的
store.dispatch
- action:当前被 dispatch 的 action 对象
三、中间件的执行原理
多个中间件通过 applyMiddleware 或 RTK 的 configureStore 组合形成一条链。执行过程类似洋葱模型:
dispatch(action)
│
▼
┌─── Middleware A(前半段)───┐
│ ┌─── Middleware B(前半段)───┐
│ │ ┌─── Middleware C(前半段)───┐
│ │ │ │
│ │ │ Reducer 执行 │
│ │ │ │
│ │ └─── Middleware C(后半段)───┘
│ └─── Middleware B(后半段)───┘
└─── Middleware A(后半段)───┘
核心实现原理(简化版):
function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState);
let dispatch = store.dispatch;
const storeAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action),
};
const chain = middlewares.map(middleware => middleware(storeAPI));
dispatch = compose(...chain)(store.dispatch);
return { ...store, dispatch };
};
}
function compose(...fns) {
return fns.reduce((a, b) => (...args) => a(b(...args)));
}
compose 将 [A, B, C] 组合为 A(B(C(dispatch))),这样 A 的 next 指向 B 的处理函数,B 的 next 指向 C 的处理函数,C 的 next 指向原始 dispatch。
四、常用中间件
1. redux-thunk — 异步处理
redux-thunk 是最简单的异步中间件,允许 dispatch 一个函数(而非普通对象)。RTK 默认内置了它。
// thunk action creator
function fetchUser(userId) {
return async (dispatch, getState) => {
dispatch({ type: 'user/loading' });
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
dispatch({ type: 'user/loaded', payload: data });
} catch (error) {
dispatch({ type: 'user/error', payload: error.message });
}
};
}
// 使用
store.dispatch(fetchUser(123));
thunk 中间件的实现极其简洁:
function thunkMiddleware({ dispatch, getState }) {
return (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
}
只有 11 行代码:如果 action 是函数,执行它并传入 dispatch 和 getState;否则正常传递给下一个中间件。
2. redux-saga — 复杂异步流
redux-saga 使用 ES6 Generator 函数管理副作用,适用于复杂的异步流程(竞态、取消、重试、并行等):
import { call, put, takeLatest, all, delay } from 'redux-saga/effects';
function* fetchUserSaga(action) {
try {
yield put({ type: 'user/loading' });
const user = yield call(api.fetchUser, action.payload);
yield put({ type: 'user/loaded', payload: user });
} catch (error) {
yield put({ type: 'user/error', payload: error.message });
}
}
function* searchSaga(action) {
yield delay(300); // 防抖
const results = yield call(api.search, action.payload);
yield put({ type: 'search/results', payload: results });
}
function* rootSaga() {
yield all([
takeLatest('user/fetch', fetchUserSaga), // 自动取消之前的请求
takeLatest('search/query', searchSaga),
]);
}
saga 的优势:
takeLatest 自动处理竞态条件(取消上一个未完成的请求)
- Generator 让异步流程读起来像同步代码
- 可以用
call 的 mock 轻松测试
- 支持
fork、cancel、race 等复杂并发模式
3. redux-logger — 日志记录
在开发环境记录每一次 action 和状态变化:
import logger from 'redux-logger';
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(logger),
});
输出示例:
action counter/increment @ 14:23:45.123
prev state { counter: { value: 0 } }
action { type: 'counter/increment' }
next state { counter: { value: 1 } }
4. RTK 内置中间件
Redux Toolkit 的 configureStore 默认配置了以下中间件:
const store = configureStore({
reducer: rootReducer,
// 默认中间件包括:
// - redux-thunk(异步支持)
// - serializableCheck(检测不可序列化的值)
// - immutableCheck(检测意外的 state 变更,开发环境)
});
五、手写一个简单的中间件
日志中间件
const loggerMiddleware = (storeAPI) => (next) => (action) => {
console.group(action.type);
console.log('dispatching:', action);
console.log('prev state:', storeAPI.getState());
const result = next(action);
console.log('next state:', storeAPI.getState());
console.groupEnd();
return result;
};
错误上报中间件
const crashReporter = (storeAPI) => (next) => (action) => {
try {
return next(action);
} catch (err) {
console.error('Caught an exception!', err);
reportToServer({
error: err,
action,
state: storeAPI.getState(),
});
throw err;
}
};
条件 dispatch 中间件
const conditionalMiddleware = (storeAPI) => (next) => (action) => {
if (action.meta?.skipIf) {
const state = storeAPI.getState();
if (action.meta.skipIf(state)) {
return; // 跳过此 action
}
}
return next(action);
};
// 使用
dispatch({
type: 'counter/increment',
meta: {
skipIf: (state) => state.counter.value >= 10,
},
});
节流中间件
function createThrottleMiddleware(interval = 300) {
const pending = {};
return (storeAPI) => (next) => (action) => {
const key = action.type;
if (action.meta?.throttle) {
if (pending[key]) return;
pending[key] = true;
setTimeout(() => { pending[key] = false; }, interval);
}
return next(action);
};
}
六、RTK 中使用自定义中间件
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.prepend(conditionalMiddleware) // 在默认中间件之前
.concat(loggerMiddleware), // 在默认中间件之后
});
注意 getDefaultMiddleware() 返回的是 RTK 默认中间件数组(包含 thunk 等),可以链式调用 prepend 和 concat 添加自定义中间件。
七、中间件选型建议
对于大多数项目,RTK 内置的 thunk + createAsyncThunk 已经足够。只有在需要精细控制异步流程(如 WebSocket 管理、复杂的并发调度)时,才考虑引入 redux-saga。
八、总结
Redux 中间件是一个强大的扩展机制,它以优雅的函数式设计(三层柯里化 + compose 组合)实现了 AOP(面向切面编程)的能力。理解中间件的工作原理,不仅有助于使用现有中间件,更能帮助你在需要时编写自定义中间件来解决特定业务问题。