如何为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
相关产品推荐
相关产品推荐

