React

梗概

useEffect 无法直接监听 ref 值的变化,这是由于 React 的依赖比较机制决定的,需要采用其他方式处理 ref 变更监听需求。

原因说明

  1. ref 对象比较特性

    • React 的 useEffect 依赖数组使用浅比较(Object.is 或 ===)来检测变化
    • ref 是一个包含 .current 属性的对象,即使内部 .current 值改变,ref 对象引用本身不变
    • useEffect 无法感知到 ref.current 的变化
  2. 依赖追踪机制

    • useEffect 只有在依赖数组中的变量引用发生变化时才会触发
    • ref 对象在组件的整个生命周期中保持同一引用

解决方案

方案一:使用 state 配合

function RefWatcher() {
  const myRef = useRef(initialValue);
  const [refValue, setRefValue] = useState(initialValue);
  
  // 当需要更新 ref 时,同步更新 state
  const updateRefValue = (newValue) => {
    myRef.current = newValue;
    setRefValue(newValue); // 触发重新渲染并使 useEffect 可以监听
  };
  
  useEffect(() => {
    console.log('Ref value changed:', refValue);
    // 此处可执行需要监听 ref 变化的逻辑
  }, [refValue]);
  
  return (
    // 组件内容
  );
}

方案二:使用 MutationObserver(DOM ref)

function DomRefWatcher() {
  const domRef = useRef(null);
  
  useEffect(() => {
    if (!domRef.current) return;
    
    const observer = new MutationObserver((mutations) => {
      // 处理 DOM 变化
    });
    
    observer.observe(domRef.current, {
      attributes: true,
      childList: true,
      subtree: true
    });
    
    return () => observer.disconnect();
  }, []);
  
  return <div ref={domRef}>{/* 内容 */}</div>;
}

方案三:使用自定义 Hook

function useRefEffect(ref, callback, deps = []) {
  useEffect(() => {
    // 手动检测 ref.current 变化的逻辑
    let prevValue = ref.current;
    
    const checkForChanges = () => {
      if (ref.current !== prevValue) {
        callback(ref.current, prevValue);
        prevValue = ref.current;
      }
      
      // 使用 requestAnimationFrame 或 setTimeout 周期性检查
      requestId = requestAnimationFrame(checkForChanges);
    };
    
    let requestId = requestAnimationFrame(checkForChanges);
    
    return () => cancelAnimationFrame(requestId);
  }, deps);
}

最佳实践

  • 优先使用 state 而非 ref 来保存需要监听变化的数据
  • 将需要触发副作用的逻辑与修改 ref 的逻辑放在同一个函数内
  • 仅在不需要重新渲染的情况下使用 ref

相关概念