尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Redux Thunk 实战指南:Thunk 中间件原理、安装配置与异步流程编排

Redux Thunk 实战指南:Thunk 中间件原理、安装配置与异步流程编排 前端【免费下载链接】redux-thunkThunk middleware for Redux项目地址https://gitcode.com/gh_mirrors/re/redux-thunk点击查看免费下载本文以开源仓库 redux-thunk 的官方 README 为主体结合 中间件源码、类型定义 与 单元测试 展开讲解。你将系统掌握 Redux Thunk 是什么、如何在 Redux Toolkit 与手写 store 两种场景下安装配置、如何注入自定义参数extraArgument、以及如何利用 thunk 组合 Promise 编排复杂的异步控制流最终具备在真实项目中落地 thunk 异步逻辑的完整能力。一句话认识 Redux ThunkRedux 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 2x在定义时立刻算出结果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-thunk3.1.0的发布结构考虑得非常细致exports字段为不同环境提供了对应的入口types指向dist/redux-thunk.d.tsimport指向dist/redux-thunk.mjsESMdefault指向dist/cjs/redux-thunk.cjsCommonJSsideEffects: 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回调中传入自定义extraArgumentimport { 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 creatorconst INCREMENT_COUNTER INCREMENT_COUNTER function increment() { return { type: INCREMENT_COUNTER } } function incrementAsync() { return dispatch { setTimeout(() { // 太好了可以在 dispatch 中调用同步或异步 action dispatch(increment()) }, 1000) } }这里incrementAsync()返回的函数就是 thunk1 秒后通过dispatch派发一个普通的increment()action。调用方store.dispatch(incrementAsync())与派发普通 action 的写法完全一致——中间件让 thunk 伪装成了普通 action。条件派发示例一个返回函数以执行条件派发的 action creatorfunction incrementIfOdd() { return (dispatch, getState) { const { counter } getState() if (counter % 2 0) { return } dispatch(increment()) } }incrementIfOdd()借助getState读取当前状态仅在计数器为奇数时才真正派发increment()——这就是条件延迟派发的典型形态。中间件的判定机制源码级为什么函数能被dispatch接收答案在 src/index.ts 的中间件核心实现中const middleware: ThunkMiddlewareState, 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 要求redux3.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?qsecretsauce) } // 这些是你已经见过的普通 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 组合的全部要点透传返回值dispatch返回 thunk 的返回值Promise 得以跨 thunk 传递并发与串行混合Promise.all并发派发多个 thunkthen串行编排依赖关系状态参与决策getState().isShopOpen、getState().myMoney在流程中实时读取最新状态服务端渲染等待数据先dispatch再渲染保证首屏数据完整组件驱动补数据componentDidMount/componentDidUpdate中按需派发 thunk。返回值透传的测试验证任何返回值都会成为dispatch的返回值这一行为在 test/index.test.ts 中有直接验证当 action 是函数时actionHandler(() expected)的结果等于expectedexpect(outcome).toBe(expected)同时该测试还验证了 thunk 是同步调用的调用后副作用立即生效expect(mutated).toBe(1)以及普通对象 action 会原样透传给next、非函数的next返回值也会被原样返回。TypeScript 类型系统支持redux-thunk 的完整类型定义位于 src/types.ts并在 src/index.ts 中全部重新导出。核心类型包括ThunkActionReturnType, State, ExtraThunkArg, BasicAction描述一个 thunk 函数签名接收dispatch、getState、extraArgument三个参数并返回ReturnType。它重载了dispatch的类型使其既能接收 thunk 函数返回 thunk 的返回值也能接收标准 action 对象返回 action 本身ThunkDispatchState, ExtraThunkArg, BasicActionthunk 中间件改造后的dispatch类型含三个重载thunk 函数重载、普通 action 重载以及为规避 TypeScript 泛型推断问题对应社区 issue #248而增加的联合重载ThunkActionDispatchActionCreator描述 thunk action creator 被bindActionCreators()包装后的签名——接收外层函数的参数返回内层 thunk 函数的返回值类型ThunkMiddlewareState, 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 ThunkMiddlewareState, Actions))。源码与测试中间件的完整行为契约综合 src/index.ts、test/index.test.ts 与 typescript_test/index.test-d.ts可以归纳出 redux-thunk 的完整行为契约行为源码位置测试验证中间件为三段柯里化结构API → next → actionsrc/index.tstest/index.test.tsnextHandler、actionHandler均为函数typeof action function时调用 thunk 并注入dispatch/getState/extraArgumentsrc/index.tstest/index.test.ts、L87-L100非函数 action 原样透传给nextsrc/index.tstest/index.test.tsthunk 返回值成为dispatch返回值src/index.tstest/index.test.tsthunk 被同步调用src/index.tstest/index.test.ts未传入中间件 API 对象时抛错src/index.ts解构undefinedtest/index.test.tswithExtraArgument(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-thunkThunk middleware for Redux项目地址https://gitcode.com/gh_mirrors/re/redux-thunk点击查看免费下载相关推荐DeepSeek Coder 33B Base实战指南从安装到代码生成的完整教程DeepSeek Coder 33B Base实战指南从安装到代码生成的完整教程 想要体验最先进的代码生成AI模型吗DeepSeek Coder 33B BRedux 项目中的 Thunk 中间件异步逻辑处理指南Redux 项目中的 Thunk 中间件异步逻辑处理指南 什么是 Thunk 在编程领域thunk 是一个专业术语指的是 一段延迟执行的代码 。在前端范式变革下一代AI智能体如何重塑企业智能化生态范式变革下一代AI智能体如何重塑企业智能化生态 在人工智能技术快速演进的今天企业面临着从传统自动化向智能自主化转型的关键抉择。AI智能体生态系统正经历着从单AI Agent人工智能上一篇实时通知系统Maybe财务事件即时推送下一篇革命性iOS开源集合open-source-ios-apps开发者必备的1621个应用模板创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表