Redux Toolkit Async Thunk提取重复try-catch代码的TypeScript问题解决
提取Redux Toolkit Async Thunk通用try-catch逻辑的TypeScript问题解决
问题背景
我在Redux Toolkit的features目录下的slice文件中定义了多个Async Thunk函数,每个函数都包含重复的try-catch错误处理逻辑,希望将这部分逻辑提取为独立函数以消除代码冗余。当前代码可正常运行,getPostComments、createComment、deleteComment均为axios请求函数。
现有重复代码
export const fetchComments = createAsyncThunk('users/fetchComments', async (id: number, thunkAPI) => { try { return await getPostComments(id); } catch (err) { if (err instanceof Error) { return thunkAPI.rejectWithValue(err.message); } return thunkAPI.rejectWithValue('something went wrong'); } }); export const addComment = createAsyncThunk( 'users/addComment', async (data: CommentData, thunkAPI) => { try { return await createComment(data); } catch (err) { if (err instanceof Error) { return thunkAPI.rejectWithValue(err.message); } return thunkAPI.rejectWithValue('something went wrong'); } }, ); export const removeComment = createAsyncThunk( 'users/removeComment', async (id: number, thunkAPI) => { try { await deleteComment(id); return id; } catch (err) { if (err instanceof Error) { return thunkAPI.rejectWithValue(err.message); } return thunkAPI.rejectWithValue('something went wrong'); } }, );
尝试的通用函数及遇到的错误
我尝试编写通用try-catch函数时遇到了TypeScript错误,比如TS2304: Cannot find name 'GetThunkAPI',同时还出现了以下问题:
- 无法识别
GetThunkAPI类型 - Async Thunk的回调函数返回类型不匹配(未返回通用函数的执行结果)
尝试的通用函数代码:
const tryCatchMiddleware = async ( callback: (arg: number) => Promise<Comment[]>, thunkAPI: GetThunkAPI<{}>, arg: number, ) => { try { // eslint-disable-next-line @typescript-eslint/return-await return await callback(arg); } catch (err) { if (err instanceof Error) { return thunkAPI.rejectWithValue(err.message); } return thunkAPI.rejectWithValue('something went wrong'); } }; // eslint-disable-next-line max-len export const fetchComments = createAsyncThunk('users/fetchComments', async (id: number, thunkAPI) => { tryCatchMiddleware(getPostComments, thunkAPI, id); });
项目依赖
"dependencies": { "@reduxjs/toolkit": "^1.8.3", "axios": "^0.27.2", "bulma": "^0.9.4", "classnames": "^2.3.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.2", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1" }, "devDependencies": { "@cypress/react": "^5.12.4", "@cypress/webpack-dev-server": "^1.8.4", "@types/node": "^17.0.45", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", "cypress": "^9.5.3", "eslint": "^7.32.0", "eslint-plugin-cypress": "^2.11.2", "gh-pages": "^3.2.3", "mochawesome": "^7.1.3", "mochawesome-merge": "^4.2.0", "mochawesome-report-generator": "^6.2.0", "node-sass": "^7.0.1", "stylelint": "^13.13.1", "typescript": "^4.6.3" },
解决方案
1. 修复类型错误与通用函数写法
Redux Toolkit中不存在GetThunkAPI全局类型,我们可以通过TypeScript的Parameters工具类型获取Async Thunk回调中thunkAPI的正确类型,同时用泛型让通用函数适配不同的参数和返回值:
import { AsyncThunkPayloadCreator } from '@reduxjs/toolkit'; // 通用try-catch包装函数 const withTryCatch = <Arg, Return, RejectValue = string>( callback: (arg: Arg) => Promise<Return>, thunkAPI: Parameters<AsyncThunkPayloadCreator<Return, Arg, any>>[1], arg: Arg ): Promise<Return | RejectValue> => { try { return callback(arg); } catch (err) { if (err instanceof Error) { return thunkAPI.rejectWithValue(err.message); } return thunkAPI.rejectWithValue('something went wrong'); } };
然后修改各个Async Thunk的实现,必须return通用函数的执行结果以确保返回类型正确:
export const fetchComments = createAsyncThunk('users/fetchComments', async (id: number, thunkAPI) => { return withTryCatch(getPostComments, thunkAPI, id); }); export const addComment = createAsyncThunk('users/addComment', async (data: CommentData, thunkAPI) => { return withTryCatch(createComment, thunkAPI, data); }); export const removeComment = createAsyncThunk('users/removeComment', async (id: number, thunkAPI) => { return withTryCatch(async (id) => { await deleteComment(id); return id; }, thunkAPI, id); });
2. 进一步封装:直接生成带错误处理的Async Thunk
可以把通用逻辑和createAsyncThunk封装在一起,进一步减少重复代码:
import { createAsyncThunk, AsyncThunkPayloadCreator } from '@reduxjs/toolkit'; const createThunkWithTryCatch = <Arg, Return, RejectValue = string>( typePrefix: string, callback: AsyncThunkPayloadCreator<Return, Arg, any> ) => { return createAsyncThunk(typePrefix, async (arg: Arg, thunkAPI) => { try { return await callback(arg, thunkAPI); } catch (err) { if (err instanceof Error) { return thunkAPI.rejectWithValue(err.message); } return thunkAPI.rejectWithValue('something went wrong'); } }); }; // 使用方式 export const fetchComments = createThunkWithTryCatch('users/fetchComments', (id) => getPostComments(id)); export const addComment = createThunkWithTryCatch('users/addComment', (data) => createComment(data)); export const removeComment = createThunkWithTryCatch('users/removeComment', async (id) => { await deleteComment(id); return id; });
最佳实践建议
- 保持类型安全:利用TypeScript泛型让通用函数适配不同的参数、返回值类型,避免使用
any类型。 - 灵活扩展错误处理:如果需要针对不同请求返回自定义错误信息,可以给通用函数添加可选的错误消息参数,或者根据axios错误的响应状态码做差异化处理。
- 避免过度封装:如果项目中只有少量Async Thunk,封装带来的收益可能有限;但如果有大量重复的错误逻辑,封装能显著提升代码可维护性。
- 统一错误格式:可以在通用函数中把错误标准化为固定格式的对象,方便后续在reducer或组件中统一处理。
内容的提问来源于stack exchange,提问作者Paul Gutsal
相关产品推荐
相关产品推荐

