如何在React自定义Hook中正确检测DOM Ref变化并处理监听?
解决React自定义Hook中Ref节点变更时事件监听器失效的问题
你遇到的核心问题是React Ref的current属性变更不会触发useEffect重新执行——因为Ref本身是一个稳定的对象,只有current属性可变,所以依赖数组里写Ref不会让useEffect响应节点的挂载/卸载(比如条件渲染导致DOM节点销毁重建的场景),最终导致监听器无法正确清理或重新绑定。
下面是可复用的useIsFocused Hook实现,能完美解决这个问题:
import { useState, useRef, useCallback, Ref } from 'react'; function useIsFocused<T extends HTMLElement>(externalRef?: Ref<T>) { const [isFocused, setIsFocused] = useState(false); const previousElementRef = useRef<T | null>(null); // 缓存事件处理函数,避免闭包问题 const handleFocus = useCallback(() => setIsFocused(true), []); const handleBlur = useCallback(() => setIsFocused(false), []); // 组合内部Ref与外部Ref,跟踪节点变化 const combinedRef = useCallback((node: T | null) => { // 清理前一个节点的事件监听器 if (previousElementRef.current) { previousElementRef.current.removeEventListener('focus', handleFocus); previousElementRef.current.removeEventListener('blur', handleBlur); } // 更新当前节点引用 previousElementRef.current = node; // 给新节点绑定事件 if (node) { node.addEventListener('focus', handleFocus); node.addEventListener('blur', handleBlur); } // 同步到外部传入的Ref if (typeof externalRef === 'function') { externalRef(node); } else if (externalRef != null) { (externalRef as React.MutableRefObject<T | null>).current = node; } }, [externalRef, handleFocus, handleBlur]); return { isFocused, ref: combinedRef }; }
实现关键点
- 回调Ref跟踪节点变化:用
combinedRef这个回调Ref替代传统的对象Ref,每次DOM节点挂载、卸载或替换时都会触发回调,确保我们能及时处理事件的绑定与清理。 - 清理旧节点监听器:在绑定新节点事件前,先移除前一个节点的监听器,彻底避免内存泄漏。
- 兼容外部Ref:支持传入对象Ref或回调Ref,同步更新外部引用,不影响原有业务逻辑。
- 稳定的事件处理函数:用
useCallback包裹事件函数,避免因函数引用变化导致的不必要重渲染。
理想使用示例(App.tsx)
import { useState } from 'react'; import useIsFocused from './useIsFocused'; function App() { const [showInput, setShowInput] = useState(true); const { isFocused, ref: inputRef } = useIsFocused(); return ( <div> <button onClick={() => setShowInput(!showInput)}> {showInput ? '隐藏输入框' : '显示输入框'} </button> {showInput && <input ref={inputRef} placeholder="点击获取焦点" />} <p>输入框是否聚焦: {isFocused ? '是' : '否'}</p> </div> ); } export default App;
这个实现不仅能处理条件渲染场景,还能在节点动态替换时自动同步事件监听器,同时保持简洁的API,保证良好的开发者体验。
内容的提问来源于stack exchange,提问作者d8k_irf
相关产品推荐
相关产品推荐

