You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Reducer泛型改造:Records与可区分联合类型访问报错排查

解决React Reducer拆分后TypeScript类型不匹配问题

问题场景

为了让React Reducer更通用,将动作处理逻辑拆分为独立函数时,定义了可区分联合类型Actions:

export type Actions =
  | {
  type: "ADD_COLUMN";
  newColumnIndex: number;
  column: SelectorColumnData;
}
  | {
  type: "ITEM_CHECKED";
  columnIndex: number;
  item: SelectorItem;
};

export type ActionTypes = Actions["type"];

接着用Record创建动作处理函数映射:

const reducerRecord: Record<ActionTypes, (draft: Draft<SelectorState>, action: Actions) => void> = {
  "ITEM_CHECKED": itemCheckedAction,
  "ADD_COLUMN": addColumnAction,
};

export const selectorReducer: ImmerReducer<SelectorState, Actions> = (
  draft: Draft<SelectorState>,
  action: Actions,
): void => {
  reducerRecord[action.type](draft, action);
};

但编写itemCheckedAction时,TypeScript报错TS2339: property 'columnIndex' does not exist on type 'Actions',尝试用Extract断言或类型守卫都无法解决问题。


解决方案

核心问题是每个动作处理函数应该接收对应具体类型的Action,而非整个联合类型,同时调整映射对象的类型定义以确保类型安全。

1. 给处理函数指定精确的Action类型

直接为每个处理函数的action参数指定对应的子类型,而非Actions联合类型:

// 处理ITEM_CHECKED动作的函数,仅接收对应类型的action
export const itemCheckedAction = (
  draft: Draft<SelectorState>,
  action: Extract<Actions, { type: "ITEM_CHECKED" }>
) => {
  const { columnIndex, item } = action; // 此处无类型错误
  // 业务逻辑处理
};

// 处理ADD_COLUMN动作的函数,仅接收对应类型的action
export const addColumnAction = (
  draft: Draft<SelectorState>,
  action: Extract<Actions, { type: "ADD_COLUMN" }>
) => {
  const { newColumnIndex, column } = action; // 此处无类型错误
  // 业务逻辑处理
};

2. 调整映射对象的类型定义

使用映射类型替代Record,让TypeScript自动关联Action类型与对应的处理函数参数类型:

// 定义映射类型,确保每个Action类型对应正确的处理函数签名
type ReducerHandlers = {
  [T in ActionTypes]: (
    draft: Draft<SelectorState>,
    action: Extract<Actions, { type: T }>
  ) => void;
};

// 初始化映射对象,TypeScript会严格校验每个处理函数的类型
const reducerRecord: ReducerHandlers = {
  "ITEM_CHECKED": itemCheckedAction,
  "ADD_COLUMN": addColumnAction,
};

// 最终Reducer函数
export const selectorReducer: ImmerReducer<SelectorState, Actions> = (
  draft: Draft<SelectorState>,
  action: Actions
): void => {
  // 此处需要类型断言,因为TypeScript暂时无法自动推断action.type与handler的关联
  (reducerRecord[action.type as ActionTypes] as (draft: Draft<SelectorState>, action: Actions) => void)(draft, action);
};

补充:修复类型守卫的写法

如果坚持在处理函数中使用联合类型参数,需去掉变量的类型注解,让TypeScript自动推断守卫后的类型:

export const itemCheckedAction = (draft: Draft<SelectorState>, action: Actions) => {
  if(action.type === "ITEM_CHECKED") {
    const { columnIndex, item } = action; // TypeScript自动推断为ITEM_CHECKED类型
  }
};

内容的提问来源于stack exchange,提问作者serlingpa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 19:45:32