TypeScript结合Redux、typesafe-actions写reducer时如何修复concat报错
TypeScript + React + Redux + typesafe-actions 类型报错修复
问题场景
项目开发中使用上述技术栈时,遇到如下TypeScript编译报错:
No overload matches this call. Overload 1 of 2, '(...items: ConcatArray[]): List[]', gave the following error. Argument of type '{ id: number; name: string | undefined; email: string | undefined; gender: string | undefined; }' is not assignable to parameter of type 'ConcatArray'. Object literal may only specify known properties, and 'id' does not exist in type 'ConcatArray'.ts(2769)
报错定位到代码行:id: Math.max(...state.map((todo) => todo.id)) + 1,
曾尝试修改id字段的类型修复报错,始终未解决问题。
现有业务代码片段
1. 类型定义文件(Types.d.ts)
export interface PersonType { id?: number; name?: string; email?: string; gender?: string; }
2. Reducer实现文件(PeopleReducers.ts)
import { ActionType, createAction, createReducer } from "typesafe-actions"; import { PersonType } from "../../types"; export const ADD_LIST = "people/ADD_LIST"; export const DELETE_LIST = "people/DELETE_LIST"; export const addList = createAction( ADD_LIST, ({ id, name, email, gender }: PersonType) => ({ id, name, email, gender, }) )(); export const deleteList = createAction(DELETE_LIST, ({ id }) => ({ id, }))(); const actions = { addList, deleteList }; type ListAction = ActionType<typeof actions>; export type List = { id: number; name: string; email: string; gender: string; }; type ListState = List[]; const initialState: ListState = []; const list = createReducer<ListState, ListAction>(initialState, { [ADD_LIST]: (state, { payload: { name, email, gender } }) => state.concat({ id: Math.max(...state.map((todo) => todo.id)) + 1, // 报错定位行 name, email, gender, }), [DELETE_LIST]: (state, { payload: { id } }) => state.filter((item) => item.id !== id), }); export default list;
3. 列表行组件(Row.tsx)
import { PersonType } from "../types"; export default function Row({ name, email, gender }: PersonType) { return ( <> <p>{name}</p> <p>{email}</p> <p>{gender}</p> </> ); }
报错根因
本次报错和id字段类型设置无关,核心是三个层面的类型不匹配:
concat方法的重载识别偏差:Array.prototype.concat存在两个重载分支,TypeScript检测到传入的是普通对象而非数组时,会自动将参数匹配到“需要接收ConcatArray数组类型”的分支,因此抛出“参数不能赋值给ConcatArray”的错误。- 可选字段的兼容问题:
PersonType中name/email/gender均为可选字段(带?标记),实际推导的类型是string | undefined,和List类型中要求的必填string字段不兼容,传入的对象结构和数组期望的元素类型不匹配。 - 边界场景的类型风险:当state为空数组时,
Math.max(...空数组)的返回值为-Infinity,计算出的id不是合法number类型,会进一步触发类型异常。
修复方案
按以下步骤调整即可彻底解决报错:
- 拆分类型定义,明确不同场景的字段约束
将原来混写的PersonType拆分为两个类型:用户提交的入参不需要带id,系统生成的完整数据必须带id,避免把系统生成字段暴露给用户入参。
调整后的类型定义:// 用户提交的新增人员入参(无需传入系统生成的id) export type AddPersonPayload = { name: string; email: string; gender: string; }; // 系统存储的完整人员类型(带自动生成的id) export type PersonType = AddPersonPayload & { id: number; }; - 修正action的payload类型
原来的addList动作使用带id的PersonType作为入参,替换为上述定义的AddPersonPayload作为入参类型,保证拆出的name/email/gender都是确定的string类型,不会出现undefined。 - 替换
concat为展开运算符,规避重载识别问题
用[...state, 新对象]的写法代替state.concat(新对象),从语法层面避免TypeScript对concat方法的错误重载匹配。 - 增加空数组场景的id计算兜底
当state长度为0时,给id计算设置初始值0,避免出现-Infinity的非法id值。
调整后的id计算逻辑:const maxId = state.length > 0 ? Math.max(...state.map(todo => todo.id)) : 0; const nextId = maxId + 1;
调整后的完整Reducer代码可参考:
import { ActionType, createAction, createReducer } from "typesafe-actions"; import { AddPersonPayload } from "../../types"; export const ADD_LIST = "people/ADD_LIST"; export const DELETE_LIST = "people/DELETE_LIST"; // 修正action的payload类型 export const addList = createAction( ADD_LIST, (payload: AddPersonPayload) => payload )(); export const deleteList = createAction(DELETE_LIST, (id: number) => ({ id }))(); const actions = { addList, deleteList }; type ListAction = ActionType<typeof actions>; export type List = { id: number; name: string; email: string; gender: string; }; type ListState = List[]; const initialState: ListState = []; const list = createReducer<ListState, ListAction>(initialState, { [ADD_LIST]: (state, { payload }) => { // 空数组场景兜底 const maxId = state.length > 0 ? Math.max(...state.map(todo => todo.id)) : 0; return [ ...state, { id: maxId + 1, ...payload, } ] }, [DELETE_LIST]: (state, { payload: { id } }) => state.filter((item) => item.id !== id), }); export default list;
内容的提问来源于stack exchange,提问作者suyeon
相关产品推荐
相关产品推荐

