Redux 中间件 (Middleware)
1. 前置概念:函数式编程思想
在深入中间件之前,需要理解两个函数式编程的核心概念:函数组合和柯里化。
1.1 函数组合 (Compose)
当需要依次执行多个函数,且上一个函数的返回值是下一个函数的参数时,可以使用函数组合来优化代码结构,提高可读性。
场景: 假设有三个函数 f1, f2, f3,需要将参数 'omg' 依次传入执行。
不优雅的实现 (洋葱模型):
// 可读性差,难以维护
const result = f1(f2(f3('omg')));优雅的实现 (使用 compose):
// 可读性好,易于扩展
const composedFn = compose(f1, f2, f3);
const result = composedFn('omg');compose(f1, f2, f3) 会返回一个新函数,执行顺序是从右到左 (f3 -> f2 -> f1)。
1.2 函数柯里化 (Currying / 颗粒化)
柯里化是将一个接收多个参数的函数,转变为一系列只接收一个参数的函数的过程。这样做可以使得函数参数单一,便于复用和组合。
普通函数:
function add(a, b, c) {
return a + b + c;
}
add(1, 2, 3);柯里化后的函数:
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
}
}
}
curriedAdd(1)(2)(3);Redux 中间件的 API 设计就大量运用了柯里化的思想。
2. 中间件 (Middleware) 是什么?
2.1 核心作用
Redux 的 dispatch 方法本身只能处理纯对象(Plain Object)形式的 Action。对于异步操作(如 API 请求),直接 dispatch 一个函数会报错。
中间件就像一个“中介”,它位于 dispatch(action) 和 reducer 之间,用于拦截 Action,执行副作用(如异步请求、日志记录等),然后再决定是放行 Action 到下一个中间件/Reducer,还是分发一个新的 Action。

2.2 applyMiddleware API
applyMiddleware 是 Redux 提供的官方 API,用于将一个或多个中间件应用到 createStore 上。
常用中间件:
redux-thunk: 允许dispatch函数,用于处理简单的异步逻辑。redux-logger: 在控制台打印 Action 信息及前后的 State 状态,便于调试。
使用示例:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducers';
// 将中间件作为参数传入 applyMiddleware
const store = createStore(
rootReducer,
applyMiddleware(thunk, logger)
);2.3 中间件的执行顺序
applyMiddleware 会将所有中间件组合成一个链式调用的结构。Action 会依次通过这个链。
重要规则: redux-logger 通常应该放在中间件列表的最后一个。
- 原因:
logger需要记录最终到达 Reducer 的 Action 以及由该 Action 引起的 State 变化。如果放在前面,它可能记录到被其他中间件(如thunk)处理前的原始 Action(例如一个函数),而不是最终的 Plain Object Action,导致日志信息不准确。
3. 从零实现 Redux 中间件相关 API
3.1 实现 compose 函数
function compose(...funcs) {
// 边缘情况:没有函数传入
if (funcs.length === 0) {
return (arg) => arg;
}
// 边缘情况:只有一个函数
if (funcs.length === 1) {
return funcs[0];
}
// 使用 reduce 将函数串联起来
// a 是上一次聚合的结果(一个函数),b 是当前遍历到的函数
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}3.2 实现 applyMiddleware 函数
applyMiddleware 是一个高阶函数,它返回一个 "enhancer"(增强器),这个增强器再接收 createStore 来生成最终的 store。
import { compose } from './compose';
export function applyMiddleware(...middlewares) {
// 返回一个 enhancer
return (createStore) => (reducer) => {
// 1. 先按照原始方式创建 store
const store = createStore(reducer);
// 2. 保存原始的 dispatch 方法
let dispatch = store.dispatch;
// 3. 定义要传递给中间件的 API
const middlewareAPI = {
getState: store.getState,
// 这里的 dispatch 必须用箭头函数包装,确保中间件在调用 dispatch 时,
// 使用的是被增强后的最新 dispatch,而不是创建 store 时的原始 dispatch。
dispatch: (...args) => dispatch(...args)
};
// 4. 将 middlewareAPI 注入到每个中间件中,得到一个函数链
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// 5. 使用 compose 将所有中间件组合起来,并包装原始的 dispatch
// 最终得到一个被层层增强的 dispatch 方法
dispatch = compose(...chain)(store.dispatch);
// 6. 返回新的 store,其中 dispatch 方法已经被替换为增强版
return {
...store,
dispatch
};
};
}3.3 实现 logger 中间件
所有中间件都遵循 ({ getState, dispatch }) => next => action => { ... } 的柯里化结构。
const logger = ({ getState }) => next => action => {
console.log('--------------------');
console.log('will dispatch', action.type);
// 1. 打印更新前的 state
const prevState = getState();
console.log('prevState', prevState);
// 2. 调用 next,将 action 传递给下一个中间件或 reducer
// next(action) 的返回值非常重要,需要 return 出去
const returnValue = next(action);
// 3. 打印更新后的 state
const nextState = getState();
console.log('nextState', nextState);
console.log('--------------------');
return returnValue;
};3.4 实现 thunk 中间件
thunk 的核心逻辑是判断 Action 是不是一个函数。
const thunk = ({ dispatch, getState }) => next => action => {
// 如果 action 是一个函数
if (typeof action === 'function') {
// 执行该函数,并将 dispatch 和 getState 作为参数传入
// 这样在异步操作完成后,就可以在函数内部 dispatch 一个新的 action
return action(dispatch, getState);
}
// 如果 action 不是函数,则直接传递给下一个中间件
return next(action);
};4. 其他中间件与进阶
4.1 Promise 中间件
与 thunk 类似,Promise 中间件可以用来处理 Action 是一个 Promise 的情况。
核心逻辑:
- 检查 Action 或 Action.payload 是否为 Promise 对象。
- 如果是,则在
.then()中处理成功和失败的情况,并dispatch相应的 Action。 - 如果不是,则调用
next(action)放行。
4.2 Flux Standard Action (FSA)
FSA 是一个社区约定的 Action 对象格式标准,旨在使 Action 更具可读性和一致性。一个标准的 FSA Action 应该:
- 是一个纯 JavaScript 对象。
- 必须有一个
type属性。 - 可以有
payload、error、meta属性。
一些成熟的 Promise 中间件会检查 Action 是否符合 FSA 规范。
4.3 Redux Saga 简介
Redux Saga 是另一个用于处理副作用(尤其是复杂异步流)的中间件。与 thunk 不同,它使用 ES6 的 Generator 函数来让异步代码看起来像同步一样,提供了更强大的控制能力,如任务的取消、并发处理等,但学习曲线也更陡峭。