React中如何为自定义Hook返回的Ref添加事件监听?
React自定义Hook useInput事件绑定失效的解决方案
问题根源
你的代码核心问题在于:useRef的current是可变容器,React不会将其视为状态更新的触发点——即使ref.current从null变为DOM元素,useEffect也不会重新执行,导致输入元素挂载后无法绑定change事件监听。
最优解决方案
方案1:使用回调式Ref(推荐)
回调式Ref能在元素挂载/卸载时直接触发回调,无需依赖useEffect,逻辑更简洁可靠:
const useInput = () => { const handleChange = () => { console.log("Input updated"); }; // 存储当前DOM元素,用于卸载时清理事件 const inputElementRef = useRef(null); // 回调式Ref,元素挂载/卸载时自动触发 const inputRef = useCallback((element) => { // 先清理之前元素的事件监听 if (inputElementRef.current) { inputElementRef.current.removeEventListener("change", handleChange); } // 给新挂载的元素绑定事件 if (element) { element.addEventListener("change", handleChange); } // 更新当前元素引用 inputElementRef.current = element; }, [handleChange]); return { ref: inputRef }; }; const Input = props => { const { ref: inputRef } = useInput(); return ( <input {...props} ref={inputRef} /> ); };
- 当输入元素挂载到DOM时,回调会收到DOM元素,此时绑定
change事件; - 当元素卸载时,回调会收到
null,此时清理之前存储的元素的事件监听; - 用
useCallback缓存回调Ref,避免因回调频繁变化导致不必要的重渲染。
方案2:通过useState同步Ref变化触发useEffect
如果坚持使用useEffect的写法,可以通过useState同步Ref的变化——State更新会触发组件重渲染,进而让useEffect重新执行:
const useInput = () => { const ref = useRef(null); const [currentElement, setCurrentElement] = useState(null); const handleChange = () => { console.log("Input updated"); }; useEffect(() => { if (!currentElement) return; currentElement.addEventListener("change", handleChange); // 卸载时清理事件 return () => currentElement.removeEventListener("change", handleChange); }, [currentElement, handleChange]); // 回调Ref同步元素到State const inputRef = useCallback((element) => { ref.current = element; setCurrentElement(element); }, []); return { ref: inputRef }; }; const Input = props => { const { ref: inputRef } = useInput(); return ( <input {...props} ref={inputRef} /> ); };
- 用回调Ref将DOM元素同步到State,State更新触发重渲染;
useEffect依赖currentElement,当元素挂载时,currentElement变为DOM节点,useEffect执行并绑定事件。
内容的提问来源于stack exchange,提问作者Grover
相关产品推荐
相关产品推荐

