DrillLab
第 85 / 105 道85 / 105 · #354

解释一下 Redux 中间件

explain Redux Middleware

先自己答,再往下看Answer it yourself first

一句话:中间件是夹在「派发 action」和 「reducer 收到 action」之间的一层, 能拦截、改写、延迟、 甚至吞掉一个 action。

签名是三层柯里化—— 这个形状本身常被问到:store => next => action => {}。 多个中间件靠 next串成一条链,和 Express 的中间件是同一个模式

为什么需要它:因为reducer 必须是纯的, 所以异步和副作用无处安放。 中间件就是专门给副作用留的位置

常见的几个:

  • redux-thunk—— 让你能 dispatch 一个函数而不只是对象, 在里面做异步。最简单,RTK 默认装。
  • redux-saga—— 用 generator 描述复杂异步流程 (可取消、可重试、能编排多个请求)。 能力强但学习成本高。
  • redux-logger—— 打印每个 action 前后的 state。

会追问:「thunk 和 saga 怎么选?」—— 大部分项目 thunk 够了;只有在需要「取消、去抖、 复杂的流程编排」时 saga 才值那份复杂度
「能自己写一个吗?」—— 能,而且面试常让手写一个 logger。

In one line: middleware is a layer between “an action is dispatched” and “the reducer receives it”. It can intercept, rewrite, delay or even swallow an action.

The signature is curried three levels deep — the shape itself gets asked about: store => next => action => {}. Several middlewares chain together through next, and it is the same pattern as Express middleware.

Why you need it: because the reducer has to be pure, async work and side effects have nowhere to live. Middleware is the place reserved for side effects.

The common ones:

  • redux-thunk — lets you dispatch a function instead of only an object, and do your async work inside it. Simplest option, and RTK installs it by default.
  • redux-saga — describes complex async flows with generators (cancellable, retryable, able to orchestrate several requests). Powerful, but a steep learning curve.
  • redux-logger — prints the state before and after each action.

Follow-up: “thunk or saga?” — thunk is enough for most projects; saga only earns its complexity when you need cancellation, debouncing or real flow orchestration.
“Could you write one?” — yes, and interviewers often ask you to write a logger on the spot.

JavaScript中间件的形状与 thunk示意Illustrative
1// 手写一个 logger 中间件:注意那三层箭头
2const logger = (store) => (next) => (action) => {
3 console.log("派发:", action.type, action.payload);
4 const result = next(action); // 交给下一个中间件 / reducer
5 console.log("新状态:", store.getState());
6 return result;
7};
8
9// thunk 让 dispatch 能收函数
10const fetchUser = (id) => async (dispatch) => {
11 dispatch({ type: "user/loading" });
12 try {
13 const res = await fetch(`/api/users/${id}`);
14 if (!res.ok) throw new Error(`HTTP ${res.status}`); // 别忘了这一句
15 dispatch({ type: "user/loaded", payload: await res.json() });
16 } catch (e) {
17 dispatch({ type: "user/failed", payload: e.message });
18 }
19};
1// Writing a logger middleware yourself: note the three levels of arrows
2const logger = (store) => (next) => (action) => {
3 console.log("dispatching:", action.type, action.payload);
4 const result = next(action); // hand it to the next middleware or the reducer
5 console.log("new state:", store.getState());
6 return result;
7};
8
9// thunk lets dispatch accept a function
10const fetchUser = (id) => async (dispatch) => {
11 dispatch({ type: "user/loading" });
12 try {
13 const res = await fetch(`/api/users/${id}`);
14 if (!res.ok) throw new Error(`HTTP ${res.status}`); // do not forget this line
15 dispatch({ type: "user/loaded", payload: await res.json() });
16 } catch (e) {
17 dispatch({ type: "user/failed", payload: e.message });
18 }
19};