下面,我们来系统的梳理关于 Redux Toolkit 异步操作:createAsyncThunk 的基本知识点:


一、createAsyncThunk 概述

1.1 为什么需要 createAsyncThunk

在 Redux 中处理异步操作(如 API 调用)时,传统方法需要手动处理:

  • 多个 action(请求开始、成功、失败)
  • 复杂的 reducer 逻辑
  • 错误处理重复代码
  • 取消操作难以实现

createAsyncThunk 解决的问题

  • 自动生成异步生命周期 actions
  • 简化异步状态管理(pending/fulfilled/rejected)
  • 内置错误处理机制
  • 支持请求取消

1.2 核心特点

  • 标准化流程:自动生成三种 action 类型
  • Promise 集成:基于 Promise 的异步操作
  • 错误处理:自动捕获错误并 dispatch rejected action
  • TypeScript 友好:完整的类型支持
  • Redux Toolkit 集成:与 createSlice 无缝协作

二、基本用法与核心概念

2.1 创建异步 Thunk

import { createAsyncThunk } from '@reduxjs/toolkit';export const fetchUser = createAsyncThunk(// 唯一标识符:'feature/actionName''users/fetchUser',// 异步 payload 创建器async (userId, thunkAPI) => {try {const response = await fetch(`/api/users/${userId}`);return await response.json(); // 作为 fulfilled action 的 payload} catch (error) {// 返回拒绝原因return thunkAPI.rejectWithValue(error.message);}}
);

2.2 参数详解

参数类型说明
typePrefixstring唯一标识符,自动生成三种 action 类型
payloadCreatorfunction包含异步逻辑的函数,返回 Promise
optionsobject可选配置项(如条件执行)

2.3 自动生成的 Action Types

fetchUser.pending;   // 'users/fetchUser/pending'
fetchUser.fulfilled; // 'users/fetchUser/fulfilled'
fetchUser.rejected;  // 'users/fetchUser/rejected'

三、与 createSlice 集成

3.1 在 extraReducers 中处理状态

import { createSlice } from '@reduxjs/toolkit';
import { fetchUser } from './userThunks';const userSlice = createSlice({name: 'user',initialState: {data: null,status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'error: null},reducers: {// 同步 reducers...},extraReducers: (builder) => {builder.addCase(fetchUser.pending, (state) => {state.status = 'loading';state.error = null;}).addCase(fetchUser.fulfilled, (state, action) => {state.status = 'succeeded';state.data = action.payload;}).addCase(fetchUser.rejected, (state, action) => {state.status = 'failed';state.error = action.payload || action.error.message;});}
});export default userSlice.reducer;

3.2 状态管理最佳实践

const initialState = {data: null,// 异步状态标识isLoading: false,isSuccess: false,isError: false,error: null
};// 在 extraReducers 中:
.addCase(fetchUser.pending, (state) => {state.isLoading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {state.isLoading = false;state.isSuccess = true;state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {state.isLoading = false;state.isError = true;state.error = action.payload;
});

四、高级功能与技巧

4.1 访问 State 和 Dispatch

通过 thunkAPI 参数访问:

export const updateUser = createAsyncThunk('users/updateUser',async (userData, thunkAPI) => {const { getState, dispatch } = thunkAPI;// 获取当前状态const { auth } = getState();const token = auth.token;try {const response = await fetch('/api/users', {method: 'PUT',headers: {'Authorization': `Bearer ${token}`,'Content-Type': 'application/json'},body: JSON.stringify(userData)});if (!response.ok) {// 处理 API 错误const error = await response.json();throw new Error(error.message);}// 触发其他 actiondispatch(showNotification('用户信息已更新'));return await response.json();} catch (error) {return thunkAPI.rejectWithValue(error.message);}}
);

4.2 条件执行(Conditional Execution)

export const fetchUser = createAsyncThunk('users/fetchUser',async (userId, thunkAPI) => {// 实现逻辑...},{condition: (userId, { getState }) => {const { users } = getState();// 如果用户已在缓存中,则取消请求if (users.data[userId]) {return false; // 取消执行}// 如果正在加载,则取消if (users.status === 'loading') {return false;}return true; // 允许执行}}
);

4.3 请求取消

export const searchProducts = createAsyncThunk('products/search',async (query, thunkAPI) => {// 创建取消令牌const controller = new AbortController();const signal = controller.signal;// 注册取消回调thunkAPI.signal.addEventListener('abort', () => {controller.abort();});try {const response = await fetch(`/api/products?q=${query}`, { signal });return await response.json();} catch (error) {if (error.name === 'AbortError') {// 请求被取消,不视为错误return thunkAPI.rejectWithValue({ aborted: true });}return thunkAPI.rejectWithValue(error.message);}}
);// 在组件中取消请求
useEffect(() => {const promise = dispatch(searchProducts(query));return () => {promise.abort(); // 组件卸载时取消请求};
}, [dispatch, query]);

4.4 乐观更新

export const updatePost = createAsyncThunk('posts/update',async (postData, thunkAPI) => {const { id, ...data } = postData;const response = await api.updatePost(id, data);return response.data;}
);// 在 createSlice 中
extraReducers: (builder) => {builder.addCase(updatePost.fulfilled, (state, action) => {const index = state.posts.findIndex(p => p.id === action.payload.id);if (index !== -1) {state.posts[index] = action.payload;}}).addCase(updatePost.rejected, (state, action) => {// 回滚乐观更新const originalPost = action.meta.arg.originalPost;const index = state.posts.findIndex(p => p.id === originalPost.id);if (index !== -1) {state.posts[index] = originalPost;}});
}// 在 dispatch 时传递原始数据
dispatch(updatePost({id: 123,title: '新标题',originalPost: currentPost // 保存原始数据用于回滚
}));

五、错误处理

5.1 统一错误格式

export const fetchData = createAsyncThunk('data/fetch',async (_, thunkAPI) => {try {const response = await api.getData();return response.data;} catch (error) {// 标准化错误格式return thunkAPI.rejectWithValue({code: error.response?.status || 500,message: error.message,details: error.response?.data?.errors});}}
);// 在 reducer 中
.addCase(fetchData.rejected, (state, action) => {state.error = {code: action.payload.code || 500,message: action.payload.message || '未知错误',details: action.payload.details};
});

5.2 全局错误处理

// 中间件:全局错误处理
const errorLoggerMiddleware = store => next => action => {if (action.type.endsWith('/rejected')) {const error = action.error || action.payload;console.error('Redux 异步错误:', {type: action.type,error: error.message || error,stack: error.stack});// 发送错误到监控服务trackError(error);}return next(action);
};// 配置 store
const store = configureStore({reducer: rootReducer,middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(errorLoggerMiddleware)
});

六、测试策略

6.1 测试异步 Thunk

import configureStore from '@reduxjs/toolkit';
import { fetchUser } from './userThunks';
import userReducer from './userSlice';describe('fetchUser async thunk', () => {let store;beforeEach(() => {store = configureStore({reducer: {user: userReducer}});// 模拟 fetch APIglobal.fetch = jest.fn();});it('处理成功的用户获取', async () => {const mockUser = { id: 1, name: 'John' };fetch.mockResolvedValue({ok: true,json: () => Promise.resolve(mockUser)});await store.dispatch(fetchUser(1));const state = store.getState().user;expect(state.data).toEqual(mockUser);expect(state.status).toBe('succeeded');});it('处理失败的用户获取', async () => {fetch.mockRejectedValue(new Error('Network error'));await store.dispatch(fetchUser(1));const state = store.getState().user;expect(state.error).toBe('Network error');expect(state.status).toBe('failed');});
});

6.2 测试 Slice 的 extraReducers

import userReducer, { fetchUserPending, fetchUserFulfilled, fetchUserRejected 
} from './userSlice';describe('userSlice extraReducers', () => {const initialState = {data: null,status: 'idle',error: null};it('应处理 fetchUser.pending', () => {const action = { type: fetchUser.pending.type };const state = userReducer(initialState, action);expect(state).toEqual({data: null,status: 'loading',error: null});});it('应处理 fetchUser.fulfilled', () => {const mockUser = { id: 1, name: 'John' };const action = { type: fetchUser.fulfilled.type,payload: mockUser};const state = userReducer(initialState, action);expect(state).toEqual({data: mockUser,status: 'succeeded',error: null});});it('应处理 fetchUser.rejected', () => {const error = 'Failed to fetch';const action = { type: fetchUser.rejected.type,payload: error};const state = userReducer(initialState, action);expect(state).toEqual({data: null,status: 'failed',error});});
});

七、实践与性能优化

7.1 组织代码结构

src/├── app/│   └── store.js├── features/│   └── users/│       ├── usersSlice.js│       ├── userThunks.js      // 异步 thunks│       ├── userSelectors.js│       └── UserList.js└── services/└── api.js                 // API 客户端

7.2 创建 API 服务层

// services/api.js
import axios from 'axios';const api = axios.create({baseURL: '/api',timeout: 10000,headers: {'Content-Type': 'application/json'}
});export const fetchUser = (userId) => api.get(`/users/${userId}`);
export const createUser = (userData) => api.post('/users', userData);
export const updateUser = (userId, userData) => api.put(`/users/${userId}`, userData);
export const deleteUser = (userId) => api.delete(`/users/${userId}`);export default api;

7.3 封装可复用 Thunk 逻辑

// utils/createThunk.js
export function createThunk(typePrefix, apiCall) {return createAsyncThunk(typePrefix,async (arg, thunkAPI) => {try {const response = await apiCall(arg);return response.data;} catch (error) {const message = error.response?.data?.message || error.message;return thunkAPI.rejectWithValue(message);}});
}// 使用示例
import { createThunk } from '../utils/createThunk';
import { fetchUser } from '../../services/api';export const getUser = createThunk('users/getUser', fetchUser);

八、案例:电商应用商品管理

8.1 商品 Thunks

// features/products/productThunks.js
import { createAsyncThunk } from '@reduxjs/toolkit';
import { fetchProducts, fetchProductDetails,createProduct,updateProduct,deleteProduct
} from '../../services/api';export const loadProducts = createAsyncThunk('products/load',async (category, thunkAPI) => {try {const response = await fetchProducts(category);return response.data;} catch (error) {return thunkAPI.rejectWithValue(error.message);}}
);export const loadProductDetails = createAsyncThunk('products/loadDetails',async (productId, thunkAPI) => {try {const response = await fetchProductDetails(productId);return response.data;} catch (error) {return thunkAPI.rejectWithValue(error.message);}},{condition: (productId, { getState }) => {const { products } = getState();// 避免重复加载return !products.details[productId];}}
);export const addNewProduct = createAsyncThunk('products/add',async (productData, thunkAPI) => {try {const response = await createProduct(productData);return response.data;} catch (error) {return thunkAPI.rejectWithValue(error.response.data.errors);}}
);

8.2 商品 Slice

// features/products/productsSlice.js
import { createSlice } from '@reduxjs/toolkit';
import { loadProducts, loadProductDetails,addNewProduct
} from './productThunks';const initialState = {items: [],details: {},status: 'idle',loadingDetails: {},error: null,createStatus: 'idle'
};const productsSlice = createSlice({name: 'products',initialState,reducers: {clearProductError: (state) => {state.error = null;}},extraReducers: (builder) => {builder// 加载商品列表.addCase(loadProducts.pending, (state) => {state.status = 'loading';state.error = null;}).addCase(loadProducts.fulfilled, (state, action) => {state.status = 'succeeded';state.items = action.payload;}).addCase(loadProducts.rejected, (state, action) => {state.status = 'failed';state.error = action.payload;})// 加载商品详情.addCase(loadProductDetails.pending, (state, action) => {state.loadingDetails[action.meta.arg] = true;}).addCase(loadProductDetails.fulfilled, (state, action) => {state.loadingDetails[action.meta.arg] = false;state.details[action.meta.arg] = action.payload;}).addCase(loadProductDetails.rejected, (state, action) => {state.loadingDetails[action.meta.arg] = false;// 可以单独存储每个商品的错误信息})// 创建新商品.addCase(addNewProduct.pending, (state) => {state.createStatus = 'loading';state.error = null;}).addCase(addNewProduct.fulfilled, (state, action) => {state.createStatus = 'succeeded';state.items.unshift(action.payload); // 乐观更新}).addCase(addNewProduct.rejected, (state, action) => {state.createStatus = 'failed';state.error = action.payload;});}
});export const { clearProductError } = productsSlice.actions;
export default productsSlice.reducer;

九、总结

9.1 createAsyncThunk 核心优势

  1. 简化异步流程:自动生成三种 action 类型
  2. 标准化状态管理:pending/fulfilled/rejected 生命周期
  3. 内置错误处理:rejectWithValue 标准化错误
  4. 高级功能支持:条件执行、请求取消、乐观更新
  5. 测试友好:清晰的异步流程便于测试

9.2 实践总结

  • 分离业务逻辑:使用服务层封装 API 调用
  • 标准化错误处理:统一错误格式和全局处理
  • 合理使用条件执行:避免不必要的请求
  • 实施乐观更新:提升用户体验
  • 组件卸载时取消请求:避免内存泄漏
  • 使用 TypeScript:增强类型安全和开发体验

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/92043.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/92043.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/92043.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32F103C8T6 BC20模块NBIOT GPS北斗模块采集温湿度和经纬度发送到EMQX

云平台配置 访问下载页面:免费试用 EMQX Cloud 或 EMQX Enterprise | 下载 EMQX,根据需求选择对应版本下载。将下载的压缩包上传至服务器(推荐存放于C盘根目录,便于后续操作),并解压至指定路径&#xff08…

YOLO11涨点优化:自研检测头, 新创新点(SC_C_11Detect)检测头结构创新,实现有效涨点

目标检测领域迎来重大突破!本文揭秘原创SC_C_11Detect检测头,通过空间-通道协同优化与11层深度结构,在YOLO系列上实现mAP最高提升5.7%,小目标检测精度暴涨9.3%!创新性结构设计+即插即用特性,为工业检测、自动驾驶等场景带来革命性提升! 一、传统检测头的三大痛点 在目…

OSCP 考试期间最新考试政策

根据 Offensive Security 官方最新考试政策(2025 年 7 月),OSCP 考试期间禁止或严格限制以下工具与行为: 一、绝对禁止使用的工具/服务 类别举例说明商业/付费版本Metasploit Pro、Burp Suite Pro、Cobalt Strike、Canvas、Core …

如何基于MQ实现分布式事务

文章目录1.可靠消息最终一致性1.1 本地消息表1.1.1 本地消息表的优缺点1.消息堆积,扫表慢2.集中式扫表,会影响正常业务3.定时扫表的延迟问题1.1.2 本地消息表的代码实践1.表结构设计2.具体业务实现1.2 事务消息1.2.1 事务消息的三个阶段阶段1&#xff1a…

ARM学习(45)AXI协议总线学习

笔者来介绍一下ARM AMBA 总线中的AXI协议 1、简介 ARM 公司推出的AMBA 总线(Advanced Microcontroller Bus Architecture) ,目前已经推出到AMBA5版本。主要包括 APB:Advanced Peripheral Bus,针对外设 AHB:Advanced High-Performance Bus,高性能总线,支持64/128 位多管…

Visual C++与HGE游戏引擎:创建伪2.5D斜45度视角游戏

本文还有配套的精品资源,点击获取 简介:本教程专注讲解如何结合Visual C和HGE游戏引擎构建一个斜45度视角的伪2.5D游戏世界。HGE提供了DirectX的接口,简化了图形和音频处理,使得开发者可以专注于游戏逻辑和视觉效果的实现。教程…

打造个人数字图书馆:LeaNote+cpolar如何成为你的私有化知识中枢?

文章目录前言1. 安装Docker2. Docker本地部署Leanote蚂蚁笔记3. 安装cpolar内网穿透4. 固定Leanote蚂蚁笔记公网地址前言 在信息爆炸的时代,如何系统管理知识资产并实现价值输出?蚂蚁笔记(Leanote)提供了一种全新解决方案。这款开…

[特殊字符]️ 整个键盘控制无人机系统框架

🎯 五大核心模块详解1. 📥 输入处理模块keyboard_control_node ├── 功能:捕获键盘输入并转换为ROS消息 ├── 文件:keyboard_control.cpp ├── 输入:键盘按键 (W/A/S/D/R/F/Q/E/L/ESC) ├── 输出:g…

机器学习第三课之逻辑回归(三)LogisticRegression

目录 简介 1.下采样 2.过采样 简介 接上两篇篇博客最后,我们使用了K折交叉验证去寻找最合适的C值,提升模型召回率,对于选取C的最优值,我们就要把不同C值放到模型里面训练,然后用验证集去验证得到结果进行比较&#x…

1.Java语言有什么特点

1.Java语言有什么特点 1.面向对象编程,拥有封装,继承和多态的特性,所有可以很好的设计出低耦合的项目工程。 2.很好的可移植性,在Java中有java虚拟机(JVM)的支持,每写一个类都是.Class文件。J…

部署 Kibana 8.2.2 可视化管理 Elasticsearch 8.2.2 集群

✅ 适用版本:Elasticsearch 8.2.2 Kibana 8.2.2 一、环境准备 组件版本示例地址Elasticsearch8.2.2192.168.130.61:9200, 192.168.130.62:9200, 192.168.130.65:9200Kibana8.2.2部署在 192.168.130.651操作系统CentOS 7⚠️ 严格版本匹配:Kibana 8.2.2…

7.2 I/O接口 (答案见原书 P305)

第7章 输入/输出系统 7.1 I/O系统基本概念 (答案见原书 P301) & 7.2 I/O接口 (答案见原书 P305) 01. 在统一编址的方式下,区分存储单元和I/O设备是靠( A )。 题目原文 在统一编址的方式下,区分存储单元和I/O设备是靠( )。 A. 不同的地址码 B. 不同的地址线 C. 不同…

并发编程常用工具类(上):CountDownLatch 与 Semaphore 的协作应用

在 Java 并发编程领域,JDK 提供的工具类是简化多线程协作的重要武器。这些工具类基于 AQS(AbstractQueuedSynchronizer)框架实现,封装了复杂的同步逻辑,让开发者无需深入底层即可实现高效的线程协作。本文作为并发工具…

Go 工程化全景:从目录结构到生命周期的完整服务框架

今天天气很好, 正好手头有个小项目, 整理了一下中小项目标准化的痛点问题, 如下, 希望可以帮到大家. 一个成熟的 Go 项目不仅需要清晰的代码组织,还需要完善的生命周期管理。本文将详细讲解生产级 Go 服务的目录设计(包含 model 等核心目录)、…

【C++】2. 类和对象(上)

文章目录一、类的定义1、类定义格式2、访问限定符3、类域二、实例化1、实例化概念2、对象⼤⼩三、this指针四、C和C语⾔实现Stack对⽐一、类的定义 1、类定义格式 class为定义类的关键字,Stack为类的名字,{ }中为类的主体,注意类定义结束时…

UnityURP 扭曲屏幕效果实现

UnityURP 扭曲屏幕效果实现前言项目下载URPGrabPass空间扭曲着色器实现添加可视化控制创建材质球并设置补充粒子使用步骤CustomData映射移动设备优化鸣谢前言 在Unity的Universal Render Pipeline (URP) 中,传统的GrabPass功能被移除,借助URPGrabPass工…

(三)软件架构设计

2024年博主考软考高级系统架构师没通过,于是决定集中精力认真学习系统架构的每一个环节,并在2025年软考中取得了不错的成绩,虽然做信息安全的考架构师很难,但找对方法,问题就不大! 本文主要是博主在学习过程…

切记使用mt19937构造随机数

在做 Kazaee CodeForces - 1746F 这个问题的时候,最初的时候使用了ran(),然后一直WA,遂改成mt19937,顺利通过本道题。 mt19937 Rand(time(0)); 调用随机数时候,使用: Rand() & 1 注意看&#xff0…

基于N32G45x+RTT驱动框架的定时器外部计数

时钟选择 高级控制定时器的内部时钟:CK_INT: 两种外部时钟模式: 外部输入引脚 外部触发输入 ETR 内部触发输入(ITRx):一个定时器用作另一个定时器的预分频器 外部时钟原理 通过配置 TIMx_SMCTRL.SMSEL=111 选择该模式。 计数器可以配置为在所选输入的时钟上升沿或下降沿 …

[特殊字符] Ubuntu 下 MySQL 离线部署教学(含手动步骤与一键脚本)

适用于 Ubuntu 20.04 / 22.04 无网络环境部署 MySQL。 建议初学者先按手动方式部署一遍理解原理,再使用自动化脚本完成批量部署。📁 一、准备工作 ✅ 1. 虚拟机环境 系统:Ubuntu 22.04(或兼容版本)环境:无网…