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;