You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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 };
}

实现关键点

  1. 回调Ref跟踪节点变化:用combinedRef这个回调Ref替代传统的对象Ref,每次DOM节点挂载、卸载或替换时都会触发回调,确保我们能及时处理事件的绑定与清理。
  2. 清理旧节点监听器:在绑定新节点事件前,先移除前一个节点的监听器,彻底避免内存泄漏。
  3. 兼容外部Ref:支持传入对象Ref或回调Ref,同步更新外部引用,不影响原有业务逻辑。
  4. 稳定的事件处理函数:用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 05:54:57