☰
Redux Thunk 实战指南:Thunk 中间件原理、安装配置与异步流程编排
2026/10/1 2:01:52 网站建设 项目流程
  • 前端

【免费下载链接】redux-thunk

Thunk middleware for Redux

项目地址:https://gitcode.com/gh_mirrors/re/redux-thunk
点击查看免费下载

本文以开源仓库 redux-thunk 的官方 README 为主体,结合 中间件源码、类型定义 与 单元测试 展开讲解。你将系统掌握 Redux Thunk 是什么、如何在 Redux Toolkit 与手写 store 两种场景下安装配置、如何注入自定义参数extraArgument、以及如何利用 thunk 组合 Promise 编排复杂的异步控制流,最终具备在真实项目中落地 thunk 异步逻辑的完整能力。

一句话认识 Redux Thunk

Redux Thunk 是 Redux 的Thunk 中间件(middleware)。它允许你编写"内部带有逻辑的函数"作为 action 派发,这些函数可以访问 Redux store 的dispatch与getState方法,从而在 Redux 生态内自由地执行异步逻辑与复杂的同步逻辑。仓库在 package.json 中将其定位为 "Thunk middleware for Redux",当前版本为3.1.0,采用 MIT 协议开源。

从中间件的标准视角看,Redux 官方将中间件定义为"位于dispatch与 store 之间、用于扩展 store 能力的函数层"。Redux Thunk 正是这样一层:它拦截所有派发到 store 的 action,发现其中"实际上是函数"的项,便不再当作普通 action 处理,而是立即调用它,并把 store 的dispatch、getState(以及可选的extraArgument)注入进去。

什么是 "thunk"?

一个thunk是"用一个函数包裹表达式以延迟其求值"的函数。

// 1 + 2 的计算是立即发生的 // x === 3 let x = 1 + 2 // 1 + 2 的计算被延迟了 // foo 可以在之后被调用以执行计算 // foo 就是一个 thunk! let foo = () => 1 + 2

x在定义时立刻算出结果3,而foo只是"准备好的一段计算",直到调用foo()才会真正求值。这个概念正是 Redux Thunk 的核心思想:把"派发动作"这个动作本身延迟到合适的时机执行。"thunk" 一词据说源自 "think" 的戏谑过去式,意指"思考后延后处理的产物"。

安装与初始化配置

Redux Thunk 有两种典型的接入方式,取决于你使用的是 Redux Toolkit 还是基础版 ReduxcreateStoreAPI。

使用 Redux Toolkit:零配置自动集成

如果你按官方推荐使用Redux Toolkit,那么无需额外安装任何东西——RTK 的configureStoreAPI 默认就会添加 thunk 中间件:

import { configureStore } from '@reduxjs/toolkit' import todosReducer from './features/todos/todosSlice' import filtersReducer from './features/filters/filtersSlice' const store = configureStore({ reducer: { todos: todosReducer, filters: filtersReducer } }) // thunk 中间件已被自动添加

注意:thunk 中间件并不包含在 Redux 核心库中,而是被@reduxjs/toolkit包默认启用。因此只要 store 由configureStore创建,store.dispatch就天然支持派发 thunk 函数。

手动接入:createStore + applyMiddleware

如果你使用基础版 ReduxcreateStoreAPI,需要手动完成两步。

第一步,安装依赖:

npm install redux-thunk yarn add redux-thunk

第二步,引入中间件。请注意,thunk 中间件是命名导出(named export),不是默认导出。在 ES Modules 环境中:

import { thunk } from 'redux-thunk'

在 CommonJS 环境中:

const { thunk } = require('redux-thunk')

第三步,通过applyMiddleware()启用:

import { createStore, applyMiddleware } from 'redux' import { thunk } from 'redux-thunk' import rootReducer from './reducers/index' const store = createStore(rootReducer, applyMiddleware(thunk))

包结构与模块格式说明

从仓库 package.json 可以看出,redux-thunk@3.1.0的发布结构考虑得非常细致:

  • exports字段为不同环境提供了对应的入口:types指向dist/redux-thunk.d.ts,import指向dist/redux-thunk.mjs(ESM),default指向dist/cjs/redux-thunk.cjs(CommonJS);
  • sideEffects: false声明模块无副作用,便于打包器做 tree-shaking;
  • 声明peerDependencies: { "redux": "^5.0.0" },即要求宿主项目提供 Redux 5.x 作为对等依赖;
  • 构建脚本通过 tsup.config.ts 生成 ESM(.mjs)、兼容 Webpack 4 的 legacy ESM(.js)以及 CJS(.cjs)三套产物,入口统一为 src/index.ts。

注入自定义参数:extraArgument

自2.1.0版本起,Redux Thunk 支持向 thunk 中间件注入一个自定义参数(extraArgument)。它典型地用于"API 服务层"场景:在测试中可以把真实服务无缝替换成 mock 服务,而业务代码无需改动。

方式一:Redux Toolkit 的 getDefaultMiddleware

在configureStore内部的getDefaultMiddleware回调中传入自定义extraArgument:

import { configureStore } from '@reduxjs/toolkit' import rootReducer from './reducer' import { myCustomApiService } from './api' const store = configureStore({ reducer: rootReducer, middleware: getDefaultMiddleware => getDefaultMiddleware({ thunk: { extraArgument: myCustomApiService } }) }) // 稍后使用 function fetchUser(id) { // extraArgument 是 thunk 函数的第三个参数 return (dispatch, getState, api) => { // 你可以在这里使用 api } }

方式二:传入多个值

如果需要同时注入多个值,把它们合并进一个对象即可:

const store = configureStore({ reducer: rootReducer, middleware: getDefaultMiddleware => getDefaultMiddleware({ thunk: { extraArgument: { api: myCustomApiService, otherValue: 42 } } }) }) // 稍后使用 function fetchUser(id) { return (dispatch, getState, { api, otherValue }) => { // 你可以在这里使用 api 和 otherValue } }

方式三:手写 store 场景下的 withExtraArgument

如果你手动创建 store,则应使用命名导出withExtraArgument()函数来生成携带自定义参数的 thunk 中间件:

const store = createStore(reducer, applyMiddleware(withExtraArgument(api)))

从源码看,withExtraArgument并非独立实现,而是同一个工厂函数的别名。在 src/index.ts 中:

function createThunkMiddleware< State = any, BasicAction extends Action = AnyAction, ExtraThunkArg = undefined >(extraArgument?: ExtraThunkArg) { // ... return middleware } export const thunk = createThunkMiddleware() // 导出工厂函数,便于用户创建注入自定义 "extra arg" 的定制版本 export const withExtraArgument = createThunkMiddleware

也就是说,默认的thunk等价于createThunkMiddleware()(不注入任何参数),而withExtraArgument(api)等价于createThunkMiddleware(api)。在 单元测试 中,withExtraArgument(extraArg)生成中间件后,thunk 函数收到的第三个参数与注入值严格相等(expect(arg).toBe(extraArg)),印证了这一行为。

为什么需要 Thunk?

使用普通的基础版 Redux store,你只能通过派发 action 进行简单的同步更新。中间件扩展了 store 的能力,让你能够编写与 store 交互的异步逻辑。

Thunks 是 Redux基础副作用逻辑的推荐中间件方案,覆盖两类场景:

  • 需要访问 store 的复杂同步逻辑;
  • 类似 AJAX 请求的简单异步逻辑。

Redux 官方文档有专门的《Writing Logic with Thunks》页面,详细讲解 thunk 存在的原因、thunk 中间件的工作机制以及实用模式;社区中关于"为什么异步流程需要中间件"的经典讨论也系统地介绍了从内联异步调用、异步 action creator 到 thunk 中间件的渐进演进过程。

核心动机:让 action creator 返回函数

Redux Thunk 允许你编写返回函数而非 action 的 action creator。这个 thunk 函数可以延迟 action 的派发,也可以只在满足某个条件时才派发。内层函数会接收到 store 的dispatch和getState两个方法作为参数。

异步派发示例

一个返回函数以执行异步派发的 action creator:

const INCREMENT_COUNTER = 'INCREMENT_COUNTER' function increment() { return { type: INCREMENT_COUNTER } } function incrementAsync() { return dispatch => { setTimeout(() => { // 太好了!可以在 dispatch 中调用同步或异步 action dispatch(increment()) }, 1000) } }

这里incrementAsync()返回的函数就是 thunk:1 秒后通过dispatch派发一个普通的increment()action。调用方store.dispatch(incrementAsync())与派发普通 action 的写法完全一致——中间件让 thunk "伪装"成了普通 action。

条件派发示例

一个返回函数以执行条件派发的 action creator:

function incrementIfOdd() { return (dispatch, getState) => { const { counter } = getState() if (counter % 2 === 0) { return } dispatch(increment()) } }

incrementIfOdd()借助getState读取当前状态,仅在计数器为奇数时才真正派发increment()——这就是"条件延迟派发"的典型形态。

中间件的判定机制(源码级)

为什么函数能被dispatch接收?答案在 src/index.ts 的中间件核心实现中:

const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> = ({ dispatch, getState }) => next => action => { // thunk 中间件会检查所有传给 store.dispatch 的值。 // 如果这个 "action" 实际上是个函数,就调用它并返回结果。 if (typeof action === 'function') { // 注入 store 的 dispatch 和 getState 方法,以及任何 "extra arg" return action(dispatch, getState, extraArgument) } // 否则,像普通中间件一样把 action 继续传给下一个中间件 return next(action) }

三段式柯里化是 Redux 自定义中间件的标准结构:第一层接收{ dispatch, getState }(中间件 API),第二层接收next(下一个中间件),第三层接收action。唯一的判别逻辑是typeof action === 'function'——是函数就调用并注入三个参数,否则原样交给next。整套中间件逻辑只有十几行,这正是 thunk 之所以"轻量且易于理解"的根本原因。

组合:用 Promise 编排异步控制流

Thunk 最有价值的能力在于组合(Composition):内层函数的任何返回值都会成为dispatch本身的返回值。这为"thunk action creator 互相派发、返回 Promise 互相等待"的异步控制流编排提供了极大便利。

注意:以下组合 API 要求redux@>=3.1.0(当前仓库声明的对等依赖为redux@^5.0.0)。

import { createStore, applyMiddleware } from 'redux' import { thunk } from 'redux-thunk' import rootReducer from './reducers' const store = createStore(rootReducer, applyMiddleware(thunk)) function fetchSecretSauce() { return fetch('https://www.google.com/search?q=secret+sauce') } // 这些是你已经见过的普通 action creator。 // 它们返回的 action 无需任何中间件即可被派发。 // 但它们只表达 "事实",不表达 "异步流程"。 function makeASandwich(forPerson, secretSauce) { return { type: 'MAKE_SANDWICH', forPerson, secretSauce } } function apologize(fromPerson, toPerson, error) { return { type: 'APOLOGIZE', fromPerson, toPerson, error } } function withdrawMoney(amount) { return { type: 'WITHDRAW', amount } } // 即使没有中间件,你也可以派发一个 action: store.dispatch(withdrawMoney(100)) // 但当你需要发起异步动作(如 API 调用或路由跳转)时该怎么办? // 认识一下 thunks。 // 在此语境下,thunk 是一个可以被派发以执行异步活动、 // 并能派发 action 和读取状态的函数。 // 下面是一个返回 thunk 的 action creator: function makeASandwichWithSecretSauce(forPerson) { // 我们可以在这里反转控制权——返回一个函数,即 "thunk"。 // 当这个函数被传给 dispatch 时,thunk 中间件会拦截它, // 并以 dispatch 和 getState 作为参数调用它。 // 这赋予了 thunk 函数运行逻辑、同时与 store 交互的能力。 return function (dispatch) { return fetchSecretSauce().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize('The Sandwich Shop', forPerson, error)) ) } } // Thunk 中间件让我可以像派发普通 action 一样派发 thunk 异步 action! store.dispatch(makeASandwichWithSecretSauce('Me')) // 它甚至会把 thunk 的返回值从 dispatch 中透传出来, // 因此只要我返回 Promise,就可以一直链式串联下去。 store.dispatch(makeASandwichWithSecretSauce('My partner')).then(() => { console.log('Done!') }) // 事实上,我可以编写从其他 action creator 派发普通 action 和异步 action // 的 action creator,并用 Promise 构建我的控制流。 function makeSandwichesForEverybody() { return function (dispatch, getState) { if (!getState().sandwiches.isShopOpen) { // 你不一定非要返回 Promise,但这是一种方便约定—— // 这样调用方总能对异步 dispatch 的结果调用 .then()。 return Promise.resolve() } // 我们既可以派发普通对象 action,也可以派发其他 thunk, // 这让我们能在单一流程中组合异步动作。 return dispatch(makeASandwichWithSecretSauce('My Grandma')) .then(() => Promise.all([ dispatch(makeASandwichWithSecretSauce('Me')), dispatch(makeASandwichWithSecretSauce('My wife')) ]) ) .then(() => dispatch(makeASandwichWithSecretSauce('Our kids'))) .then(() => dispatch( getState().myMoney > 42 ? withdrawMoney(42) : apologize('Me', 'The Sandwich Shop') ) ) } } // 这对服务端渲染非常有用,因为我可以一直等到数据就绪, // 再同步渲染整个应用。 store .dispatch(makeSandwichesForEverybody()) .then(() => response.send(ReactDOMServer.renderToString(<MyApp store={store} />)) ) // 我也可以在组件的 props 变化时派发一个 thunk 异步 action 来加载缺失的数据。 import { connect } from 'react-redux' import { Component } from 'react' class SandwichShop extends Component { componentDidMount() { this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson)) } componentDidUpdate(prevProps) { if (prevProps.forPerson !== this.props.forPerson) { this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson)) } } render() { return <p>{this.props.sandwiches.join('mustard')}</p> } } export default connect(state => ({ sandwiches: state.sandwiches }))(SandwichShop)

这个示例浓缩了 thunk 组合的全部要点:

  1. 透传返回值:dispatch返回 thunk 的返回值,Promise 得以跨 thunk 传递;
  2. 并发与串行混合:Promise.all并发派发多个 thunk,then串行编排依赖关系;
  3. 状态参与决策:getState().isShopOpen、getState().myMoney在流程中实时读取最新状态;
  4. 服务端渲染等待数据:先dispatch再渲染,保证首屏数据完整;
  5. 组件驱动补数据:componentDidMount/componentDidUpdate中按需派发 thunk。

返回值透传的测试验证

"任何返回值都会成为dispatch的返回值"这一行为,在 test/index.test.ts 中有直接验证:当 action 是函数时,actionHandler(() => expected)的结果等于expected(expect(outcome).toBe(expected));同时该测试还验证了 thunk 是同步调用的(调用后副作用立即生效,expect(mutated).toBe(1)),以及普通对象 action 会原样透传给next、非函数的next返回值也会被原样返回。

TypeScript 类型系统支持

redux-thunk 的完整类型定义位于 src/types.ts,并在 src/index.ts 中全部重新导出。核心类型包括:

  • ThunkAction<ReturnType, State, ExtraThunkArg, BasicAction>:描述一个 thunk 函数签名,接收dispatch、getState、extraArgument三个参数并返回ReturnType。它重载了dispatch的类型,使其既能接收 thunk 函数(返回 thunk 的返回值),也能接收标准 action 对象(返回 action 本身);
  • ThunkDispatch<State, ExtraThunkArg, BasicAction>:thunk 中间件改造后的dispatch类型,含三个重载:thunk 函数重载、普通 action 重载,以及为规避 TypeScript 泛型推断问题(对应社区 issue #248)而增加的联合重载;
  • ThunkActionDispatch<ActionCreator>:描述 thunk action creator 被bindActionCreators()包装后的签名——接收外层函数的参数,返回内层 thunk 函数的返回值类型;
  • ThunkMiddleware<State, BasicAction, ExtraThunkArg>:中间件本身的类型,默认State = any、BasicAction = AnyAction、ExtraThunkArg = undefined。

typescript_test/index.test-d.ts 中的类型测试(通过tsc --noEmit运行,脚本见 package.json 的type-tests)覆盖了:dispatch可调用 thunk 与普通 action、getState()返回精确的 State 类型、withExtraArgument('bar')后 thunk 第三参数被推断为string、以及bindActionCreators场景下ThunkActionDispatch的返回值类型推导。若希望store.dispatch获得这些精确类型,可像类型测试中那样将 store 显式声明为createStore(reducer, applyMiddleware(thunk as ThunkMiddleware<State, Actions>))。

源码与测试:中间件的完整行为契约

综合 src/index.ts、test/index.test.ts 与 typescript_test/index.test-d.ts,可以归纳出 redux-thunk 的完整行为契约:

行为源码位置测试验证
中间件为三段柯里化结构(API → next → action)src/index.tstest/index.test.ts:nextHandler、actionHandler均为函数
typeof action === 'function'时调用 thunk 并注入dispatch/getState/extraArgumentsrc/index.tstest/index.test.ts、L87-L100
非函数 action 原样透传给nextsrc/index.tstest/index.test.ts
thunk 返回值成为dispatch返回值src/index.tstest/index.test.ts
thunk 被同步调用src/index.tstest/index.test.ts
未传入中间件 API 对象时抛错src/index.ts(解构undefined)test/index.test.ts
withExtraArgument(arg)等价于定制化的 thunk 工厂src/index.tstest/index.test.ts

整套测试由 Vitest 驱动(vitest --run --typecheck,配置见 vitest.config.mts 与 package.json)。

结语

Redux Thunk 以极其精简的实现(核心中间件仅十余行)解决了 Redux 异步逻辑的基础难题:用函数延迟 action 的派发,借助dispatch与getState让异步逻辑与 store 深度交互,再通过返回值透传实现 Promise 化的流程编排。它默认集成于 Redux Toolkit,也可通过applyMiddleware手动接入;withExtraArgument则让依赖注入与测试替身变得轻而易举。当你面对 AJAX 请求、条件派发、串行/并行的异步流程、服务端渲染数据预取等场景时,thunk 都是值得优先选择的轻量方案——这也正是它被 Redux 官方推荐为"基础副作用逻辑默认中间件"的原因所在。项目以 MIT 协议开源,你可以在此基础上自由地集成、扩展与二次分发。

  • 前端

【免费下载链接】redux-thunk

Thunk middleware for Redux

项目地址:https://gitcode.com/gh_mirrors/re/redux-thunk
点击查看免费下载
上一篇:实时通知系统Maybe:财务事件即时推送
下一篇:革命性iOS开源集合open-source-ios-apps:开发者必备的1621个应用模板

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询