当前位置: 首页 > news >正文

createAsyncThunk

下面,我们来系统的梳理关于 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.dtcms.com/a/315190.html

相关文章:

  • 结构体数组2-单向链表
  • MySQL详解(一)
  • SAP_MMBASIS模块-选择屏幕变式添加动态字段赋值
  • 如何在AD中快速定位器件?J+C
  • AWS服务分类
  • 人员检测识别中漏检率↓76%:陌讯动态特征融合算法实战解析
  • C++入门自学Day6-- STL简介(初识)
  • AI产品经理手册(Ch6-8)AI Product Manager‘s Handbook学习笔记
  • Vue3+TypeScript项目实战day1——项目的创建及环境配置
  • pytorch 学习笔记(2)-实现一个线性回归模型
  • sqli-labs通关笔记-第30关GET字符注入(WAF绕过 双引号闭合 手工注入+脚本注入两种方法)
  • QCustomplot极坐标系绘制
  • Qt项目模板全解析:选择最适合你的开发起点
  • Gitee:本土化DevOps平台如何助力中国企业实现高效研发协作
  • 水面垃圾清扫船cad【6张】三维图+设计说明书
  • C语言实现Elasticsearch增删改查API
  • OpenCV学习 day4
  • Pytorch-05 所以计算图和自动微分到底是什么?(计算图及自动微分引擎原理讲解)
  • AI 大模型分类全解析:从文本到多模态的技术图谱
  • AcWing 890. 能被整除的数 (容斥原理)
  • Web Scraper实战:轻松构建电影数据库
  • 直角坐标系里的四象限对NLP中的深层语义分析的积极影响和启示
  • 【Algorithm | 0x03 搜索与图论】DFS
  • AtCoder Beginner Contest 416 C 题
  • 【软件与环境】--腾讯云服务器的使用和部署
  • 【软件与环境:虚拟机】--VMware Workstation 16 pro安装+Cenos7
  • 8位以及32位的MCU如何进行选择?
  • 机器学习实战:逻辑回归深度解析与欺诈检测评估指标详解(二)
  • JVM相关知识
  • Servlet 相关笔记整理