WinddSnow

Redux-Toolkit-Part2-createAsyncThunk-RTK-Query-TypeScript-Integration

字数统计: 5.1k阅读时长: 23 min
2026/08/01

第100课:状态管理:Redux Toolkit(下)——createAsyncThunk、RTK Query、TypeScript 集成

上一课我们学习了 Redux Toolkit 的核心——createSliceconfigureStoreuseSelectoruseDispatch,掌握了同步状态管理的正确姿势。然而,真实应用中最具挑战性的部分通常是异步操作:从服务器获取数据、提交表单、处理加载中和错误状态。传统 Redux 中处理异步需要手写 thunk、saga 或使用中间件,代码分散且重复。Redux Toolkit 提供了两个强力工具来简化异步:**createAsyncThunk** 将异步逻辑封装为可追踪生命周期的 action(pending/fulfilled/rejected);RTK Query 更进一步,将数据获取和缓存抽象为声明式的 API 端点定义,自动管理加载状态、缓存失效和乐观更新。本节还将讲解如何在 TypeScript 中为整个 Redux 层提供完整的类型安全。


1. createAsyncThunk:标准化异步 Action 流

在传统 Redux 中,一个典型的异步数据获取需要 dispatch 三个 action:请求开始(设置 loading: true)、请求成功(设置 dataloading: false)、请求失败(设置 errorloading: false)。这些 action 和对应的 state 更新代码分散且高度重复。createAsyncThunk 将这个模式封装为一个函数——你只需提供一个返回 Promise 的异步函数,RTK 会自动生成三个 action creator(pendingfulfilledrejected)并管理它们的派发。

1.1 定义 Async Thunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// features/posts/postsSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 创建一个异步 thunk
export const fetchPosts = createAsyncThunk(
'posts/fetchPosts', // action type 前缀
async (userId, thunkAPI) => {
const response = await fetch(`/api/users/${userId}/posts`);
if (!response.ok) {
throw new Error('获取文章失败');
}
return response.json(); // 返回值成为 fulfilled action 的 payload
}
);

参数解析

  • 第一个参数是 action type 字符串(RTK 会自动生成 posts/fetchPosts/pendingposts/fetchPosts/fulfilledposts/fetchPosts/rejected)。
  • 第二个参数是一个异步函数。它接收两个参数:调用时传入的 payload(此处是 userId)和 thunkAPI(一个包含 dispatchgetStaterejectWithValue 等方法的工具对象)。
  • 异步函数的返回值会自动成为 fulfilled action 的 payload。如果抛出异常,异常对象会成为 rejected action 的 error 字段。

使用 rejectWithValue 自定义错误:如果希望 rejected action 携带自定义的错误信息(而非默认的异常对象),可以在 catch 块中调用 thunkAPI.rejectWithValue(customError)

1
2
3
4
5
6
7
8
9
10
11
12
export const fetchPosts = createAsyncThunk(
'posts/fetchPosts',
async (userId, { rejectWithValue }) => {
try {
const response = await fetch(`/api/users/${userId}/posts`);
if (!response.ok) throw new Error('获取文章失败');
return await response.json();
} catch (err) {
return rejectWithValue(err.message);
}
}
);

1.2 在 Slice 中处理 Async Thunk 的生命周期

createSliceextraReducers 字段用于处理由外部来源(如 createAsyncThunk)生成的 action。通过 builder.addCase 链式调用,可以为每个生命周期阶段(pendingfulfilledrejected)编写 reducer 逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const postsSlice = createSlice({
name: 'posts',
initialState: {
items: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null,
},
reducers: {
// 同步 reducer(如清空列表)
clearPosts(state) {
state.items = [];
state.status = 'idle';
state.error = null;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state) => {
state.status = 'loading';
state.error = null;
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded';
state.items = action.payload; // 服务端返回的数据
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload ?? action.error.message;
});
},
});

关键概念

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchPosts, clearPosts } from './postsSlice';

function PostsList({ userId }) {
const dispatch = useDispatch();
const { items, status, error } = useSelector(state => state.posts);

useEffect(() => {
if (status === 'idle') {
dispatch(fetchPosts(userId));
}
}, [userId, status, dispatch]);

if (status === 'loading') return <div>加载中...</div>;
if (status === 'failed') return <div>错误:{error}</div>;

return (
<div>
{items.map(post => (
<article key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</article>
))}
</div>
);
}

设计要点

  • status 字段统一管理异步流程:'idle'(尚未请求)、'loading'(请求中)、'succeeded'(请求成功)、'failed'(请求失败)。这是社区公认的异步状态管理模式,比使用多个布尔值(loadingerror)更清晰、更不易出现矛盾状态。
  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// services/api.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
reducerPath: 'api', // Store 中存储 API 状态的位置
baseQuery: fetchBaseQuery({ // 基础请求配置(基于 fetch 封装)
baseUrl: '/api',
}),
tagTypes: ['Post', 'User'], // 定义缓存标签(用于失效和重新获取)
endpoints: (builder) => ({

// 查询端点:获取数据(自动缓存)
getPosts: builder.query({
query: (userId) => `/users/${userId}/posts`,
providesTags: (result) =>
result
? [...result.map(({ id }) => ({ type: 'Post', id })), { type: 'Post', id: 'LIST' }]
: [{ type: 'Post', id: 'LIST' }],
}),

getPost: builder.query({
query: (postId) => `/posts/${postId}`,
providesTags: (result, error, postId) => [{ type: 'Post', id: postId }],
}),

// 变更端点:创建、更新、删除(自动使缓存失效)
addPost: builder.mutation({
query: (newPost) => ({
url: '/posts',
method: 'POST',
body: newPost,
}),
invalidatesTags: [{ type: 'Post', id: 'LIST' }],
}),

updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch,
}),
invalidatesTags: (result, error, { id }) => [{ type: 'Post', id }],
}),

deletePost: builder.mutation({
query: (id) => ({
url: `/posts/${id}`,
method: 'DELETE',
}),
invalidatesTags: (result, error, id) => [{ type: 'Post', id }],
}),
}),
});

// 导出自动生成的 Hooks
export const {
useGetPostsQuery,
useGetPostQuery,
useAddPostMutation,
useUpdatePostMutation,
useDeletePostMutation,
} = api;

自动生成的 Hooks 命名规则use{EndpointName}Query(查询)和 use{EndpointName}Mutation(变更)。查询 Hooks 在组件挂载时自动发起请求,变更 Hooks 返回一个数组 [trigger, { isLoading }]

2.2 在 Store 中集成 RTK Query

api.reducerapi.middleware 注册到 Store 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// store.js
import { configureStore } from '@reduxjs/toolkit';
import { api } from './services/api';
import postsReducer from './features/posts/postsSlice';

const store = configureStore({
reducer: {
posts: postsReducer,
[api.reducerPath]: api.reducer, // 添加 API reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware), // 添加 API 中间件(缓存、订阅等)
});

export default store;

RTK Query 的中间件负责处理缓存生命周期、轮询、重新获取等高级特性,必须添加到中间件链中。

2.3 在组件中使用查询 Hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { useGetPostsQuery, useAddPostMutation, useDeletePostMutation } from './services/api';

function PostsPage({ userId }) {
// 自动获取数据:data 为响应数据,isLoading 和 isError 追踪状态
const { data: posts = [], isLoading, isError, error } = useGetPostsQuery(userId);

// 变更 Hooks 返回一个数组:[触发函数, { isLoading }]
const [addPost, { isLoading: isAdding }] = useAddPostMutation();
const [deletePost] = useDeletePostMutation();

if (isLoading) return <div>加载中...</div>;
if (isError) return <div>错误:{error.data?.message || error.error}</div>;

return (
<div>
<button
disabled={isAdding}
onClick={() => addPost({ title: '新文章', body: '内容...', userId })}
>
{isAdding ? '添加中...' : '新增文章'}
</button>
<ul>
{posts.map(post => (
<li key={post.id}>
{post.title}
<button onClick={() => deletePost(post.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}

查询 Hooks 返回的核心字段

字段 说明
data 服务端返回的数据(成功时)。
isLoading 首次加载中(仅首次请求时为 true)。
isFetching 任何请求进行中(包括后台重新获取),适合显示加载指示器。
isError 请求是否失败。
error 错误对象(包含 statusdataerror 字段)。
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 中推导出 RootStateAppDispatch 类型,并创建类型化的 Hooks

3.1 推导 Store 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// store.ts
import { configureStore } from '@reduxjs/toolkit';
import postsReducer from './features/posts/postsSlice';
import { api } from './services/api';

const store = configureStore({
reducer: {
posts: postsReducer,
[api.reducerPath]: api.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
});

// 从 store 自身推导类型
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export default store;

RootState 是所有 reducer 组合后的完整 state 类型。AppDispatch 是增强后的 dispatch 类型(包含 thunk 等中间件)。

3.2 创建类型化 Hooks

在 React 组件中直接使用 useSelectoruseDispatch 会丢失类型信息(默认类型为 any 或泛型 DefaultRootState)。应该创建类型增强版本的 Hooks:

1
2
3
4
5
6
7
// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

// 在整个应用中使用这些类型化版本,而非原始的 useDispatch 和 useSelector
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

之后在所有组件中导入 useAppDispatchuseAppSelector 替代原始的 Hooks。useAppSelector 会自动推导 state 的类型,无需在每个选择器中手动标注。

3.3 createSlicecreateAsyncThunk 的 TypeScript 写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// features/counter/counterSlice.ts
import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
import type { RootState } from '../../store';

interface CounterState {
value: number;
status: 'idle' | 'loading' | 'failed';
}

const initialState: CounterState = {
value: 0,
status: 'idle',
};

// Async thunk 类型:<返回值类型, 参数类型>
export const incrementAsync = createAsyncThunk<number, number>(
'counter/incrementAsync',
async (amount: number) => {
await new Promise(resolve => setTimeout(resolve, 1000));
return amount;
}
);

const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
// PayloadAction<参数类型>
increment: (state, action: PayloadAction<number | undefined>) => {
state.value += action.payload ?? 1;
},
},
extraReducers: (builder) => {
builder.addCase(incrementAsync.pending, (state) => {
state.status = 'loading';
});
builder.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = 'idle';
state.value += action.payload; // payload 类型为 number
});
builder.addCase(incrementAsync.rejected, (state) => {
state.status = 'failed';
});
},
});

export const { increment } = counterSlice.actions;

// 选择器
export const selectCount = (state: RootState) => state.counter.value;

export default counterSlice.reducer;

关键类型注解

  • PayloadAction<P> 标注 reducer 的 action 参数类型,Ppayload 的类型。
  • createAsyncThunk<ReturnType, ArgumentType> 提供 thunk 的返回值类型和调用参数类型。
  • 选择器函数中的 state: RootState 确保访问路径的类型安全。

4. 综合实战:带缓存的博客管理页面

以下示例整合 createSlicecreateAsyncThunk、RTK Query 和 TypeScript,构建一个完整的博客管理页面。RTK Query 负责文章列表的获取和缓存,createAsyncThunk 负责一个特殊的“发布文章”操作(该操作涉及多个 reducer 的复杂交互)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// ---- store.ts ----
import { configureStore } from '@reduxjs/toolkit';
import postsReducer from './features/posts/postsSlice';
import { api } from './services/api';

const store = configureStore({
reducer: {
posts: postsReducer,
[api.reducerPath]: api.reducer,
},
middleware: (gDM) => gDM().concat(api.middleware),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;

// ---- hooks.ts ----
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

// ---- services/api.ts ----
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

interface Post {
id: number;
title: string;
body: string;
userId: number;
}

export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Post'],
endpoints: (builder) => ({
getPosts: builder.query<Post[], number>({
query: (userId) => `/users/${userId}/posts`,
providesTags: (result) =>
result
? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
: ['Post'],
}),
addPost: builder.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({ url: '/posts', method: 'POST', body }),
invalidatesTags: ['Post'],
}),
}),
});

export const { useGetPostsQuery, useAddPostMutation } = api;

// ---- App.tsx ----
import { Provider } from 'react-redux';
import store from './store';
import BlogManager from './BlogManager';

export default function App() {
return (
<Provider store={store}>
<BlogManager userId={1} />
</Provider>
);
}

// ---- BlogManager.tsx ----
import { useGetPostsQuery, useAddPostMutation } from './services/api';
import { useAppDispatch, useAppSelector } from './hooks';
import { publishPost, selectDraft } from './features/posts/postsSlice';

function BlogManager({ userId }: { userId: number }) {
const { data: posts = [], isLoading } = useGetPostsQuery(userId);
const [addPost] = useAddPostMutation();
const dispatch = useAppDispatch();
const draft = useAppSelector(selectDraft);

if (isLoading) return <div>加载中...</div>;

return (
<div>
<h2>文章列表</h2>
<ul>
{posts.map(post => (
<li key={post.id}>
{post.title}
<button onClick={() => dispatch(publishPost(post))}>发布草稿</button>
</li>
))}
</ul>

<h3>新建文章</h3>
<button onClick={() => addPost({ title: draft, body: '...', userId })}>
提交
</button>
<p>草稿标题:{draft}</p>
</div>
);
}

设计要点

  • RTK Query 自动管理文章列表的缓存和失效,组件只需使用 Hook。
  • useAppSelectoruseAppDispatch 提供类型安全的读写。
  • 选择器 selectDraftpublishPost thunk 演示了与同步 Slice 的混合使用。

课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) createAsyncThunk 会自动生成哪三个 action 类型后缀?
    A. start / success / failure
    B. pending / fulfilled / rejected
    C. begin / done / error
    D. loading / loaded / failed

  2. (单选) RTK Query 通过什么机制知道某个查询在 mutation 后需要重新获取?
    A. 手动调用 refetch() 函数。
    B. 组件卸载后重新挂载。
    C. providesTagsinvalidatesTags 标签系统。
    D. 监听 Redux DevTools 事件。

  3. (填空) 使用 createSlice 时,为处理 createAsyncThunk 生成的外部 action,需要在 ______ 字段中使用 builder.addCase 模式。

  4. (多选) 关于 RTK Query 的描述,哪些是正确的?
    A. 查询 Hooks 在组件挂载时自动发起请求。
    B. 查询数据默认被缓存,相同参数的查询不会重复请求。
    C. 需要手动编写 loading/error 状态管理。
    D. 需要将 api.middleware 添加到 Store 中间件链。

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要为一个任务管理应用使用 Redux Toolkit 构建完整的异步状态管理。要求如下:

  • 使用 createAsyncThunk 创建一个 fetchTasks 异步操作,从 /api/tasks 获取任务列表。
  • tasksSlice 中使用 extraReducers 处理 pendingfulfilledrejected 状态,管理 statuserror
  • 使用 RTK Query 创建一个 addTask mutation,向 /api/tasks POST 新任务,并使任务列表缓存失效。
  • 使用 TypeScript:从 Store 推导 RootStateAppDispatch,创建类型化 Hooks(useAppSelectoruseAppDispatch)。
  • 创建一个 TaskList 组件,使用上述 Hooks 展示任务列表,并支持添加新任务。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成完整的 Redux 配置、Slice、API 服务和组件代码。提示词中需明确指定 TypeScript 类型推导、RTK Query 标签系统和异步状态处理逻辑。

三、Agent 模式下的提示词示例

你是一个资深前端开发 Agent。请为一个 TypeScript React 应用使用 Redux Toolkit 构建任务管理模块。需要创建以下文件:

  1. src/store.ts:使用 configureStore,包含 tasksReducerapi.reducer,导出 RootStateAppDispatch
  2. src/hooks.ts:创建 useAppDispatchuseAppSelector 类型化 Hooks。
  3. src/features/tasks/tasksSlice.ts
    • Task 接口:{ id: number; title: string; completed: boolean }
    • fetchTasks thunk:createAsyncThunk<Task[], void>,使用 fetch 请求 /api/tasksrejectWithValue 捕获错误。
    • tasksSliceinitialState: { items: Task[], status: 'idle' | 'loading' | 'succeeded' | 'failed', error: string | null }
    • extraReducers 处理三个生命周期。
    • 导出 reducer 和 selectAllTasks 选择器。
  4. src/services/api.ts
    • createApibaseUrl: '/api'
    • getTasks query:builder.query<Task[], void>query: () => '/tasks'providesTags: ['Task']
    • addTask mutation:builder.mutation<Task, Partial<Task>>,POST 到 /tasksinvalidatesTags: ['Task']
    • 导出自动生成的 Hooks。
  5. src/components/TaskList.tsx:使用 useGetTasksQuery 展示列表,使用 useAddTaskMutation 添加任务,显示 loading 和 error 状态。
  6. 所有文件使用严格 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 服务,将其 reducermiddleware 注册到 Store(与已有 reducer 并行不悖)。然后将一个功能模块的 HTTP 请求(如用户列表获取)从旧的 thunk + reducer 模式迁移为 RTK Query 的 query endpoint,删除对应的 thunk 和手动状态管理代码。逐步替换其他模块,最终所有标准化的 HTTP 数据获取都由 RTK Query 接管,仅保留特殊的异步操作用 createAsyncThunk

TypeScript 类型安全的最佳实践:从 store.getStatestore.dispatch 推导 RootStateAppDispatch 类型;创建类型化的 useAppSelectoruseAppDispatch Hooks 替代原始 Hooks;为 createSlice 的 reducer 添加 PayloadAction<T> 类型;为 createAsyncThunk<ReturnType, ArgumentType> 提供泛型参数;RTK Query 的 endpoint 定义中为 builder.query<ResponseType, ArgumentType>builder.mutation<ResponseType, PayloadType> 提供类型,确保从请求到渲染全链路类型安全。


课后练习答案

一、概念自测答案

  1. B

    • 解析:createAsyncThunk 自动生成 pendingfulfilledrejected 三种 action type 后缀。
  2. C

    • 解析:RTK Query 通过 providesTags(查询提供)和 invalidatesTags(变更失效)标签系统自动管理缓存失效和重新获取。
  3. extraReducers

    • 解析:createSliceextraReducers 用于处理外部 action(如 createAsyncThunk 生成的 action)。
  4. A、B、D

    • 解析:C 错误,RTK Query 自动管理 loading/error 状态(isLoadingisError),无需手动编写。A、B、D 均为正确描述。

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请用 Redux Toolkit 和 TypeScript 构建一个任务管理模块。要求:

  • tasksSlice:Task 接口,fetchTasks thunk(获取任务列表),extraReducers 处理三种状态。
  • RTK Query apigetTasks query 和 addTask mutation,使用标签系统实现缓存失效。
  • Store 集成两者,导出 RootState 和 AppDispatch。
  • 类型化 Hooks:useAppSelectoruseAppDispatch
  • TaskList 组件:展示任务列表,支持添加任务。
  • 输出所有文件:store.ts、hooks.ts、tasksSlice.ts、api.ts、TaskList.tsx。”
CATALOG
  1. 1. 第100课:状态管理:Redux Toolkit(下)——createAsyncThunk、RTK Query、TypeScript 集成
    1. 1.1. 1. createAsyncThunk:标准化异步 Action 流
      1. 1.1.1. 1.1 定义 Async Thunk
      2. 1.1.2. 1.2 在 Slice 中处理 Async Thunk 的生命周期
      3. 1.1.3. 1.3 在组件中派发 Async Thunk
    2. 1.2. 2. RTK Query:声明式数据获取与缓存
      1. 1.2.1. 2.1 创建 API 服务
      2. 1.2.2. 2.2 在 Store 中集成 RTK Query
      3. 1.2.3. 2.3 在组件中使用查询 Hooks
      4. 1.2.4. 2.4 RTK Query 与 Async Thunk 的选择
    3. 1.3. 3. TypeScript 集成:为 Redux 层提供类型安全
      1. 1.3.1. 3.1 推导 Store 类型
      2. 1.3.2. 3.2 创建类型化 Hooks
      3. 1.3.3. 3.3 createSlice 与 createAsyncThunk 的 TypeScript 写法
    4. 1.4. 4. 综合实战:带缓存的博客管理页面
    5. 1.5. 课后练习
      1. 1.5.1. 一、概念自测(选择题 / 填空题)
      2. 1.5.2. 二、AI 编程任务:编写面向 AI 的提示词
      3. 1.5.3. 三、Agent 模式下的提示词示例
      4. 1.5.4. 四、面试真题与参考答案
    6. 1.6. 课后练习答案
      1. 1.6.1. 一、概念自测答案
      2. 1.6.2. 二、AI 编程任务参考答案(提示词示例)