Redux 中间件机制
中间件是Redux的核心灵魂,Redux的异步处理、日志打印、错误捕获、状态持久化等几乎所有高级能力,都依赖中间件实现,而且Redux的中间件设计是洋葱模型的经典实践,理解它能举一反三看懂很多框架的中间件逻辑。
一、先明确:Redux 中间件到底是解决什么问题?
Redux 原生的工作流是严格同步、无副作用的,原生核心流程只有3步:
dispatch(action) → reducer(state, action) → newState
这个原生流程有2个致命缺陷:
dispatch只能接收一个纯对象(plain object) 作为 action,不能传函数、Promise、异步回调;- 整个流程是「一次性走完」的,没有任何可以切入的扩展点,无法在「发起action」和「reducer处理action」之间做任何自定义操作。
中间件的核心使命:
为 Redux 增强 dispatch 函数的能力,在 dispatch 分发 action 的「生命周期」中,增加一系列可插拔的扩展逻辑,让我们可以:
- 在 action 到达 reducer 之前,做日志打印、参数校验、错误捕获、请求接口等操作;
- 让
dispatch可以接收非纯对象(函数/Promise/异步逻辑),完美解决 Redux 异步请求的核心痛点; - 在 reducer 处理完 action 生成新状态后,做状态持久化、页面埋点、触发回调等后续操作。
二、Redux 中间件的本质 & 核心设计思想
2.1 核心本质
Redux 中间件的本质,是一个 「柯里化的函数」,是一个层层嵌套的高阶函数,它的标准函数签名是固定的,必须遵循这个规范才能被 Redux 识别:
// 中间件的标准格式(柯里化三层函数)
const middleware = store => next => action => {
// 中间件的核心逻辑写在这里
}
三层函数的参数含义
- 第一层:
store→ 传入 Redux 的 store 实例,包含getState()、dispatch()两个核心方法(注意:这里的dispatch是「原生未增强的dispatch」); - 第二层:
next→ 一个函数,代表「当前中间件的下一个执行环节」,本质是下一个中间件的处理函数,如果是最后一个中间件,next就是 Redux 原生的dispatch; - 第三层:
action→ 就是我们通过dispatch(action)传入的那个 action 对象/函数/Promise 等。
2.2 核心设计思想:洋葱模型
这是 Redux 中间件最核心、最重要的特性,没有之一!所有 Redux 中间件的执行顺序,都遵循「洋葱模型」。
什么是洋葱模型?
当我们配置多个中间件时,Redux 会把这些中间件串联成一个「执行链」,请求进入时,会从左到右依次执行所有中间件的「前置逻辑」;处理完成后,会从右到左依次执行所有中间件的「后置逻辑」,就像剥洋葱一样:从外层到内层,再从内层回到外层。
举个直观的例子(必看)
假设我们有两个中间件 logger(日志)、thunk(处理异步),配置顺序是 [logger, thunk],执行流程如下:
dispatch(action)
→ 进入 logger 中间件的「前置逻辑」(打印:开始分发action)
→ 调用 next() → 进入 thunk 中间件的「前置逻辑」(判断action是否是函数)
→ 调用 next() → 执行 Redux 原生 dispatch → reducer 处理 → 生成新状态
→ 回到 thunk 中间件的「后置逻辑」(无操作)
→ 回到 logger 中间件的「后置逻辑」(打印:分发完成,新状态xxx)
核心总结:所有中间件共享「进入」和「返回」的流程,先进后出,内层执行完才会执行外层的后置逻辑。
三、Redux 中间件的执行流程
我们结合 Redux 的核心 API,把「创建store→应用中间件→dispatch触发」的完整流程梳理清楚,让你知道中间件是如何被「注入」到 Redux 中的,一共分5步,这是理解中间件的关键:
前置知识:Redux 的核心 API applyMiddleware
Redux 提供了一个原生高阶函数 applyMiddleware(...middlewares),它的作用是:接收所有中间件,返回一个「增强器 enhancer」,用来增强 createStore 的能力。
完整执行链路
步骤1:定义中间件 & 创建增强器
import { createStore, applyMiddleware } from 'redux'
import reducer from './reducer'
// 1. 定义两个自定义中间件(遵循标准签名)
const loggerMiddleware = store => next => action => {
console.log('前置:旧状态', store.getState())
const result = next(action) // 执行下一个环节
console.log('后置:新状态', store.getState())
return result
}
const testMiddleware = store => next => action => {
console.log('前置:要分发的action', action)
const result = next(action)
console.log('后置:分发完成')
return result
}
// 2. 用 applyMiddleware 包装所有中间件,生成「增强器」
const enhancer = applyMiddleware(loggerMiddleware, testMiddleware)
步骤2:创建「被中间件增强后的 store」
// 3. 把增强器传入 createStore,创建增强版 store
const store = createStore(reducer, enhancer)
关键点:此时 store 上的
dispatch方法,已经不是原生的 dispatch,而是被所有中间件「层层包装后的增强版 dispatch」!
步骤3:applyMiddleware 的内部核心操作
当执行 applyMiddleware 时,它内部会做3件核心事(源码简化逻辑,无复杂代码):
- 接收所有中间件,传入
store(仅传入getState和原生dispatch),执行中间件的第一层函数,得到一批next => action => {}格式的函数; - 通过一个核心方法
compose(组合函数),把这批函数从右到左串联成一个「链式函数」; - 用这个链式函数,包装 Redux 的原生 dispatch,生成一个「增强版 dispatch」。
步骤4:触发增强版 dispatch
// 4. 触发分发,此时会执行所有中间件的逻辑
store.dispatch({ type: 'INCREMENT' })
步骤5:中间件的链式执行
当调用增强版 dispatch(action) 时,会触发:
- 先执行第一个中间件的前置逻辑 → 调用
next(action)→ 进入下一个中间件; - 所有中间件的前置逻辑执行完后,
next指向「原生 dispatch」,执行 reducer 生成新状态; - 再从最后一个中间件开始,依次执行所有中间件的后置逻辑;
- 最终返回 action 执行结果。
四、最经典的内置中间件:redux-thunk
我们前面说过,Redux 原生 dispatch 只能传「纯对象」,而异步请求是 Redux 开发中最常见的需求,redux-thunk 就是解决这个问题的官方推荐中间件,也是 Redux 中间件最经典的应用场景。
4.1 redux-thunk 的核心作用
让 Redux 的 dispatch 方法,不仅可以接收「纯对象 action」,还可以接收「函数」。
4.2 redux-thunk 的极简源码(核心逻辑只有10行)
看懂这个源码,你就彻底懂了 Redux 异步的原理,无任何冗余,原汁原味:
// redux-thunk 核心源码
const thunk = ({ dispatch, getState }) => next => action => {
// 核心判断:如果 action 是一个函数
if (typeof action === 'function') {
// 把 dispatch 和 getState 传给这个函数,让函数内部可以手动分发action
return action(dispatch, getState)
}
// 如果 action 是纯对象,就执行下一个中间件/原生dispatch
return next(action)
}
export default thunk
4.3 redux-thunk 解决异步的核心原理
异步请求的核心痛点:异步操作的结果(比如接口返回数据),需要触发 action 来更新状态,但原生 Redux 无法在异步回调中分发 action。
redux-thunk 的解决方案:
- 允许我们给
dispatch传入一个函数(这个函数就是「异步action创建函数」); - 这个函数内部可以写任意异步逻辑(axios请求、setTimeout等);
- 异步逻辑执行完成后,在函数内部手动调用 dispatch,分发一个「纯对象 action」给 reducer;
- reducer 接收 action,生成新状态,完成异步更新。
4.4 实战用法(异步请求接口)
// 1. 引入 thunk 并应用
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk, loggerMiddleware))
// 2. 定义「异步action创建函数」- 返回一个函数
const fetchUser = () => {
// 这个函数会被 thunk 执行,传入 dispatch 和 getState
return async (dispatch, getState) => {
dispatch({ type: 'FETCH_USER_START' }) // 发起请求,更新loading状态
try {
const res = await axios.get('/api/user') // 异步请求接口
dispatch({ type: 'FETCH_USER_SUCCESS', payload: res.data }) // 请求成功,分发action
} catch (err) {
dispatch({ type: 'FETCH_USER_FAIL', payload: err.message }) // 请求失败,分发action
}
}
}
// 3. 触发异步action
store.dispatch(fetchUser())
核心总结:redux-thunk 并没有「让 Redux 本身支持异步」,而是提供了一个「异步逻辑的容器」,让异步逻辑可以在函数内部自主控制 action 的分发时机。
五、中间件的组合原理:compose 函数(洋葱模型的基石)
5.1 什么是 compose 函数?
Redux 内置了一个核心工具函数 compose(...funcs),它的作用是:将多个函数「从右到左」组合成一个嵌套的函数,执行时从最右侧函数开始,结果作为参数传给左侧函数。
简单说:compose(f1, f2, f3) 等价于 (...args) => f1(f2(f3(...args)))。
5.2 compose 为什么是洋葱模型的基石?
applyMiddleware 内部完全依赖 compose 函数来实现中间件的串联,执行逻辑如下:
- 我们传入的中间件顺序是
[logger, thunk]; - compose 会把它们组合成:
logger(thunk(原生dispatch)); - 最终生成的增强版 dispatch,本质是:调用 logger 的逻辑 → 内部调用 thunk 的逻辑 → 内部调用原生 dispatch;
- 执行完成后,结果从原生 dispatch → thunk → logger 依次返回,完美实现「先进后出」的洋葱模型。
核心结论:没有 compose 函数,就没有中间件的洋葱执行顺序。
六、自定义一个完整的中间件
理解了所有原理后,我们手写一个实用的自定义中间件,包含「日志打印+错误捕获」功能:
/**
* 自定义中间件:日志+错误捕获
* 遵循标准签名:store => next => action
*/
const customMiddleware = store => next => action => {
try {
// 前置逻辑:分发action前的操作
console.group(`[${new Date().toLocaleTimeString()}] 分发Action: ${action.type}`)
console.log('📌 旧状态:', store.getState())
console.log('📌 Action内容:', action)
// 执行下一个中间件/原生dispatch,拿到执行结果
const result = next(action)
// 后置逻辑:分发完成后的操作
console.log('📌 新状态:', store.getState())
console.groupEnd()
return result
} catch (error) {
// 捕获整个流程中的错误
console.error('❌ Redux执行出错:', error)
// 可以在这里分发一个错误action,更新状态
store.dispatch({ type: 'REDUX_ERROR', payload: error.message })
throw error // 抛出错误,不阻断流程
}
}
使用方式和普通中间件一致,传入 applyMiddleware 即可,这个中间件可以完美捕获 dispatch、reducer、其他中间件中的所有错误,同时打印清晰的日志,非常实用。
七、Redux 中间件的核心总结
核心定义
- Redux 中间件是柯里化的三层高阶函数,标准签名:
store => next => action => {}; - 核心作用是增强 dispatch 的能力,为 Redux 提供可插拔的扩展逻辑;
- 核心设计思想是洋葱模型,先进后出,前置逻辑从左到右,后置逻辑从右到左。
核心流程
- 用
applyMiddleware包装所有中间件,生成增强器; - 用增强器创建增强版 store,此时
store.dispatch被包装; - 调用
dispatch(action),触发中间件链式执行; - 所有中间件执行完后,最终执行原生 dispatch → reducer → 生成新状态。
核心知识点
redux-thunk是最常用的中间件,核心是让 dispatch 支持接收函数,解决异步问题;compose函数是中间件组合的基石,实现从右到左的函数嵌套;- 中间件的执行顺序由传入
applyMiddleware的顺序决定,越先传入的中间件,越外层执行。
核心思想
Redux 中间件的设计,是「开闭原则」的完美体现:对扩展开放,对修改关闭。Redux 的核心源码(createStore、reducer)不需要任何修改,就可以通过中间件扩展出无限的能力,这也是 Redux 能成为主流状态管理库的核心原因之一。