React Hook Form:如何复用带不同control与name的useFieldArray组件?
处理React Hook Form v7中嵌套数组字段组件的TypeScript类型问题
我编写了一个处理数组值的Field Array组件,该数组值可在不同表单的不同层级嵌套。向组件传入表单control和对应字段名name以获取值,但在React Hook Form v7版本中无法正确进行类型定义——代码可正常运行,但会触发TypeScript错误。
简化代码示例
import { Control, useFieldArray } from "react-hook-form"; interface Item { key: string; } interface A { items: Item[]; } interface B { data: { items: Item[]; }; } interface ItemFormFieldsProps<TControl extends Control<A> | Control<B>> { control: TControl; name: TControl extends Control<A> ? "items" : "data.items"; } const ItemFormFields = <TControl extends Control<A> | Control<B>>({ control, name, }: ItemFormFieldsProps<TControl>) => { const { fields } = useFieldArray({ control, // 此处触发TypeScript错误 name, // 此处触发TypeScript错误 }); };
错误信息
control 参数错误
Type 'Control<A, any> | Control<B, any>' is not assignable to type 'Control<A, any> | undefined'. Type 'Control<B, any>' is not assignable to type 'Control<A, any>'. The types of '_subjects.state' are incompatible between these types. Type 'FormStateSubjectRef<B>' is not assignable to type 'FormStateSubjectRef<A>'. Type 'FormStateSubjectRef<B>' is not assignable to type '{ readonly observers: Observer<Partial<FormState<A>> & { name?: string | undefined; }>[]; subscribe: (value: Observer<Partial<FormState<A>> & { ...; }>) => Subscription; unsubscribe: Noop; }'. Types of property 'observers' are incompatible. Type 'Observer<Partial<FormState<B>> & { name?: string | undefined; }>[]' is not assignable to type 'Observer<Partial<FormState<A>> & { name?: string | undefined; }>[]'. Type 'Observer<Partial<FormState<B>> & { name?: string | undefined; }>' is not assignable to type 'Observer<Partial<FormState<A>> & { name?: string | undefined; }>'. Type 'Partial<FormState<A>> & { name?: string | undefined; }' is not assignable to type 'Partial<FormState<B>> & { name?: string | undefined; }'. Type 'Partial<FormState<A>> & { name?: string | undefined; }' is not assignable to type 'Partial<FormState<B>>'. Types of property 'defaultValues' are incompatible. Type 'A | Readonly<{ items?: ({ key?: string | undefined; } | undefined)[] | undefined; }> | undefined' is not assignable to type 'B | Readonly<{ data?: { items?: ({ key?: string | undefined; } | undefined)[] | undefined; } | undefined; }> | undefined'. Type 'A' is not assignable to type 'B | Readonly<{ data?: { items?: ({ key?: string | undefined; } | undefined)[] | undefined; } | undefined; }> | undefined'.ts(2322) fieldArray.d.ts(8, 5): The expected type comes from property 'control' which is declared here on type 'UseFieldArrayProps<A, "items", "id">'
name 参数错误
Type '"items" | "data.items"' is not assignable to type '"items"'. Type '"data.items"' is not assignable to type '"items"'.ts(2322) fieldArray.d.ts(6, 5): The expected type comes from property 'name' which is declared here on type 'UseFieldArrayProps<A, "items", "id">'
解决方案
问题根源在于泛型约束直接使用Control<A> | Control<B>,TypeScript无法在useFieldArray中正确推断联合类型的分支,导致类型不匹配。以下是两种可行的修复方案:
方案1:针对指定表单类型的精确约束
将表单根类型作为泛型参数,通过条件类型关联control与name的类型:
import { Control, useFieldArray, useForm } from "react-hook-form"; interface Item { key: string; } interface A { items: Item[]; } interface B { data: { items: Item[]; }; } // 以表单根类型作为泛型参数 interface ItemFormFieldsProps<T extends A | B> { control: Control<T>; name: T extends A ? "items" : "data.items"; } const ItemFormFields = <T extends A | B>({ control, name, }: ItemFormFieldsProps<T>) => { // 通过泛型分支让TypeScript正确推断参数类型 const { fields } = useFieldArray({ control, name, } as T extends A ? { control: Control<A>, name: "items" } : { control: Control<B>, name: "data.items" }); // 此处返回组件内容 return null; }; // 正确使用示例 const UsageA = () => { const { control } = useForm<A>(); return <ItemFormFields control={control} name="items" />; }; const UsageB = () => { const { control } = useForm<B>(); return <ItemFormFields control={control} name="data.items" />; };
方案2:通用型约束(支持任意含Item数组的表单)
通过工具类型自动匹配表单中所有指向Item[]的字段路径:
import { Control, useFieldArray, Path, FieldValues, useForm } from "react-hook-form"; interface Item { key: string; } interface A { items: Item[]; } interface B { data: { items: Item[]; }; } // 自动提取表单中所有指向Item[]的字段路径 type ItemArrayPath<T extends FieldValues> = { [K in Path<T>]: T[K] extends Item[] ? K : never; }[Path<T>]; interface ItemFormFieldsProps<T extends FieldValues> { control: Control<T>; name: ItemArrayPath<T>; } const ItemFormFields = <T extends FieldValues>({ control, name, }: ItemFormFieldsProps<T>) => { const { fields } = useFieldArray({ control, name }); return null; };
内容的提问来源于stack exchange,提问作者adesurirey
相关产品推荐
相关产品推荐

