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

React useMutationObserver Hook示例无法运行,求排查差异

问题分析与差异点

1. 回调函数的闭包陷阱与依赖问题

你的incrementMutationCount函数存在闭包问题:它直接引用了mutationCount的当前值,每次组件重新渲染时,这个函数都会被重新创建,并且捕获的是旧的mutationCount值,导致调用setMutationCount(mutationCount + 1)时无法正确更新计数。同时,由于这个函数每次都是新的引用,会触发useMutationObserver里的useEffect重新执行,反复创建和销毁MutationObserver实例。

另外,你没有用useCallback包裹这个回调函数,导致不必要的Observer重建。正确的做法是改用函数式更新,并包裹useCallback:

const incrementMutationCount = React.useCallback(() => {
  setMutationCount(prev => prev + 1);
}, []);

2. 默认Options的引用不稳定

useMutationObserver的默认options是在函数参数里创建的对象,每次调用这个hook时,都会生成一个新的对象引用。这会导致useEffect的依赖项变化,进而反复触发Observer的创建与销毁。应该把默认options提取到hook外部:

const DEFAULT_OBSERVER_OPTIONS = {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
};

const useMutationObserver = (
  ref,
  callback,
  options = DEFAULT_OBSERVER_OPTIONS
) => {
  React.useEffect(() => {
    if (ref.current) {
      const observer = new MutationObserver(callback);
      observer.observe(ref.current, options);
      return () => observer.disconnect();
    }
  }, [callback, options, ref]);
};

3. Resize操作不会触发MutationObserver

你代码里的可resize div使用了resize: both,但浏览器的原生resize操作不会触发MutationObserver——MutationObserver仅监听DOM结构(子节点增减)、元素属性变化、文本内容变化。如果需要监听元素尺寸变化,应该使用ResizeObserver,而非MutationObserver。这也是你误以为resize会增加计数但实际没反应的原因。

4. DOM节点监听范围的匹配问题

原示例可能将ref直接挂在了可编辑/变化的目标节点上,而你的ref挂在了外层div。虽然设置了subtree: true可以监听子节点变化,但结合前面的闭包问题,导致内容变化时也无法正确触发计数更新。修正闭包问题后,内容变化应该能正常触发计数。

修正后的完整代码

import React from "react";

const DEFAULT_OBSERVER_OPTIONS = {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
};

const useMutationObserver = (
  ref,
  callback,
  options = DEFAULT_OBSERVER_OPTIONS
) => {
  React.useEffect(() => {
    if (ref.current) {
      const observer = new MutationObserver(callback);
      observer.observe(ref.current, options);
      return () => observer.disconnect();
    }
  }, [callback, options, ref]);
};

const App = () => {
  const mutationRef = React.useRef();
  const [mutationCount, setMutationCount] = React.useState(0);
  const [content, setContent] = React.useState('Hello world');

  const incrementMutationCount = React.useCallback(() => {
    setMutationCount(prev => prev + 1);
  }, []);

  useMutationObserver(mutationRef, incrementMutationCount);

  // 可选:添加ResizeObserver监听尺寸变化
  React.useEffect(() => {
    if (mutationRef.current?.firstElementChild) {
      const resizeObserver = new ResizeObserver(() => {
        setMutationCount(prev => prev + 1);
      });
      resizeObserver.observe(mutationRef.current.firstElementChild);
      return () => resizeObserver.disconnect();
    }
  }, []);

  return (
    <>
      <label htmlFor="content-input">Edit this to update the text:</label>
      <textarea
        id="content-input"
        style={{ width: '100%' }}
        value={content}
        onChange={e => setContent(e.target.value)}
      />
      <div
        style={{ width: '100%' }}
        ref={mutationRef}
      >
        <div
          style={{
            resize: 'both',
            overflow: 'auto',
            maxWidth: '100%',
            border: '1px solid black',
            padding: '1rem'
          }}
        >
          <h2>Resize or change the content:</h2>
          <p>{content}</p>
        </div>
      </div>
      <div>
        <h3>Mutation count {mutationCount}</h3>
      </div>
    </>
  );
};

export default App;

内容的提问来源于stack exchange,提问作者user6329530

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 01:35:15