React TypeScript useReducer报错:无匹配重载,返回state正常却报错?
问题场景
编写useReducer时遇到TypeScript类型报错:仅直接返回state时正常,执行ADD/REMOVE/UPDATE操作就触发错误。报错信息如下:
No overload matches this call.
Overload 1 of 5, '(reducer: ReducerWithoutAction, initializerArg: any, initializer?: undefined): [any, DispatchWithoutAction]', gave the following error.
Argument of type '(state: IMeeting[], action: ReducerAction) => (number | IMeeting)[]' is not assignable to parameter of type 'ReducerWithoutAction'.
Overload 2 of 5, '(reducer: (state: IMeeting[], action: ReducerAction) => (number | IMeeting)[], initialState: never, initializer?: undefined): [never, Dispatch]', gave the following error.
Argument of type 'IMeeting[]' is not assignable to parameter of type 'never'.ts(2769)
相关代码:
const initListMeeting: IMeeting[] = meetingData; type ReducerAction = { type: REDUCER_ACTIONS; payload: IMeeting | number; }; type MeetingsContext = { children: ReactElement | ReactElement[]; }; export type ListMeetingContextType = { listMeeting: IMeeting[]; dispatch: Dispatch<ReducerAction>; }; const reducer = (state: IMeeting[], action: ReducerAction) => { switch (action.type) { case REDUCER_ACTIONS.ADD_MEETING: return [...state, action.payload]; case REDUCER_ACTIONS.REMOVE_MEETING: return [...state].filter((meeting) => meeting.id !== action.payload); case REDUCER_ACTIONS.UPDATE_MEETING: return state.map((meeting) => meeting.id === (action.payload as IMeeting).id ? (action.payload) : meeting, ); default: return state; } }; export const ListMeetingContext = createContext<ListMeetingContextType>({ listMeeting: meetingData, dispatch: () => null, }); export const StateProvider = ({ children }: MeetingsContext): JSX.Element => { const [listMeeting, dispatch] = useReducer(reducer, initListMeeting); return ( <ListMeetingContext.Provider value={{ listMeeting, dispatch }}> {children} </ListMeetingContext.Provider> ); }
错误原因
ReducerAction类型设计不精准
当前ReducerAction的payload定义为IMeeting | number,TypeScript无法根据action.type区分不同操作对应的payload类型。比如执行ADD_MEETING时,它无法确定action.payload一定是IMeeting,会默认认为可能是number,导致返回的数组类型变成(number | IMeeting)[],和初始状态的IMeeting[]类型不匹配。useReducer类型推断冲突
useReducer要求reducer的返回值类型必须和初始state的类型完全一致。现在reducer返回的是联合类型数组,而初始state是纯IMeeting[],TypeScript找不到匹配的重载签名,因此抛出2769错误。冗余断言与逻辑隐患
UPDATE_MEETING中使用(action.payload as IMeeting)强制类型断言,只是临时绕过类型检查,并没有解决TypeScript对payload类型的不确定性;同时REMOVE_MEETING中用meeting.id(数字类型)和可能为对象的action.payload比较,存在逻辑错误,进一步加剧了类型推断的混乱。
修复方案
1. 拆分ReducerAction为联合类型
为不同操作定义专属的action类型,让TypeScript能自动推导每个case中payload的类型:
type ReducerAction = | { type: REDUCER_ACTIONS.ADD_MEETING; payload: IMeeting } | { type: REDUCER_ACTIONS.REMOVE_MEETING; payload: number } | { type: REDUCER_ACTIONS.UPDATE_MEETING; payload: IMeeting };
2. 简化reducer逻辑,移除冗余断言
修改后的reducer无需类型断言,TypeScript会自动识别每个case的payload类型:
const reducer = (state: IMeeting[], action: ReducerAction) => { switch (action.type) { case REDUCER_ACTIONS.ADD_MEETING: return [...state, action.payload]; case REDUCER_ACTIONS.REMOVE_MEETING: return state.filter((meeting) => meeting.id !== action.payload); case REDUCER_ACTIONS.UPDATE_MEETING: return state.map((meeting) => meeting.id === action.payload.id ? action.payload : meeting ); default: return state; } };
3. 确保类型上下文一致
此时useReducer能正确推断出listMeeting为IMeeting[],dispatch的类型也会匹配Dispatch<ReducerAction>,和ListMeetingContextType的定义完全对齐,报错会自动消失。
内容的提问来源于stack exchange,提问作者Anh Tuấn Ngô Hoàng

