如何通过react-hook-form向子组件传递表单初始值?
问题分析与优化方案
你的代码确实能实现表单初始值填充的需求,但存在几个可以优化的点,比如不必要的无限循环渲染,以及子组件和表单逻辑的耦合问题。React Hook Form其实有更优雅的方式来处理自定义子组件的初始值和状态同步,下面给你拆解最优实践:
现有代码的问题
- 无限循环渲染:两个组件里的
useEffect都没有设置依赖数组,这会导致每次组件渲染时都会执行setValue,触发重新渲染,形成无限循环(你可以在控制台看看console.log(year)是不是一直在重复输出)。 - 子组件耦合度高:把子组件的
register和setValue直接从父组件传递,让YearSelect和表单逻辑强绑定,复用性变差。
最优解决方案:使用Controller组件
React Hook Form提供了Controller组件,专门用来处理自定义受控组件,它会帮你管理组件的注册、值同步和验证,不需要手动传递register或setValue,代码更简洁解耦。
修改后的完整代码:
import { useEffect } from 'react'; import { useForm, Controller } from 'react-hook-form'; function YearSelect({ value, onChange }) { return ( <select value={value} onChange={(e) => onChange(e.target.value)}> <option value="">---</option> <option value="2021">2021</option> <option value="2022">2022</option> </select> ) } function App() { // 1. 优先用defaultValues设置初始值,这是React Hook Form的推荐方式 const { register, handleSubmit, control, setValue } = useForm({ defaultValues: { title: '', year: '' } }); // 2. 模拟API调用后填充初始值,添加空依赖数组确保只执行一次 useEffect(() => { // 假设这里是API请求逻辑 setTimeout(() => { setValue('title', 'HEY'); setValue('year', '2021'); }, 1000); }, [setValue]); // 依赖setValue,避免不必要的重复执行 const onSubmit = data => { console.log(data); alert('表单提交成功'); } return ( <form onSubmit={handleSubmit(onSubmit)}> Title: <input name="title" {...register('title')}/><br/> Year: {/* 用Controller包裹自定义组件,作为表单和组件的桥梁 */} <Controller name="year" control={control} render={({ field: { value, onChange } }) => ( <YearSelect value={value} onChange={onChange} /> )} /> <button type="submit">Save</button> </form> ); } export default App;
方案说明
- 用
defaultValues初始化:这是React Hook Form官方推荐的初始值设置方式,表单加载时会自动填充对应字段的值,避免手动调用setValue的繁琐。 Controller的作用:它作为表单和自定义组件之间的桥梁,自动处理值的同步、注册和验证逻辑,子组件只需要接收value和onChange,完全和表单逻辑解耦,复用性更强。- 修复useEffect循环:给
useEffect添加依赖数组[setValue],确保只有在setValue变化时才执行(实际上setValue是useForm返回的稳定函数,所以这个effect只会执行一次),避免无限循环。
另一种简化方案(不使用Controller)
如果你不想用Controller,也可以通过传递control给子组件,用control.register来注册字段,同时利用watch获取字段值,不过这种方式还是不如Controller优雅:
// YearSelect组件修改 function YearSelect({ control, watch }) { const yearValue = watch('year', ''); // 第二个参数是默认值 return ( <select {...control.register('year')} value={yearValue} > <option value="">---</option> <option value="2021">2021</option> <option value="2022">2022</option> </select> ) } // App组件中使用 <YearSelect control={control} watch={watch} />
但这种方式还是需要子组件依赖表单的control和watch,不如Controller的解耦效果好,所以优先推荐第一种方案。
内容的提问来源于stack exchange,提问作者Ruben
相关产品推荐
相关产品推荐

