如何为React函数式组件传入泛型,使Props扩展接口并支持动态表单
React Hook Form 动态输入组件泛型改造问题
场景说明
我正在使用react-hook-form的FormProvider创建动态输入表单,顶层代码如下:
const formMethods = useForm<TSongFormData>(); return ( <FormProvider {...formMethods}> <SongInputForm /> </FormProvider> )
在SongInputForm组件中实现了实际表单结构,包含自定义的InputElement输入组件:
const SongForm = () => { const { handleSubmit } = useFormContext<TSongFormData>(); return ( <form onSubmit={handleSubmit((data) => console.log("data", data))} > <InputElement label="Song Name" name="name" /> <InputElement label="Author" name="author" /> <input type="submit" /> </form> ); }; }
InputElement组件接收原生input的所有props,其中name属性被约束为表单数据键的联合类型TSongFormDataKey,当前硬编码类型的实现如下:
interface ITextInputProps extends React.InputHTMLAttributes<HTMLInputElement> { label?: string; name: TSongFormDataKey; } const InputElement = ({ name, label, ...restProps }: ITextInputProps) => { const { register } = useFormContext<TSongFormData>(); return ( <div> <label htmlFor={name}>{label}</label> <input id={name} {...restProps} {...register(name)} /> </div> ); };
需求与问题
当前InputElement组件硬编码了TSongFormData和TSongFormDataKey类型,无法复用在其他结构的表单中。我希望将这两个类型通过泛型传入组件,实现组件动态化,提升Props类型的灵活性。期望的实现形式大致如下:
interface ITextInputProps<T> extends React.InputHTMLAttributes<HTMLInputElement> { label?: string; name: T; } const InputElement = <T,K>({ name, label, ...restProps }: ITextInputProps<T>) => { const { register } = useFormContext<K>(); return ( <div> <label htmlFor={name}>{label}</label> <input id={name} {...restProps} {...register(name)} /> </div> ); };
其中T对应TSongFormDataKey(表单键联合类型),K对应TSongFormData(表单数据类型)。
解决方案
我们可以只声明一个表单数据类型的泛型,通过keyof操作符自动推导键的类型,既保证类型关联,又简化泛型参数:
import React from 'react'; import { useFormContext, RegisterOptions } from 'react-hook-form'; // 定义组件Props,泛型TFormData为表单数据的类型 interface ITextInputProps<TFormData> extends React.InputHTMLAttributes<HTMLInputElement> { label?: string; name: keyof TFormData; // 可选:支持传入react-hook-form的校验规则 registerOptions?: RegisterOptions<TFormData>; } // 组件泛型声明,约束TFormData为对象类型 const InputElement = <TFormData extends Record<string, unknown>>({ name, label, registerOptions, ...restProps }: ITextInputProps<TFormData>) => { const { register } = useFormContext<TFormData>(); return ( <div> {label && <label htmlFor={String(name)}>{label}</label>} <input id={String(name)} {...restProps} {...register(name, registerOptions)} /> </div> ); };
使用方式
在表单组件中使用时,TypeScript会自动通过useFormContext推断出泛型类型,无需手动传入:
const SongForm = () => { const { handleSubmit } = useFormContext<TSongFormData>(); return ( <form onSubmit={handleSubmit((data) => console.log("data", data))}> <InputElement label="Song Name" name="name" /> <InputElement label="Author" name="author" /> <input type="submit" /> </form> ); };
关键说明
TFormData extends Record<string, unknown>确保泛型参数是对象类型,符合表单数据的结构要求name: keyof TFormData保证name只能是表单数据类型的合法键,避免类型错误- 新增
registerOptions参数,支持传入react-hook-form的校验规则,增强组件实用性 - 使用
String(name)将键转换为字符串,兼容非字符串类型的键(表单键通常为字符串,此处理提升兼容性)
改造后的InputElement可以复用在任何由FormProvider包裹的表单中,完全适配不同结构的表单数据类型。
内容的提问来源于stack exchange,提问作者Jerome Marshall
相关产品推荐
相关产品推荐

