React Hook Form受控组件封装的TypeScript类型错误排查与解决
React Hook Form 封装 Office UI Fabric TextField 的类型错误修复
问题背景
项目中使用react-hook-form的<Controller>封装office-ui-fabric-react的TextField组件,原有代码运行正常:
<Controller control={control} name={'Value'} rules={{ required: true }} render={({ field, fieldState, formState }) => ( <TextField value={field.value} errorMessage={fieldState.error?.message} label={field.name} description={'Value of the property'} onChange={(_, value) => field.onChange(value)} onBlur={field.onBlur} required /> )} />
为简化重复代码,希望封装成ControlledTextField组件,用法如下:
<ControlledTextField control={control} name={'Value'} rules={{ required: true }} label={'Value'} description={'Value of the property'} required />
现有TypeScript实现代码如下,但编译时报错:Type 'Control<F, any>' is not assignable to type 'Control<FieldValues, any>',错误出现在<Controller>的control={control}处:
type ControllerPropsEx = Omit<ControllerProps, 'render' | 'control' | 'name'>; // Base type for controlled inputs type ControlledType<F extends FieldValues, T> = { name: Path<F>; control: Control<F>; controllerProps?: ControllerPropsEx; } & T; // props specific to TextField component, combined with controller props // and where I remove props actually managed by react hook form type ControlledTextFieldProps<F extends FieldValues> = ControlledType< F, Omit<ITextFieldProps, 'onChange' | 'onBlur' | 'value' | 'errorMessage'> >; const ControlledTextField = <F extends FieldValues>({ name, control, controllerProps, ...props }: ControlledTextFieldProps<F>): JSX.Element => { return ( <Controller name={name} control={control} {...controllerProps} render={( { field: { onBlur, onChange, value, name }, fieldState: { error } } ) => ( <TextField {...props} name={name} value={value || ''} onChange={onChange} onBlur={onBlur} errorMessage={error?.message} /> )} /> ); };
错误原因
ControllerProps默认使用Control<FieldValues, any>类型,而组件泛型F extends FieldValues导致传入的Control<F>与Controller期望的默认类型不兼容。需要让ControllerProps也绑定泛型F,匹配组件的表单字段类型。
解决方案
修改类型定义,让ControllerPropsEx基于带泛型的ControllerProps<F>,同时给<Controller>组件指定泛型F,确保类型完全匹配:
// 调整ControllerPropsEx为带泛型的类型 type ControllerPropsEx<F extends FieldValues> = Omit<ControllerProps<F>, 'render' | 'control' | 'name'>; // Base type for controlled inputs type ControlledType<F extends FieldValues, T> = { name: Path<F>; control: Control<F>; controllerProps?: ControllerPropsEx<F>; } & T; // props specific to TextField component, combined with controller props // and where I remove props actually managed by react hook form type ControlledTextFieldProps<F extends FieldValues> = ControlledType< F, Omit<ITextFieldProps, 'onChange' | 'onBlur' | 'value' | 'errorMessage'> >; const ControlledTextField = <F extends FieldValues>({ name, control, controllerProps, ...props }: ControlledTextFieldProps<F>): JSX.Element => { return ( // 给Controller指定泛型F <Controller<F> name={name} control={control} {...controllerProps} render={({ field: { onBlur, onChange, value, name: fieldName }, fieldState: { error } }) => ( <TextField {...props} name={fieldName} value={value || ''} // 适配TextField的onChange参数格式(第二个参数是值) onChange={(_, newValue) => onChange(newValue)} onBlur={onBlur} errorMessage={error?.message} /> )} /> ); };
额外注意:原代码中onChange直接传递给TextField会存在参数不匹配问题(TextField的onChange第一个参数是事件,第二个是值,而react-hook-form的field.onChange只接收值),因此需要调整onChange的处理逻辑,确保参数正确传递。
验证使用
封装后的组件可按预期正常使用,类型检查完全通过:
// 定义表单字段类型 type FormValues = { Value: string; }; // 在表单中使用 const { control } = useForm<FormValues>(); <ControlledTextField<FormValues> control={control} name={'Value'} rules={{ required: '该字段必填' }} label={'Value'} description={'Value of the property'} required />
内容的提问来源于stack exchange,提问作者Steve B
相关产品推荐
相关产品推荐

