如何基于Zod判别式联合细化React Hook Form的表单类型?
问题
我用Zod定义了一个判别式联合Schema,想根据判别字段type细化React Hook Form的表单类型,但用watch或getValues做类型守卫时不生效,能不能把form的类型细化成更具体的子类型?
代码示例:
import { useForm, UseFormReturn } from 'react-hook-form'; import { z } from 'zod'; const BazSchema = z.object({ type: z.literal('baz'), baz: z.string(), }); type BazSchema = z.infer<typeof BazSchema>; const BarSchema = z.object({ type: z.literal('bar'), bar: z.string(), }); type BarSchema = z.infer<typeof BarSchema>; const Schema = z.discriminatedUnion('type', [BazSchema, BarSchema]); type Schema = z.infer<typeof Schema>; export function MyComponent() { const form = useForm<Schema>({ defaultValues: { type: 'baz', }, }); const type = form.watch('type'); if (type === 'baz') { // TypeScript会报错:form类型为UseFormReturn<Schema>,无法赋值给UseFormReturn<BazSchema> return <BazForm form={form} />; } return null; } export function BazForm({ form }: { form: UseFormReturn<BazSchema> }) { return ( <form {...form}> <input {...form.register('baz')} /> </form> ); }
解决方案
方法1:类型断言(快速实现)
在条件分支里直接对form做类型断言,明确告知TypeScript当前表单的具体类型:
if (type === 'baz') { return <BazForm form={form as UseFormReturn<BazSchema>} />; }
注意:需确保运行时type的取值和断言类型一致,否则可能引发运行时错误,适合逻辑简单的场景。
方法2:自定义类型守卫(更安全)
编写类型守卫函数,结合表单getValues验证当前类型,让TypeScript自动细化form的类型:
import { UseFormReturn } from 'react-hook-form'; function isBazForm(form: UseFormReturn<Schema>): form is UseFormReturn<BazSchema> { return form.getValues('type') === 'baz'; } // 使用示例 if (isBazForm(form)) { // TypeScript自动识别form为UseFormReturn<BazSchema> return <BazForm form={form} />; }
这种方式兼顾类型安全和运行时验证,适合复杂业务场景。
方法3:传递control而非完整form
拆分组件时,让子组件接收Control类型而非整个UseFormReturn,结合useWatch精准监听类型字段:
import { useForm, useWatch, Control } from 'react-hook-form'; export function MyComponent() { const form = useForm<Schema>({ defaultValues: { type: 'baz' }, }); const type = useWatch({ control: form.control, name: 'type' }); return type === 'baz' ? <BazForm control={form.control} /> : null; } export function BazForm({ control }: { control: Control<BazSchema> }) { return ( <form> <input {...control.register('baz')} /> </form> ); }
这种方式利用React Hook Form的Control类型特性,让子组件类型更精准,避免对整个form做类型转换。
为什么默认类型守卫不生效?
React Hook Form的UseFormReturn类型基于初始泛型Schema生成,watch返回的type虽能被类型守卫识别,但TypeScript无法自动关联form的整体类型和单个字段的取值——因为form的类型是固定的联合类型,必须通过手动断言或类型守卫明确告知TypeScript当前对应的具体子类型。
内容的提问来源于stack exchange,提问作者Brian Gates
相关产品推荐
相关产品推荐

