FumadocsZDecode
React

Redux 中间件的实现原理

Redux 中间件通过函数组合和柯里化对 dispatch 方法进行增强,实现了一个洋葱模型的执行流程,是 Redux 异步处理和功能扩展的核心机制。


一、中间件的本质

Redux 中间件是一个高阶函数,遵循柯里化的三层函数签名:

const middleware = (store: any) => (next: any) => (action: any) => {
  // 在调用 next 之前执行逻辑(下行)
  // 在调用 next 之后执行逻辑(上行)
  return next(action)
}

三层参数的含义:

  • store:Redux store,包含 getState 和 dispatch
  • next:下一个中间件或原始 dispatch
  • action:被 dispatch 的 action

二、applyMiddleware 的实现

applyMiddleware 是 Redux 提供的中间件应用器,它通过函数组合将多个中间件编织成一条链。

简化实现

function applyMiddleware(...middlewares: any[]) {
  return (createStore: any) => (...args: any[]) => {
    const store = createStore(...args)
    let dispatch = store.dispatch

    // 中间件可访问的 API
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action: any) => dispatch(action),
    }

    // 给每个中间件注入 store API
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))

    // 组合所有中间件,形成链条
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch,
    }
  }
}

compose 函数的实现

function compose(...funcs: Function[]): Function {
  if (funcs.length === 0) return (arg: any) => arg
  if (funcs.length === 1) return funcs[0]

  // 从右到左组合函数:f(g(x)) 表示先执行 g,再执行 f
  return funcs.reduce(
    (a: Function, b: Function) => (...args: any[]) => a(b(...args))
  )
}

三、执行流程(洋葱模型)

多个中间件的执行遵循洋葱模型:先从外到内逐层进入,遇到 next 后再从内到外逐层返回。

执行顺序示例

const store = createStore(reducer, applyMiddleware(middleware1, middleware2))

// 当 dispatch 一个 action 时的执行顺序:
// 1. middleware1 的前半部分(next 之前)
// 2. middleware2 的前半部分(next 之前)
// 3. 原始 reducer 处理 action
// 4. middleware2 的后半部分(next 之后)
// 5. middleware1 的后半部分(next 之后)

四、常见中间件示例

日志中间件

const logger = (store: any) => (next: any) => (action: any) => {
  console.log('dispatching:', action)
  const result = next(action)
  console.log('next state:', store.getState())
  return result
}

异步处理中间件(Thunk)

const thunk = (store: any) => (next: any) => (action: any) => {
  // action 是函数时,调用它并传入 dispatch 和 getState
  if (typeof action === 'function') {
    return action(store.dispatch, store.getState)
  }
  // action 是普通对象,传递给下一个中间件
  return next(action)
}

// 使用方式
const fetchUser = (id: number) => (dispatch: any, getState: any) => {
  dispatch({ type: 'FETCH_USER_START' })
  return fetch(`/api/users/${id}`)
    .then((res) => res.json())
    .then((data) =>
      dispatch({ type: 'FETCH_USER_SUCCESS', payload: data })
    )
    .catch((err) =>
      dispatch({ type: 'FETCH_USER_ERROR', payload: err })
    )
}

store.dispatch(fetchUser(1))

条件中间件(只在满足条件时执行)

const conditionalMiddleware = (condition: boolean) => (store: any) => (
  next: any
) => (action: any) => {
  if (condition) {
    console.log('action:', action)
  }
  return next(action)
}

五、中间件的组合

import { createStore, applyMiddleware } from 'redux'

const store = createStore(
  reducer,
  applyMiddleware(
    thunk,        // 第一个执行
    logger,       // 第二个执行
    errorHandler  // 第三个执行
  )
)

中间件执行顺序由传入 applyMiddleware 的顺序决定。通常的最佳实践是:异步中间件(如 thunk)放在最前,日志和监控中间件放在后面。


六、注意事项

必须调用 next(action)

不调用 next 会中断 action 的传递,导致后续中间件和 reducer 无法执行。

// ✓ 正确:调用 next 继续传递
const middleware = (store: any) => (next: any) => (action: any) => {
  console.log(action)
  return next(action)
}

// ✗ 错误:中断了 action 的传递
const middleware = (store: any) => (next: any) => (action: any) => {
  console.log(action)
  // 没有调用 next,action 不会继续传递
}

中间件内避免直接修改状态

中间件应该是纯函数,通过 dispatch 新的 action 来修改状态,而不是直接修改。

// ✗ 错误:直接修改
const middleware = (store: any) => (next: any) => (action: any) => {
  store.getState().user.name = 'newName' // 禁止
  return next(action)
}

// ✓ 正确:dispatch 新 action
const middleware = (store: any) => (next: any) => (action: any) => {
  if (needsSpecialHandling(action)) {
    store.dispatch({ type: 'UPDATE_NAME', payload: 'newName' })
  }
  return next(action)
}

中间件的顺序很重要

不同顺序会导致完全不同的执行流程和结果。

// 这两种顺序会产生不同的行为
applyMiddleware(thunk, logger)  // thunk 先执行
applyMiddleware(logger, thunk)  // logger 先执行

核心机制:中间件通过函数组合实现洋葱模型,applyMiddleware 负责链接各个中间件,compose 负责函数嵌套。每个中间件都会被注入 store 的 API,可以在 dispatch 前后执行逻辑,必须调用 next(action) 才能继续传递。

On this page