You need to enable JavaScript to run this app. TypeScript结合Redux、typesafe-actions写reducer时如何修复concat报错
优惠活动
大模型
产品
解决方案
定价
更多

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类型,会进一步触发类型异常。

修复方案

按以下步骤调整即可彻底解决报错:

  1. 拆分类型定义,明确不同场景的字段约束
    将原来混写的PersonType拆分为两个类型:用户提交的入参不需要带id,系统生成的完整数据必须带id,避免把系统生成字段暴露给用户入参。
    调整后的类型定义:
    // 用户提交的新增人员入参(无需传入系统生成的id)
    export type AddPersonPayload = {
      name: string;
      email: string;
      gender: string;
    };
    // 系统存储的完整人员类型(带自动生成的id)
    export type PersonType = AddPersonPayload & {
      id: number;
    };
    
  2. 修正action的payload类型
    原来的addList动作使用带id的PersonType作为入参,替换为上述定义的AddPersonPayload作为入参类型,保证拆出的name/email/gender都是确定的string类型,不会出现undefined。
  3. 替换concat为展开运算符,规避重载识别问题
    用[...state, 新对象]的写法代替state.concat(新对象),从语法层面避免TypeScript对concat方法的错误重载匹配。
  4. 增加空数组场景的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.31 05:54:04