You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>
  );
};

原方案问题说明

  1. 第一种方案:模板字符串语法错误,${P}.K会将K识别为固定字符串字面量,正确写法应为${P}.${K & string},同时useFormContext传入联合类型时,TS无法确定当前字段属于哪一种类型分支,会触发参数不兼容报错。
  2. 第二种方案:前缀数据结构定义正确,但同样存在联合类型推导失败的问题,无法匹配register方法的入参类型要求。

内容的提问来源于stack exchange,提问作者Ricardo Marimon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 04:18:01