August 20, 2025

🙈 Mutation Observer 오용: 무한 루프와 성능 저하, 그리고 예측 불가능한 동작

JavaScript
React
성능
컴포넌트
렌더링전략
UX
아키텍처
에러처리

Summary

Mutation Observer를 부주의하게 사용하면 무한 루프, 성능 저하, 예측 불가능한 동작을 초래할 수 있습니다. 상태 관리, 비동기 처리, 감시 범위 최소화, disconnect() 호출 등을 통해 이러한 문제를 해결해야 합니다.

Why Wrong?

Mutation Observer는 DOM 변경을 감지하는 강력한 API이지만, 잘못 사용하면 무한 루프, 성능 저하, 예측 불가능한 동작을 유발할 수 있습니다. 특히 콜백 함수 내에서 감지 대상 DOM을 직접 변경하는 경우, Mutation Observer가 다시 트리거되어 무한 루프에 빠질 위험이 있습니다. 또한, 필요 이상의 넓은 범위의 DOM 변경을 감시하거나, 불필요하게 많은 Mutation Observer 인스턴스를 생성하는 경우 성능 저하를 초래할 수 있습니다. 이는 브라우저의 렌더링 파이프라인을 방해하고 사용자 경험을 악화시킵니다.

How to Fix?

Mutation Observer 콜백 함수 내에서 감지 대상 DOM을 직접 변경하는 것을 피해야 합니다. 상태 관리 라이브러리를 활용하여 DOM 변경을 유발하는 상태를 업데이트하거나, 비동기적으로 DOM을 변경하는 방식으로 무한 루프를 방지할 수 있습니다. 또한, 감시 범위를 최소화하고, 필요한 Mutation Observer 인스턴스만 생성하여 성능을 최적화해야 합니다. 감시를 중단해야 하는 시점에 disconnect() 메서드를 호출하여 메모리 누수를 방지하는 것도 중요합니다.

Before Code (Bad)

import React, { useEffect, useRef } from 'react';

function BadComponent() {
  const elementRef = useRef(null);

  useEffect(() => {
    const observer = new MutationObserver((mutationsList) => {
      mutationsList.forEach(() => {
        // ❌ 직접 DOM 변경 -> 무한 루프 가능성!
        elementRef.current.textContent = 'DOM Updated!';
      });
    });

    observer.observe(elementRef.current, { characterData: true, subtree: true, childList: true });

    return () => observer.disconnect();
  }, []);

  return <div ref={elementRef}>Initial Text</div>;
}

export default BadComponent;

After Code (Good)

import React, { useState, useEffect, useRef } from 'react';

function GoodComponent() {
  const [text, setText] = useState('Initial Text');
  const elementRef = useRef(null);

  useEffect(() => {
    const observer = new MutationObserver((mutationsList) => {
      mutationsList.forEach(() => {
        // DOM 변경 감지 후 상태 업데이트
        setText('DOM Changed!');
      });
    });

    observer.observe(elementRef.current, { characterData: true, subtree: true, childList: true });

    return () => observer.disconnect();
  }, []);

  return <div ref={elementRef}>{text}</div>;
}

export default GoodComponent;