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

