如何为React Exercise组件的exerciseComponentProps添加类型约束?
问题:为Exercise组件的exerciseComponentProps添加类型约束并传递onMarkAsCompleted
我正在开发一个带练习功能的网站,每个练习对应不同的React组件。我用Exercise组件作为统一包裹层,提供标题、完成日期等基础样式,具体练习组件从外部传入:
const exercises = [ <Exercise key={1} numberInLesson={1} title={"Typing the alphabet"} ExerciseComponent={TypingTheAlphabet} exerciseComponentProps={{ ...some props to pass to TypingTheAlphabet }} />, <Exercise key={2} numberInLesson={2} title={"Which letter? - Vowels"} ExerciseComponent={WhichLetter} exerciseComponentProps={{ ...some props to pass to WhichLetter }} /> ]
需要实现两个需求:
- 为Exercise组件的
exerciseComponentProps添加类型约束,确保传入的props仅对应指定ExerciseComponent的有效props - 将Exercise组件的
onMarkAsCompleted属性传递给内部练习组件
以下是当前Exercise组件的Props接口及实现代码,请问如何正确标注exerciseComponentProps的类型?
export interface ExerciseComponentProps { onMarkAsCompleted: () => void; } export type ExerciseComponentType = ComponentType<ExerciseComponentProps>; export interface CommonExerciseProps<C extends ExerciseComponentType> { id: string; numberInLesson: number; title: string; ExerciseComponent: C; exerciseComponentProps: // how can I type this? } const Exercise: React.FC<CommonExerciseProps<any>> = ( props ) => { const [exerciseCompletionDate, setExerciseCompletionDate] = useState<Date | null>(null); const { numberInLesson, title, ExerciseComponent, exerciseComponentProps, } = props; return ( <div className="w3-padding-small" style={exerciseCompletionDate ? { border: "1px solid green" } : {}} > <h3> Exercise {numberInLesson}: <br /> {title} </h3> {exerciseCompletionDate && ( <small> Completed on {exerciseCompletionDate.toISOString().substring(0, 10)} </small> )} <ExerciseComponent onMarkAsCompleted={ !exerciseCompletionDate ? () => setExerciseCompletionDate(new Date()) : () => {} } {...exerciseComponentProps} /> </div> ); }; export default Exercise;
解决方案
1. 为exerciseComponentProps添加精准类型约束
利用TypeScript内置的ComponentProps工具类型提取目标组件的props类型,再通过Omit排除掉由Exercise组件内部提供的onMarkAsCompleted属性,修改CommonExerciseProps接口如下:
import React, { ComponentType, ComponentProps } from 'react'; export interface ExerciseComponentProps { onMarkAsCompleted: () => void; } export type ExerciseComponentType = ComponentType<ExerciseComponentProps>; export interface CommonExerciseProps<C extends ExerciseComponentType> { id: string; numberInLesson: number; title: string; ExerciseComponent: C; // 提取C的props类型,排除内部已处理的onMarkAsCompleted exerciseComponentProps: Omit<ComponentProps<C>, 'onMarkAsCompleted'>; }
2. 调整Exercise组件的泛型定义
将组件从React.FC<CommonExerciseProps<any>>改为泛型组件,避免使用any丢失类型校验:
const Exercise = <C extends ExerciseComponentType>( props: CommonExerciseProps<C> ) => { const [exerciseCompletionDate, setExerciseCompletionDate] = useState<Date | null>(null); const { numberInLesson, title, ExerciseComponent, exerciseComponentProps, } = props; return ( <div className="w3-padding-small" style={exerciseCompletionDate ? { border: "1px solid green" } : {}} > <h3> Exercise {numberInLesson}: <br /> {title} </h3> {exerciseCompletionDate && ( <small> Completed on {exerciseCompletionDate.toISOString().substring(0, 10)} </small> )} <ExerciseComponent onMarkAsCompleted={ !exerciseCompletionDate ? () => setExerciseCompletionDate(new Date()) : () => {} } {...exerciseComponentProps} /> </div> ); }; export default Exercise;
效果说明
- 外部传入
exerciseComponentProps时,TypeScript会自动校验是否匹配对应ExerciseComponent的props要求,多传、少传或类型不匹配都会触发报错 onMarkAsCompleted由Exercise组件内部生成并注入,外部无需传入,类型约束也会禁止外部传递该属性- 所有练习组件都必须符合
ExerciseComponentType类型(即接收onMarkAsCompletedprops),确保回调能正确传递
内容的提问来源于stack exchange,提问作者pg-robban
相关产品推荐
相关产品推荐

