创见博客
Redux的中间件(middleware)
七崽爱吃小饼干2026/01/17阅读 0专栏 React

Redux 中间件机制

中间件是Redux的核心灵魂,Redux的异步处理、日志打印、错误捕获、状态持久化等几乎所有高级能力,都依赖中间件实现,而且Redux的中间件设计是洋葱模型的经典实践,理解它能举一反三看懂很多框架的中间件逻辑。

一、先明确:Redux 中间件到底是解决什么问题?

Redux 原生的工作流是严格同步、无副作用的,原生核心流程只有3步:

dispatch(action) → reducer(state, action) → newState

这个原生流程有2个致命缺陷:

  1. dispatch 只能接收一个纯对象(plain object) 作为 action,不能传函数、Promise、异步回调;
  2. 整个流程是「一次性走完」的,没有任何可以切入的扩展点,无法在「发起action」和「reducer处理action」之间做任何自定义操作。

中间件的核心使命: 为 Redux 增强 dispatch 函数的能力,在 dispatch 分发 action 的「生命周期」中,增加一系列可插拔的扩展逻辑,让我们可以:

  • 在 action 到达 reducer 之前,做日志打印、参数校验、错误捕获、请求接口等操作;
  • 让 dispatch 可以接收非纯对象(函数/Promise/异步逻辑),完美解决 Redux 异步请求的核心痛点;
  • 在 reducer 处理完 action 生成新状态后,做状态持久化、页面埋点、触发回调等后续操作。

二、Redux 中间件的本质 & 核心设计思想

2.1 核心本质

Redux 中间件的本质,是一个 「柯里化的函数」,是一个层层嵌套的高阶函数,它的标准函数签名是固定的,必须遵循这个规范才能被 Redux 识别:

javascript
// 中间件的标准格式(柯里化三层函数)
const middleware = store => next => action => {
  // 中间件的核心逻辑写在这里
}

三层函数的参数含义

  1. 第一层:store → 传入 Redux 的 store 实例,包含 getState()、dispatch() 两个核心方法(注意:这里的dispatch是「原生未增强的dispatch」);
  2. 第二层:next → 一个函数,代表「当前中间件的下一个执行环节」,本质是下一个中间件的处理函数,如果是最后一个中间件,next 就是 Redux 原生的 dispatch;
  3. 第三层: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:定义中间件 & 创建增强器

javascript
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」

javascript
// 3. 把增强器传入 createStore,创建增强版 store
const store = createStore(reducer, enhancer)

关键点:此时 store 上的 dispatch 方法,已经不是原生的 dispatch,而是被所有中间件「层层包装后的增强版 dispatch」!

步骤3:applyMiddleware 的内部核心操作

当执行 applyMiddleware 时,它内部会做3件核心事(源码简化逻辑,无复杂代码):

  1. 接收所有中间件,传入 store(仅传入 getState 和原生 dispatch),执行中间件的第一层函数,得到一批 next => action => {} 格式的函数;
  2. 通过一个核心方法 compose(组合函数),把这批函数从右到左串联成一个「链式函数」;
  3. 用这个链式函数,包装 Redux 的原生 dispatch,生成一个「增强版 dispatch」。

步骤4:触发增强版 dispatch

javascript
// 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 异步的原理,无任何冗余,原汁原味:

javascript
// 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 的解决方案:

  1. 允许我们给 dispatch 传入一个函数(这个函数就是「异步action创建函数」);
  2. 这个函数内部可以写任意异步逻辑(axios请求、setTimeout等);
  3. 异步逻辑执行完成后,在函数内部手动调用 dispatch,分发一个「纯对象 action」给 reducer;
  4. reducer 接收 action,生成新状态,完成异步更新。

4.4 实战用法(异步请求接口)

javascript
// 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 函数来实现中间件的串联,执行逻辑如下:

  1. 我们传入的中间件顺序是 [logger, thunk];
  2. compose 会把它们组合成:logger(thunk(原生dispatch));
  3. 最终生成的增强版 dispatch,本质是:调用 logger 的逻辑 → 内部调用 thunk 的逻辑 → 内部调用原生 dispatch;
  4. 执行完成后,结果从原生 dispatch → thunk → logger 依次返回,完美实现「先进后出」的洋葱模型。

核心结论:没有 compose 函数,就没有中间件的洋葱执行顺序。


六、自定义一个完整的中间件

理解了所有原理后,我们手写一个实用的自定义中间件,包含「日志打印+错误捕获」功能:

javascript
/**
 * 自定义中间件:日志+错误捕获
 * 遵循标准签名: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 中间件的核心总结

核心定义

  1. Redux 中间件是柯里化的三层高阶函数,标准签名:store => next => action => {};
  2. 核心作用是增强 dispatch 的能力,为 Redux 提供可插拔的扩展逻辑;
  3. 核心设计思想是洋葱模型,先进后出,前置逻辑从左到右,后置逻辑从右到左。

核心流程

  1. 用 applyMiddleware 包装所有中间件,生成增强器;
  2. 用增强器创建增强版 store,此时 store.dispatch 被包装;
  3. 调用 dispatch(action),触发中间件链式执行;
  4. 所有中间件执行完后,最终执行原生 dispatch → reducer → 生成新状态。

核心知识点

  1. redux-thunk 是最常用的中间件,核心是让 dispatch 支持接收函数,解决异步问题;
  2. compose 函数是中间件组合的基石,实现从右到左的函数嵌套;
  3. 中间件的执行顺序由传入 applyMiddleware 的顺序决定,越先传入的中间件,越外层执行。

核心思想

Redux 中间件的设计,是「开闭原则」的完美体现:对扩展开放,对修改关闭。Redux 的核心源码(createStore、reducer)不需要任何修改,就可以通过中间件扩展出无限的能力,这也是 Redux 能成为主流状态管理库的核心原因之一。

评论
0/100