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

如何为React/TypeScript组件的Props创建泛型类型?

问题

我有一个通用性较强的React组件,定义如下:

export type CheckboxItem = {
  label: string,
  code: string,
};

export type CheckboxesProps = {
  items: CheckboxItem[],
  handleStateChange: (selected: (CheckboxItem['code'])[]) => void,

};

export interface CheckboxState<T>  {
  [key: CheckboxItem['code']]: boolean,
}


export default function Checkboxes({items, handleStateChange}: CheckboxesProps) {
    const [state, setState] = useState<CheckboxState>(items.reduce((stateObj: CheckboxState, item: CheckboxItem) => {
      stateObj[item.code] = false;
      return stateObj;
    }, {}));

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
      setState({
        ...state,
        [event.target.name]: event.target.checked,
      });
      handleStateChange(Object.keys(state).filter(key => state[key]));
  };
...
}

该组件能正常工作,但传入基于常量对象PrimaryNeeds生成的items时,出现类型不匹配错误:

// 在另一个组件中
export const PrimaryNeeds = {
  foodAllergies: 'foodAllergies',
  foodRestrictions: 'foodRestrictions',
  medicineAllergies: 'medicineAllergies',
  medicalConditions: 'medicalConditions',
  phobias: 'phobias',
  other: 'other',
} as const;

export type PrimaryNeedsCheckboxesProps = {
  langCode: keyof typeof LanguageCodes,
  handlePrimaryNeedsChanged: (selected: (keyof typeof PrimaryNeeds)[]) => void,
}

export default function PrimaryNeedsCheckboxes({langCode, handlePrimaryNeedsChanged}: PrimaryNeedsCheckboxesProps) {

   const checkboxItems = (Object.keys(PrimaryNeeds) as (keyof typeof PrimaryNeeds)[]).map((needKey: keyof typeof PrimaryNeeds) => {
        return {
          label: primaryNeeds[needKey],
          code: needKey,
        };
      });
    // ...
   return (
     <Checkboxes items={checkboxItems} handleStateChange={handlePrimaryNeedsChanged} />
   )

错误信息:

Type '(selected: ("foodAllergies" | "foodRestrictions" | "medicineAllergies" | "medicalConditions" | "phobias" | "other")[]) => void' is not assignable to type '(selected: string[]) => void'.
  Types of parameters 'selected' and 'selected' are incompatible.
    Type 'string[]' is not assignable to type '("foodAllergies" | "foodRestrictions" | "medicineAllergies" | "medicalConditions" | "phobias" | "other")[]'.
      Type 'string' is not assignable to type '("foodAllergies" | "foodRestrictions" | "medicineAllergies" | "medicalConditions" | "phobias" | "other")'.

尝试为组件Props添加泛型适配不同类型的item,但遇到TypeScript不允许泛型作为索引签名的问题:

export type CheckboxItem<T> = {
  label: keyof T,
  code: T[keyof T],
};
export type CheckboxesProps<T>= {
  items: CheckboxItem<T>[],
  handleStateChange: (selected: (CheckboxItem<T>['code'])[]) => void,
};
// 错误指向下方的"key"
export interface CheckboxState<T>  {
  [key: CheckboxItem<T>['code']]: boolean,
}

错误:

An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.

尝试使用映射类型时也出错:

export interface CheckboxState<T>  {
  [K in T]?: boolean,
}

错误:

A mapped type may not declare properties or methods.

请问如何为该组件的Props创建泛型类型?或者是否有其他更合适的解决方案?


解决方案

1. 正确定义泛型组件类型

核心是用泛型约束限定code的类型,同时用映射类型定义CheckboxState,规避索引签名的限制:

// 泛型CheckboxItem,CodeType代表code的字面量类型
export type CheckboxItem<CodeType extends string> = {
  label: string,
  code: CodeType,
};

// 泛型Props,与CodeType绑定
export type CheckboxesProps<CodeType extends string> = {
  items: CheckboxItem<CodeType>[],
  handleStateChange: (selected: CodeType[]) => void,
};

// 用映射类型定义CheckboxState,基于CodeType的字面量集合生成精确类型
export type CheckboxState<CodeType extends string> = {
  [K in CodeType]: boolean;
};

2. 修改组件实现适配泛型

调整Checkboxes为泛型组件,同时修正handleChange中Object.keys的类型问题(Object.keys默认返回string[],需断言为CodeType[]):

import { useState, ChangeEvent } from 'react';

export default function Checkboxes<CodeType extends string>({items, handleStateChange}: CheckboxesProps<CodeType>) {
    // 初始化state,断言类型确保正确性
    const initialState = items.reduce((stateObj, item) => {
      stateObj[item.code] = false;
      return stateObj;
    }, {} as CheckboxState<CodeType>);
    
    const [state, setState] = useState<CheckboxState<CodeType>>(initialState);

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
      const newState = {
        ...state,
        [event.target.name as CodeType]: event.target.checked,
      };
      setState(newState);
      // 过滤选中项并转换为精确类型
      const selected = Object.keys(newState)
        .filter(key => newState[key as CodeType]) as CodeType[];
      handleStateChange(selected);
  };

  // 示例渲染逻辑
  return (
    <div>
      {items.map(item => (
        <label key={item.code}>
          <input
            type="checkbox"
            name={item.code}
            checked={state[item.code]}
            onChange={handleChange}
          />
          {item.label}
        </label>
      ))}
    </div>
  );
}

3. 使用泛型组件

在PrimaryNeedsCheckboxes中,TypeScript会自动推导CodeType为PrimaryNeeds的键类型,无需手动指定:

export const PrimaryNeeds = {
  foodAllergies: 'foodAllergies',
  foodRestrictions: 'foodRestrictions',
  medicineAllergies: 'medicineAllergies',
  medicalConditions: 'medicalConditions',
  phobias: 'phobias',
  other: 'other',
} as const;

export type PrimaryNeedsCheckboxesProps = {
  langCode: keyof typeof LanguageCodes,
  handlePrimaryNeedsChanged: (selected: (keyof typeof PrimaryNeeds)[]) => void,
}

export default function PrimaryNeedsCheckboxes({langCode, handlePrimaryNeedsChanged}: PrimaryNeedsCheckboxesProps) {
   const checkboxItems = (Object.keys(PrimaryNeeds) as (keyof typeof PrimaryNeeds)[]).map((needKey) => {
        return {
          label: primaryNeeds[needKey],
          code: needKey,
        };
      });

   return (
     <Checkboxes items={checkboxItems} handleStateChange={handlePrimaryNeedsChanged} />
   )
}

关键说明

  • 映射类型{ [K in CodeType]: boolean }可以基于泛型字面量生成精确的键值对类型,绕过了索引签名的限制;
  • Object.keys返回string[]是TypeScript的设计限制,只要state的初始化和修改逻辑正确,类型断言是安全的;
  • 泛型CodeType extends string确保传入的code是字符串字面量类型,保留了类型的精确性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 19:24:51