如何在React Hook Form受控组件中正确添加debounce防抖功能
解决方案
报错根因
你遇到的报错是React合成事件的复用机制导致的:React为了提升性能会将合成事件放入事件池统一复用,事件回调执行完成后,事件对象的所有属性会被清空重置。你直接把onChange包入防抖函数后,事件对象的使用被延迟,等防抖逻辑执行时,事件已经被回收,拿到的是无效的残留对象,因此抛出类型错误。
正确实现步骤
前置注意
防抖函数必须持久化存储,不能在render阶段每次生成新实例,否则防抖功能会完全失效,同时要在组件卸载时清理未执行的防抖回调。
方案1:提前提取事件值(推荐)
只把需要用到的取值传给防抖函数,不传递整个事件对象,不会影响React事件池的正常运行:
import { useCallback, useEffect } from 'react'; import { Controller, useForm } from 'react-hook-form'; import { debounce } from 'lodash'; // 也可以替换为自己实现的防抖函数 const FormComponent = () => { const { control } = useForm(); // 持久化防抖更新函数 const debouncedUpdate = useCallback( debounce((value, onChange) => { onChange(value); }, 500), [] ); // 组件卸载时清理未执行的防抖回调 useEffect(() => { return () => debouncedUpdate.cancel(); }, [debouncedUpdate]); return ( <Controller control={control} name="test" render={({ field: { onChange, value, name } }) => ( <Dropdown name={name} value={value} // 先提取取值再传给防抖逻辑,不传整个事件 handleChange={(e) => { // 根据你的Dropdown返回值调整取值逻辑,若直接返回选中值可直接写const val = e const val = e.target?.value ?? e; debouncedUpdate(val, onChange); }} options={foodCategories()} /> )} /> ) }
方案2:保留事件对象(不推荐)
如果你确实需要用到事件对象的其他属性,可以调用e.persist()将事件从React事件池中取出,避免被回收重置:
const debouncedChange = useCallback( debounce((e, onChange) => { e.persist(); onChange(e); }, 500), []); // 组件内调用 handleChange={(e) => debouncedChange(e, onChange)}
注意事项
- 若使用自定义实现的防抖函数,需要自行补充
cancel方法用于清理未执行的回调。 - 优先使用方案1,
e.persist()会占用事件池资源,非必要场景不建议使用。
内容的提问来源于stack exchange,提问作者lost9123193
相关产品推荐
相关产品推荐

