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.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(或兼容版本)環境:無網…