React Hook Form:如何避免重复向多个TextField传递Control和Errors props
解决react-hook-form重复传递control/errors的方案
核心思路:用React Context共享全局表单上下文
你之前用自定义hook返回组件导致失焦,本质是每次调用hook都会生成全新的组件构造函数,组件每次渲染都会被重新挂载,自然会丢失输入焦点。用Context是更稳妥的方案——把control和errors放到上下文里,让封装的TextfieldController自动读取,不用手动重复传参。
步骤1:创建表单上下文
先定义一个Context来存储react-hook-form的核心实例数据:
// FormContext.js import { createContext, useContext } from 'react'; const FormContext = createContext(null); export const FormProvider = FormContext.Provider; export const useFormContext = () => { const context = useContext(FormContext); if (!context) { throw new Error('useFormContext必须在FormProvider内部使用'); } return context; };
步骤2:在表单根组件中注入上下文
在调用useForm的组件里,把control和errors通过Provider传递给所有子组件:
// 你的表单组件 import { useForm } from 'react-hook-form'; import { FormProvider } from './FormContext'; import TextfieldController from './TextfieldController'; function MyForm() { const { control, formState: { errors } } = useForm(); return ( <FormProvider value={{ control, errors }}> <TextfieldController name="username" label="用户名" rules={{ required: '用户名必填' }} /> <TextfieldController name="email" label="邮箱" rules={{ required: '邮箱必填', pattern: { value: /^\S+@\S+$/i, message: '邮箱格式不正确' } }} /> {/* 其他表单组件直接用,不用传control/errors */} </FormProvider> ); }
步骤3:修改TextfieldController消费上下文
改造你封装的组件,不再接收control和errors,而是通过useFormContext自动获取:
// TextfieldController.js import { Controller } from 'react-hook-form'; import TextField from '@mui/material/TextField'; // 这里假设你用MUI的TextField,可根据实际组件替换 import { useFormContext } from './FormContext'; function TextfieldController({ name, label, rules, ...rest }) { const { control, errors } = useFormContext(); return ( <Controller name={name} control={control} rules={rules} render={({ field }) => ( <TextField {...field} label={label} error={!!errors[name]} helperText={errors[name]?.message} {...rest} /> )} /> ); } export default TextfieldController;
为什么这个方案不会失焦?
因为TextfieldController是一个固定的组件,不会每次渲染都重新创建,只是在内部读取上下文的值,组件的身份始终稳定,所以onChange时不会触发卸载/重新挂载,自然不会丢失输入焦点。
额外优化:封装复用型Form组件
如果多个页面都需要这个逻辑,可以把FormProvider和useForm的逻辑封装成通用组件:
// ReusableForm.js import { useForm } from 'react-hook-form'; import { FormProvider } from './FormContext'; function ReusableForm({ children, defaultValues, ...formOptions }) { const { control, formState: { errors }, handleSubmit, reset } = useForm({ defaultValues, ...formOptions }); return ( <FormProvider value={{ control, errors }}> {children({ handleSubmit, reset })} </FormProvider> ); } export default ReusableForm;
使用时会更简洁:
function MyForm() { return ( <ReusableForm defaultValues={{ username: '', email: '' }}> {({ handleSubmit }) => ( <form onSubmit={handleSubmit((data) => console.log(data))}> <TextfieldController name="username" label="用户名" rules={{ required: '用户名必填' }} /> <TextfieldController name="email" label="邮箱" rules={{ required: '邮箱必填' }} /> <button type="submit">提交</button> </form> )} </ReusableForm> ); }
内容的提问来源于stack exchange,提问作者Thushan D.
相关产品推荐
相关产品推荐

