React Hook Form带可选前缀的嵌套表单如何解决TypeScript类型问题
核心实现思路
通过TS泛型+字段路径类型推导,同时兼容带/不带prefix的使用场景,保证全链路类型校验生效。
完整实现代码
import { useFormContext, Path, FieldValues } from 'react-hook-form'; // 基础表单数据结构 type TaskFormData = { title: string; }; // 通用工具类型:根据前缀生成合法字段路径 type PrefixedPath<P extends string | undefined, T extends FieldValues> = P extends string ? `${P}.${Path<T>}` : Path<T>; // 组件入参类型 type TaskFormProps<P extends string | undefined = undefined> = { prefix?: P; }; // 导出给外层使用的类型:无前缀场景的表单数据 export type TaskFormRawData = TaskFormData; // 导出给外层使用的类型:带指定前缀场景的表单数据 export type TaskFormPrefixedData<P extends string> = { [K in P]: TaskFormData; }; export const TaskForm = <P extends string | undefined = undefined>({ prefix }: TaskFormProps<P>) => { const { register } = useFormContext<FieldValues>(); // 构造符合类型要求的字段名 const fieldName = (prefix ? `${prefix}.title` : 'title') as PrefixedPath<P, TaskFormData>; return <input {...register(fieldName)} />; };
使用示例
单表单无前缀场景
import { useForm, FormProvider } from 'react-hook-form'; import { TaskForm, TaskFormRawData } from './TaskForm'; export const Form = () => { const methods = useForm<TaskFormRawData>({ defaultValues: { title: 'Some input value' } }); const { handleSubmit } = methods; const onSubmit = (data: TaskFormRawData) => { alert(JSON.stringify(data)); }; return ( <FormProvider {...methods}> <form onSubmit={handleSubmit(onSubmit)}> <TaskForm /> </form> </FormProvider> ); }
多表单带前缀场景
import { useForm, FormProvider } from 'react-hook-form'; import { TaskForm, TaskFormPrefixedData } from './TaskForm'; // 假设NoteForm同理导出了对应类型 import { NoteForm, NoteFormPrefixedData } from './NoteForm'; // 合并多表单数据类型 type CombinedFormData = TaskFormPrefixedData<'task'> & NoteFormPrefixedData<'note'>; export const PrefixedForm = () => { const methods = useForm<CombinedFormData>({ defaultValues: { task: { title: 'Some input value' }, note: { /* note表单默认值 */ } } }); const { handleSubmit } = methods; const onSubmit = (data: CombinedFormData) => { alert(JSON.stringify(data)); }; return ( <FormProvider {...methods}> <form onSubmit={handleSubmit(onSubmit)}> <NoteForm prefix="note" /> <TaskForm prefix="task" /> </form> </FormProvider> ); };
原方案问题说明
- 第一种方案:模板字符串语法错误,
${P}.K会将K识别为固定字符串字面量,正确写法应为${P}.${K & string},同时useFormContext传入联合类型时,TS无法确定当前字段属于哪一种类型分支,会触发参数不兼容报错。 - 第二种方案:前缀数据结构定义正确,但同样存在联合类型推导失败的问题,无法匹配
register方法的入参类型要求。
内容的提问来源于stack exchange,提问作者Ricardo Marimon
相关产品推荐
相关产品推荐

