TypeScript无法识别Redux reducer 鼠标悬停时类型显示为any
问题根因
你遇到的TypeScript类型识别失败本质是模块内循环引用导致类型推导中断:
- 文件顶部定义
commonAsyncThunkCreator时,内部硬编码引用了文件末尾才从appReducer.actions解构导出的unauthorizedaction - TypeScript按从上到下的顺序推导文件类型时,解析到thunk内部的
dispatch(unauthorized())语句时找不到对应标识符的类型,后续推导appReducer类型时就会出现类型丢失,最终组件中访问store状态时拿不到正确的state类型 - 这也完全匹配你测试的现象:去掉
unauthorized的导出后,thunk里的悬空引用消失,TypeScript就能正常推导appReducer的类型。
除此之外你当前的实现还有几个明显问题:
- 通用thunk硬编码依赖app模块下的
unauthorizedaction,根本无法在其他slice中复用,其他模块调用这个thunk时还是会触发app模块的登出逻辑,耦合度极高 requestHelper变量没有类型标注也没有兜底逻辑,遇到GET/POST之外的请求方法时值为undefined,调用会直接抛错createAsyncThunk创建的sync没有在slice的extraReducers中注册处理逻辑,thunk触发的pending/fulfilled/rejected状态根本不会同步更新到store里
正确实现方案
第一步:解耦通用thunk和业务slice的逻辑
不要在通用thunk工厂里硬编码依赖某个业务slice的action,把状态码判断结果作为thunk返回值透出,从设计上解决循环依赖问题,同时保证thunk可以全项目复用:
import { createAsyncThunk, createSlice, isRejectedWithValue } from "@reduxjs/toolkit"; import type { PayloadAction } from "@reduxjs/toolkit"; // 保留原有请求方法、请求枚举的导入 // import { makeGetRequest, makePostRequest, REQUEST_METHOD } from '@/utils/request' type ThunkOptions = { method: number, api_url: string, body: any | null } /** * 通用Thunk Action Creator工厂 * 无业务依赖,全项目所有slice可直接复用 * @param name thunk的action type前缀 * @returns createAsyncThunk生成的异步thunk实例 */ export const commonAsyncThunkCreator = (name: string) => { return createAsyncThunk(name, async (options: ThunkOptions, { rejectWithValue }) => { let requestHelper: ((url: string, body?: any) => Promise<any>) | null = null; if(options.method === REQUEST_METHOD.GET.id){ requestHelper = makeGetRequest; }else if(options.method === REQUEST_METHOD.POST.id){ requestHelper = makePostRequest; } // 兜底处理不支持的请求方法 if (!requestHelper) { return rejectWithValue({ error: "不支持的请求方法" }) } const response = await requestHelper(options.api_url, options.body); // 不直接在通用thunk里dispatch业务action,把401状态作为rejected结果透出 if (response.status === 401) { return rejectWithValue({ isUnauthorized: true, response }) } if(response && response.result){ return response; } return rejectWithValue(response); }); }
第二步:按正确顺序定义slice、导出action、注册异步thunk处理逻辑
严格遵循「先定义slice → 导出同步action → 创建异步thunk → 在extraReducers中注册thunk状态处理」的顺序,从根源上避免类型推导的时序问题:
interface AppReducerInitialStateType { isFetching: boolean, isLoggedIn: boolean, isSyncSuccess: boolean } const initialState : AppReducerInitialStateType = { isFetching:false, isLoggedIn:true, isSyncSuccess: false } // 先定义slice export const appReducer = createSlice({ name:"app", initialState, reducers:{ unauthorized:(state)=>{ state.isLoggedIn = false; state.isSyncSuccess = false; }, // 其他同步reducer保持原有逻辑不变 }, // 注册异步thunk的状态更新逻辑 extraReducers: (builder) => { builder .addCase(sync.pending, (state) => { state.isFetching = true; }) .addCase(sync.fulfilled, (state) => { state.isFetching = false; state.isSyncSuccess = true; }) .addCase(sync.rejected, (state, action) => { state.isFetching = false; // 直接在slice处理层处理401逻辑,不需要跨模块dispatch action if (action.payload?.isUnauthorized) { state.isLoggedIn = false; state.isSyncSuccess = false; } }) } }); // 再导出同步actions export const { unauthorized } = appReducer.actions; // 最后创建异步thunk实例,不会存在引用时序问题 export const sync = commonAsyncThunkCreator('app/sync'); export default appReducer.reducer;
第三步(可选,优化复用体验):添加全局中间件统一处理401
如果希望所有通过通用thunk发送的请求遇到401时自动触发登出,不需要每个slice重复写判断逻辑,可以添加一个全局RTK中间件:
// 中间件文件 import { unauthorized } from "@/features/app/appSlice"; export const authMiddleware = (api) => (next) => (action) => { if (isRejectedWithValue(action) && action.payload?.isUnauthorized) { api.dispatch(unauthorized()); } return next(action); }
在configureStore配置时把这个中间件加入middleware数组即可全局生效。
类型配置补充
按照Redux Toolkit TypeScript官方规范,提前封装带类型的store钩子,才能在组件中拿到正确的state类型推导:
// store.ts import { configureStore } from "@reduxjs/toolkit"; import { useDispatch, useSelector } from "react-redux"; import appReducer from "./features/app/appSlice"; // import authMiddleware from "./middleware/auth"; export const store = configureStore({ reducer: { app: appReducer, // 其他业务reducer }, // middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(authMiddleware) }); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch; // 封装后组件中直接导入使用即可自动推导类型 export const useAppSelector = useSelector.withTypes<RootState>(); export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
内容的提问来源于stack exchange,提问作者mk1024
相关产品推荐
相关产品推荐

