使用Redux Toolkit+createAsyncThunk遇类型错误及调度问题求助
React-Redux-Toolkit结合createAsyncThunk开发的TypeScript错误排查与解决
问题代码
categorySlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import useGetData from "hooks/useGetDate"; const initialState = { category: [], loading: false, error: null, }; export const getAllCategory = createAsyncThunk( "category/getAll", async (url, thunkAPI) => { const { rejectWithValue } = thunkAPI; try { const res = await useGetData(url); return res.data; } catch (err) { return rejectWithValue(err.response.data); } } ); export const categorySlice = createSlice({ name: "category", initialState, reducers: {}, extraReducers: (builder) => { builder .addCase(getAllCategory.pending, (state) => { state.loading = true; state.error = null; }) .addCase(getAllCategory.fulfilled, (state, action) => { state.loading = false; state.error = null; state.category = action.payload; // Assuming action.payload is an array of categories }) .addCase(getAllCategory.rejected, (state, action) => { state.loading = false; state.error = action?.error?.message; }); }, }); export default categorySlice.reducer;
AllCategoryPage.js
import { useEffect } from "react"; import CategoryContainer from "../../Components/Category/CategoryContainer"; import Pagination from "../../Components/Uitily/Pagination"; import { useSelector, useDispatch } from "react-redux"; import { getAllCategory } from "redux/categorySlice"; const AllCategoryPage = () => { const dispatch = useDispatch(); const data = useSelector((state) => state.categoryData.category); const isLoading = useSelector((state) => state.categoryData.loading); const error = useSelector((state) => state.categoryData.error); useEffect(() => { dispatch(getAllCategory("/api/v1/categories")); }, [dispatch]); return ( <div style={{ minHeight: "670px" }}> <CategoryContainer /> <Pagination /> </div> ); }; export default AllCategoryPage;
错误信息
错误1(Code: 2554)
Expected 0 arguments, but got 1.
错误2(Code: 2345)
Argument of type 'AsyncThunkAction<any, void, AsyncThunkConfig>' is not assignable to parameter of type 'AnyAction'.
错误3(Code: 2339)
Property 'categoryData' does not exist on type 'unknown'.
问题原因解析
错误1(2554):
- 在
createAsyncThunk的回调函数中调用了React自定义HookuseGetData,违反了React Hook的调用规则(Hook只能在组件或其他自定义Hook的顶层调用)。 - 非法调用导致TypeScript无法正确推断
getAllCategory的参数类型,误以为它不需要接收参数,传参时触发类型错误。
- 在
错误2(2345):
- 未给Redux配置正确的TypeScript类型,
useDispatch默认返回Dispatch<AnyAction>类型,无法识别createAsyncThunk生成的AsyncThunkAction,导致类型不匹配。
- 未给Redux配置正确的TypeScript类型,
错误3(2339):
- 组件中访问
state.categoryData,但Redux store中并未注册名为categoryData的reducer,slice名称为category,若注册时用category作为键名,应访问state.category。 - 未定义
RootState类型,useSelector无法推断state结构,默认视为unknown类型,因此报错属性不存在。
- 组件中访问
可行解决方案
1. 修复Hook非法调用问题
将useGetData替换为普通API请求函数(不能是Hook),若useGetData封装了axios/fetch逻辑,抽成普通函数:
修改categorySlice.js:
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; // 导入普通API函数而非Hook import { getData } from "services/api"; const initialState = { category: [], loading: false, error: null, }; export const getAllCategory = createAsyncThunk( "category/getAll", async (url, thunkAPI) => { const { rejectWithValue } = thunkAPI; try { const res = await getData(url); // 调用普通函数 return res.data; } catch (err) { return rejectWithValue(err.response?.data || "请求失败"); } } ); // 其余slice代码保持不变
2. 配置Redux TypeScript类型
在Redux store配置文件(如store.ts)中定义RootState和AppDispatch类型,导出自定义的useDispatch和useSelector:
store.ts:
import { configureStore } from "@reduxjs/toolkit"; import categoryReducer from "./categorySlice"; export const store = configureStore({ reducer: { category: categoryReducer, // 键名与slice对应,组件中需访问state.category // 其他reducer... }, }); // 定义全局状态类型 export type RootState = ReturnType<typeof store.getState>; // 定义支持异步action的dispatch类型 export type AppDispatch = typeof store.dispatch; // 导出自定义hooks,简化类型使用 import { useDispatch, useSelector } from "react-redux"; import type { TypedUseSelectorHook } from "react-redux"; export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
3. 修正组件中的state访问路径及dispatch调用
修改AllCategoryPage.js,使用自定义hooks并修正state访问路径:
import { useEffect } from "react"; import CategoryContainer from "../../Components/Category/CategoryContainer"; import Pagination from "../../Components/Uitily/Pagination"; // 导入自定义hooks而非redux原生hooks import { useAppDispatch, useAppSelector } from "redux/store"; import { getAllCategory } from "redux/categorySlice"; const AllCategoryPage = () => { const dispatch = useAppDispatch(); // 访问state.category,与store中reducer键名一致 const data = useAppSelector((state) => state.category.category); const isLoading = useAppSelector((state) => state.category.loading); const error = useAppSelector((state) => state.category.error); useEffect(() => { dispatch(getAllCategory("/api/v1/categories")); }, [dispatch]); return ( <div style={{ minHeight: "670px" }}> <CategoryContainer /> <Pagination /> </div> ); }; export default AllCategoryPage;
额外注意事项
- 若项目启用TypeScript检查,建议将组件改为
.tsx后缀,slice/store改为.ts后缀,获得更完整的类型支持。 - 可在
createAsyncThunk中显式指定泛型参数,明确返回值和参数类型,例如:
其中export const getAllCategory = createAsyncThunk<Category[], string>( "category/getAll", async (url, thunkAPI) => { // ... } );Category为自定义的分类数据类型。
内容的提问来源于stack exchange,提问作者eslam wael
相关产品推荐
相关产品推荐

