React Hook Form抽象组件时如何保留name属性的类型安全?
如何在React Hook Form抽象组件中保留name属性的类型安全
要解决抽象组件丢失name属性类型安全的问题,核心是让抽象组件与表单的FieldValues类型建立关联,利用React Hook Form提供的Path类型约束字段名。以下是具体修改方案:
1. 导入必要的类型
首先确保导入React Hook Form的Path类型,它用于提取表单字段的合法路径:
import type { Path, FieldValues, ControllerProps, useFormContext } from 'react-hook-form';
2. 修改InputExt组件为泛型组件
将InputExt改为泛型组件,关联表单的FieldValues类型,并使用Path<T>约束name属性:
import { Path, ControllerProps, FieldValues, useFormContext } from 'react-hook-form'; import { FormControl, FormDescription, FormField, FormItem, FormMessage } from "@/components/ui/form"; import { ReactNode } from "react"; // 泛型Props,关联表单的FieldValues类型T type InputExtProps<T extends FieldValues> = { name: Path<T>; // 用Path<T>替代string,确保类型安全 render: ControllerProps['render']; fieldDescription?: ReactNode; hideErrorMsg?: boolean; }; // 定义泛型组件,T继承FieldValues const InputExt = <T extends FieldValues>(props: InputExtProps<T>) => { // 明确指定useFormContext的泛型为T,获取对应类型的control const { control } = useFormContext<T>(); return ( <FormField control={control} name={props.name} render={(data) => ( <FormItem> <FormControl> {props.render(data)} </FormControl> {props.fieldDescription && ( <FormDescription> {props.fieldDescription} </FormDescription> )} {!props.hideErrorMsg && data.formState.errors[props.name] && ( <FormMessage> {data.formState.errors[props.name]?.message?.toString()} </FormMessage> )} </FormItem> )} /> ); }; export default InputExt;
3. 修改InputText组件为泛型组件
同样将InputText改为泛型组件,传递泛型参数给InputExt:
import { ReactNode } from "react"; import { Input, InputProps } from "@/components/ui/input"; import InputExt from './InputExt'; import type { Path, FieldValues } from 'react-hook-form'; // 泛型Props,关联表单的FieldValues类型T type InputTextProps<T extends FieldValues> = { name: Path<T>; // 用Path<T>约束name helpText?: ReactNode; } & InputProps; // 定义泛型组件 const InputText = <T extends FieldValues>(props: InputTextProps<T>) => { const { name, helpText = null, children, ...inputProps } = props; return ( // 将泛型T传递给InputExt <InputExt<T> name={name} fieldDescription={helpText} render={(d) => ( <Input {...inputProps} {...d.field}> {children} </Input> )} /> ); }; export default InputText;
4. 使用示例(自动类型推断)
当你在组件中使用FormProviderExt时,表单的类型会自动传递给子组件,name属性会获得类型提示和校验:
// 定义表单字段类型 type SignupForm = { email: string; password: string; username: string; }; // 初始化表单 const form = useForm<SignupForm>(); const handleSignup = (data: SignupForm) => { // 处理提交逻辑 }; // 使用组件 <FormProviderExt form={form} submitHandler={handleSignup}> {/* 这里name只能是SignupForm中的字段,输入"fdx"会触发TypeScript错误 */} <InputText name="email" placeholder="邮箱" className="py-6 bg-gray-100 border-2 border-gray-300 my-6" /> </FormProviderExt>
关键原理说明
Path<T>是React Hook Form提供的工具类型,它会生成T所有合法字段路径的联合类型,确保name只能是表单中存在的字段。- 通过给抽象组件添加泛型参数
T,并关联到表单的FieldValues类型,让TypeScript能够自动推断和校验字段名。 - 使用
useFormContext<T>明确获取对应类型的表单上下文,确保FormField的control与字段类型匹配。
内容的提问来源于stack exchange,提问作者Md. A. A. Apu
相关产品推荐
相关产品推荐

