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

如何基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 07:46:19