第100课:状态管理:Redux Toolkit(下)——createAsyncThunk、RTK Query、TypeScript 集成
上一课我们学习了 Redux Toolkit 的核心——createSlice、configureStore、useSelector 和 useDispatch,掌握了同步状态管理的正确姿势。然而,真实应用中最具挑战性的部分通常是异步操作:从服务器获取数据、提交表单、处理加载中和错误状态。传统 Redux 中处理异步需要手写 thunk、saga 或使用中间件,代码分散且重复。Redux Toolkit 提供了两个强力工具来简化异步:**createAsyncThunk** 将异步逻辑封装为可追踪生命周期的 action(pending/fulfilled/rejected);RTK Query 更进一步,将数据获取和缓存抽象为声明式的 API 端点定义,自动管理加载状态、缓存失效和乐观更新。本节还将讲解如何在 TypeScript 中为整个 Redux 层提供完整的类型安全。
1. createAsyncThunk:标准化异步 Action 流
在传统 Redux 中,一个典型的异步数据获取需要 dispatch 三个 action:请求开始(设置 loading: true)、请求成功(设置 data 和 loading: false)、请求失败(设置 error 和 loading: false)。这些 action 和对应的 state 更新代码分散且高度重复。createAsyncThunk 将这个模式封装为一个函数——你只需提供一个返回 Promise 的异步函数,RTK 会自动生成三个 action creator(pending、fulfilled、rejected)并管理它们的派发。
1.1 定义 Async Thunk
1 | // features/posts/postsSlice.js |
参数解析:
- 第一个参数是 action type 字符串(RTK 会自动生成
posts/fetchPosts/pending、posts/fetchPosts/fulfilled、posts/fetchPosts/rejected)。 - 第二个参数是一个异步函数。它接收两个参数:调用时传入的
payload(此处是userId)和thunkAPI(一个包含dispatch、getState、rejectWithValue等方法的工具对象)。 - 异步函数的返回值会自动成为
fulfilledaction 的payload。如果抛出异常,异常对象会成为rejectedaction 的error字段。
使用 rejectWithValue 自定义错误:如果希望 rejected action 携带自定义的错误信息(而非默认的异常对象),可以在 catch 块中调用 thunkAPI.rejectWithValue(customError)。
1 | export const fetchPosts = createAsyncThunk( |
1.2 在 Slice 中处理 Async Thunk 的生命周期
createSlice 的 extraReducers 字段用于处理由外部来源(如 createAsyncThunk)生成的 action。通过 builder.addCase 链式调用,可以为每个生命周期阶段(pending、fulfilled、rejected)编写 reducer 逻辑。
1 | const postsSlice = createSlice({ |
关键概念:
extraReducers使用“构建器回调”模式(builder => { ... }),而非对象字面量。这提供了更好的 TypeScript 类型推断。builder.addCase(asyncThunk.pending, reducer)是推荐写法(不使用字符串 action type)。- 每个生命周期对应的 reducer 中,
state同样是 Immer 草稿,可以直接“修改”。
1.3 在组件中派发 Async Thunk
组件中使用 useDispatch 派发异步 thunk,就像派发普通 action 一样。不同之处在于,dispatch(fetchPosts(123)) 返回的是这个异步 thunk 本身的 Promise,你可以在组件中 await 它来获知结果。
1 | import { useEffect } from 'react'; |
设计要点:
status字段统一管理异步流程:'idle'(尚未请求)、'loading'(请求中)、'succeeded'(请求成功)、'failed'(请求失败)。这是社区公认的异步状态管理模式,比使用多个布尔值(loading、error)更清晰、更不易出现矛盾状态。- 在
useEffect中检查status === 'idle'避免重复请求——如果已经有数据或正在加载,不再发起请求。
2. RTK Query:声明式数据获取与缓存
createAsyncThunk 解决了单个异步操作的问题,但在真实应用中,你需要管理多个 API 端点、缓存数据、处理失效和重新获取、追踪加载状态、实现乐观更新……这些逻辑如果全部手写,代码量会快速膨胀。RTK Query 是 Redux Toolkit 内置的数据获取和缓存方案,灵感来自 React Query 和 Apollo Client。它的核心理念是:定义 API 端点 → 自动生成 Hooks → 组件中直接使用 Hooks 获取数据,无需手写 thunk、reducer 或状态管理代码。
2.1 创建 API 服务
使用 createApi 定义 API 端点。RTK Query 会自动生成包含查询和变更操作的 React Hooks。
1 | // services/api.js |
自动生成的 Hooks 命名规则:use{EndpointName}Query(查询)和 use{EndpointName}Mutation(变更)。查询 Hooks 在组件挂载时自动发起请求,变更 Hooks 返回一个数组 [trigger, { isLoading }]。
2.2 在 Store 中集成 RTK Query
将 api.reducer 和 api.middleware 注册到 Store 中:
1 | // store.js |
RTK Query 的中间件负责处理缓存生命周期、轮询、重新获取等高级特性,必须添加到中间件链中。
2.3 在组件中使用查询 Hooks
1 | import { useGetPostsQuery, useAddPostMutation, useDeletePostMutation } from './services/api'; |
查询 Hooks 返回的核心字段:
| 字段 | 说明 |
|---|---|
data |
服务端返回的数据(成功时)。 |
isLoading |
首次加载中(仅首次请求时为 true)。 |
isFetching |
任何请求进行中(包括后台重新获取),适合显示加载指示器。 |
isError |
请求是否失败。 |
error |
错误对象(包含 status、data、error 字段)。 |
refetch |
手动触发重新请求的函数。 |
缓存与失效机制:
- 查询数据默认被缓存。相同参数的查询不会重复发送网络请求,直接从缓存返回。
- 当执行一个 mutation(如
addPost)且该 mutation 的invalidatesTags包含某个查询的providesTags时,RTK Query 会自动重新获取该查询的数据。上例中,addPost使{ type: 'Post', id: 'LIST' }失效,因此getPosts会自动重新请求,无需手动调用refetch。 - 这种基于标签的缓存失效机制是 RTK Query 最强大的特性之一——你只需声明数据之间的依赖关系,RTK Query 负责在合适时机更新。
2.4 RTK Query 与 Async Thunk 的选择
| 场景 | 推荐工具 |
|---|---|
| 从服务器获取数据并缓存 | RTK Query |
| 提交数据、更新资源 | RTK Query Mutation |
| 异步逻辑与 Redux state 深度耦合 | createAsyncThunk |
| 需要完全控制缓存行为 | RTK Query(可自定义) |
| 非 HTTP 的异步操作(如 WebSocket、定时器) | createAsyncThunk |
| 已存在大量传统 thunk 的项目,需要逐步迁移 | createAsyncThunk + RTK Query 混合使用 |
原则:任何涉及 HTTP 数据获取和缓存的场景,优先使用 RTK Query。仅在 RTK Query 无法满足需求(如非 HTTP 异步、需要与现有 Redux state 深度交互的特殊逻辑)时才使用 createAsyncThunk。
3. TypeScript 集成:为 Redux 层提供类型安全
TypeScript 与 Redux Toolkit 的结合提供了从 action 类型到组件 Hooks 的完整类型推导。关键是从 Store 中推导出 RootState 和 AppDispatch 类型,并创建类型化的 Hooks。
3.1 推导 Store 类型
1 | // store.ts |
RootState 是所有 reducer 组合后的完整 state 类型。AppDispatch 是增强后的 dispatch 类型(包含 thunk 等中间件)。
3.2 创建类型化 Hooks
在 React 组件中直接使用 useSelector 和 useDispatch 会丢失类型信息(默认类型为 any 或泛型 DefaultRootState)。应该创建类型增强版本的 Hooks:
1 | // hooks.ts |
之后在所有组件中导入 useAppDispatch 和 useAppSelector 替代原始的 Hooks。useAppSelector 会自动推导 state 的类型,无需在每个选择器中手动标注。
3.3 createSlice 与 createAsyncThunk 的 TypeScript 写法
1 | // features/counter/counterSlice.ts |
关键类型注解:
PayloadAction<P>标注 reducer 的action参数类型,P为payload的类型。createAsyncThunk<ReturnType, ArgumentType>提供 thunk 的返回值类型和调用参数类型。- 选择器函数中的
state: RootState确保访问路径的类型安全。
4. 综合实战:带缓存的博客管理页面
以下示例整合 createSlice、createAsyncThunk、RTK Query 和 TypeScript,构建一个完整的博客管理页面。RTK Query 负责文章列表的获取和缓存,createAsyncThunk 负责一个特殊的“发布文章”操作(该操作涉及多个 reducer 的复杂交互)。
1 | // ---- store.ts ---- |
设计要点:
- RTK Query 自动管理文章列表的缓存和失效,组件只需使用 Hook。
useAppSelector和useAppDispatch提供类型安全的读写。- 选择器
selectDraft和publishPostthunk 演示了与同步 Slice 的混合使用。
课后练习
一、概念自测(选择题 / 填空题)
(单选)
createAsyncThunk会自动生成哪三个 action 类型后缀?
A.start/success/failure
B.pending/fulfilled/rejected
C.begin/done/error
D.loading/loaded/failed(单选) RTK Query 通过什么机制知道某个查询在 mutation 后需要重新获取?
A. 手动调用refetch()函数。
B. 组件卸载后重新挂载。
C.providesTags和invalidatesTags标签系统。
D. 监听 Redux DevTools 事件。(填空) 使用
createSlice时,为处理createAsyncThunk生成的外部 action,需要在______字段中使用builder.addCase模式。(多选) 关于 RTK Query 的描述,哪些是正确的?
A. 查询 Hooks 在组件挂载时自动发起请求。
B. 查询数据默认被缓存,相同参数的查询不会重复请求。
C. 需要手动编写 loading/error 状态管理。
D. 需要将api.middleware添加到 Store 中间件链。
二、AI 编程任务:编写面向 AI 的提示词
场景:你需要为一个任务管理应用使用 Redux Toolkit 构建完整的异步状态管理。要求如下:
- 使用
createAsyncThunk创建一个fetchTasks异步操作,从/api/tasks获取任务列表。 - 在
tasksSlice中使用extraReducers处理pending、fulfilled、rejected状态,管理status和error。 - 使用 RTK Query 创建一个
addTaskmutation,向/api/tasksPOST 新任务,并使任务列表缓存失效。 - 使用 TypeScript:从 Store 推导
RootState和AppDispatch,创建类型化 Hooks(useAppSelector、useAppDispatch)。 - 创建一个
TaskList组件,使用上述 Hooks 展示任务列表,并支持添加新任务。
任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成完整的 Redux 配置、Slice、API 服务和组件代码。提示词中需明确指定 TypeScript 类型推导、RTK Query 标签系统和异步状态处理逻辑。
三、Agent 模式下的提示词示例
你是一个资深前端开发 Agent。请为一个 TypeScript React 应用使用 Redux Toolkit 构建任务管理模块。需要创建以下文件:
src/store.ts:使用configureStore,包含tasksReducer和api.reducer,导出RootState和AppDispatch。src/hooks.ts:创建useAppDispatch和useAppSelector类型化 Hooks。src/features/tasks/tasksSlice.ts:
Task接口:{ id: number; title: string; completed: boolean }。fetchTasksthunk:createAsyncThunk<Task[], void>,使用fetch请求/api/tasks,rejectWithValue捕获错误。tasksSlice:initialState: { items: Task[], status: 'idle' | 'loading' | 'succeeded' | 'failed', error: string | null }。extraReducers处理三个生命周期。- 导出 reducer 和
selectAllTasks选择器。src/services/api.ts:
createApi,baseUrl: '/api'。getTasksquery:builder.query<Task[], void>,query: () => '/tasks',providesTags: ['Task']。addTaskmutation:builder.mutation<Task, Partial<Task>>,POST 到/tasks,invalidatesTags: ['Task']。- 导出自动生成的 Hooks。
src/components/TaskList.tsx:使用useGetTasksQuery展示列表,使用useAddTaskMutation添加任务,显示 loading 和 error 状态。- 所有文件使用严格 TypeScript。完成后列出所有文件内容。
四、面试真题与参考答案
题目(字节跳动前端面试题):
请解释 Redux Toolkit 中
createAsyncThunk和 RTK Query 的职责定位和适用场景。在一个已有 Redux 状态管理的大型应用中,如何渐进式地引入 RTK Query 来管理 HTTP 数据获取?在 TypeScript 中,如何为整个 Redux 层提供类型安全?
参考答案:
createAsyncThunk 适合处理与 Redux state 深度耦合的单次异步操作(如登录、表单提交、复杂异步流程),它生成标准化的 pending/fulfilled/rejected action,开发者手动管理 state 更新。RTK Query 则专注于 HTTP 数据获取和缓存的抽象——定义 API 端点后自动生成 Hooks,管理缓存生命周期、自动失效和数据同步。前者更灵活,后者更自动化。
在已有大型 Redux 应用中渐进式引入 RTK Query:首先独立创建 createApi 服务,将其 reducer 和 middleware 注册到 Store(与已有 reducer 并行不悖)。然后将一个功能模块的 HTTP 请求(如用户列表获取)从旧的 thunk + reducer 模式迁移为 RTK Query 的 query endpoint,删除对应的 thunk 和手动状态管理代码。逐步替换其他模块,最终所有标准化的 HTTP 数据获取都由 RTK Query 接管,仅保留特殊的异步操作用 createAsyncThunk。
TypeScript 类型安全的最佳实践:从 store.getState 和 store.dispatch 推导 RootState 和 AppDispatch 类型;创建类型化的 useAppSelector 和 useAppDispatch Hooks 替代原始 Hooks;为 createSlice 的 reducer 添加 PayloadAction<T> 类型;为 createAsyncThunk<ReturnType, ArgumentType> 提供泛型参数;RTK Query 的 endpoint 定义中为 builder.query<ResponseType, ArgumentType> 和 builder.mutation<ResponseType, PayloadType> 提供类型,确保从请求到渲染全链路类型安全。
课后练习答案
一、概念自测答案
B
- 解析:
createAsyncThunk自动生成pending、fulfilled、rejected三种 action type 后缀。
- 解析:
C
- 解析:RTK Query 通过
providesTags(查询提供)和invalidatesTags(变更失效)标签系统自动管理缓存失效和重新获取。
- 解析:RTK Query 通过
extraReducers- 解析:
createSlice的extraReducers用于处理外部 action(如createAsyncThunk生成的 action)。
- 解析:
A、B、D
- 解析:C 错误,RTK Query 自动管理 loading/error 状态(
isLoading、isError),无需手动编写。A、B、D 均为正确描述。
- 解析:C 错误,RTK Query 自动管理 loading/error 状态(
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请用 Redux Toolkit 和 TypeScript 构建一个任务管理模块。要求:
tasksSlice:Task 接口,fetchTasksthunk(获取任务列表),extraReducers 处理三种状态。- RTK Query
api:getTasksquery 和addTaskmutation,使用标签系统实现缓存失效。- Store 集成两者,导出 RootState 和 AppDispatch。
- 类型化 Hooks:
useAppSelector、useAppDispatch。TaskList组件:展示任务列表,支持添加任务。- 输出所有文件:store.ts、hooks.ts、tasksSlice.ts、api.ts、TaskList.tsx。”