React Hook Form+Zod向嵌套表单传递register的TypeScript问题
解决React Hook Form嵌套Zod Schema的register类型兼容问题
核心思路
通过字段路径前缀拼接结合TypeScript类型适配,让复用的子组件能正确接收父组件的register函数,同时匹配嵌套Zod Schema的类型约束。以下是两种可行的实现方案:
方案一:包装register函数适配子组件类型
1. 定义可复用的子Schema与类型
import { z } from "zod"; // 子Schema export const ValueAppreciationComponentSchema = z.object({ amount: z.number().min(0, "金额不能为负"), rate: z.number().min(0).max(100, "利率范围0-100"), period: z.number().min(1, "周期至少为1") }); // 子表单值类型 export type ValueAppreciationFormValues = z.infer<typeof ValueAppreciationComponentSchema>;
2. 实现复用的子表单组件
子组件接收适配后的register函数,直接使用自身Schema的字段名:
import React from "react"; import { Register } from "react-hook-form"; import { ValueAppreciationFormValues } from "./schema"; type ValueAppreciationComponentProps = { register: Register<ValueAppreciationFormValues>; }; export const ValueAppreciationComponent = ({ register }: ValueAppreciationComponentProps) => { return ( <div className="value-appreciation-group"> <input type="number" {...register("amount")} placeholder="初始金额" /> <input type="number" {...register("rate")} placeholder="年利率(%)" /> <input type="number" {...register("period")} placeholder="投资周期(年)" /> </div> ); };
3. 父组件中包装register并传递
父Schema嵌套子Schema,通过包装函数拼接字段前缀,同时做类型适配:
// 父Schema import { z } from "zod"; import { ValueAppreciationComponentSchema } from "./schema"; export const InvestmentSchema = z.object({ investmentName: z.string().min(1, "请输入投资名称"), valueAppreciation: ValueAppreciationComponentSchema, // 其他父级字段... }); export type InvestmentFormValues = z.infer<typeof InvestmentSchema>;
import React from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { InvestmentSchema, InvestmentFormValues } from "./schema"; import { ValueAppreciationComponent, ValueAppreciationFormValues } from "./ValueAppreciationComponent"; export const InvestmentForm = () => { const { register, handleSubmit } = useForm<InvestmentFormValues>({ resolver: zodResolver(InvestmentSchema), defaultValues: { investmentName: "", valueAppreciation: { amount: 0, rate: 5, period: 10 } } }); // 包装register,拼接嵌套字段前缀 const childRegister = <T extends keyof ValueAppreciationFormValues>(name: T) => { return register(`valueAppreciation.${name as string}`); }; const onSubmit = (data: InvestmentFormValues) => { console.log("提交数据:", data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input type="text" {...register("investmentName")} placeholder="投资项目名称" /> {/* 传递适配后的register给子组件 */} <ValueAppreciationComponent register={childRegister as typeof register<ValueAppreciationFormValues>} /> <button type="submit">提交表单</button> </form> ); };
方案二:传递字段前缀,子组件内部拼接路径
这种方式无需包装register,子组件通过接收的前缀拼接完整字段路径,类型更安全:
1. 修改子组件Props
import { Register, FieldPath } from "react-hook-form"; type ValueAppreciationComponentProps<T extends string> = { register: Register<any>; fieldPrefix: T; };
2. 子组件内部拼接路径
export const ValueAppreciationComponent = <T extends string>({ register, fieldPrefix }: ValueAppreciationComponentProps<T>) => { return ( <div className="value-appreciation-group"> <input type="number" {...register(`${fieldPrefix}.amount` as const)} placeholder="初始金额" /> <input type="number" {...register(`${fieldPrefix}.rate` as const)} placeholder="年利率(%)" /> <input type="number" {...register(`${fieldPrefix}.period` as const)} placeholder="投资周期(年)" /> </div> ); };
3. 父组件调用子组件
直接传递register和嵌套字段前缀即可:
<ValueAppreciationComponent register={register} fieldPrefix="valueAppreciation" />
方案说明
- 方案一优势:子组件逻辑更简洁,完全不用关心嵌套层级,适合固定结构的子表单复用
- 方案二优势:无需类型断言,TypeScript可自动推断字段路径的合法性,父组件控制更灵活
内容的提问来源于stack exchange,提问作者Federico Taschin
相关产品推荐
相关产品推荐

