MutationEvent & MutationObserver

Sckroll·2024년 10월 13일

개요


  • MutationEvent는 DOM의 변화(요소 추가, 제거 등)를 감지하는 이벤트의 인터페이스이다. DOMNodeInserted, DOMNodeRemoved와 같은 이벤트가 여기에 속한다.

  • 하지만 MutationEvent는 deprecated 되었다. (!)

    MutationEvent MDN 노트

    • 이벤트 리스너를 추가하면 해당 문서에 대한 추가 DOM 수정의 성능이 1.5배에서 7배 정도 저하된다고 한다.
    • 그리고 이벤트 리스너를 제거해도 손상에 대해서 다시 되돌릴 수 없다는 문제가 있다.
  • MDN에서는 MutationEvent 대신 MutationObserver를 사용하는 것을 권장하고 있다.

    • 실제로 MutationEvent를 사용하면 콘솔 창에 다음과 같은 경고 메시지가 나타난다. MutationEvent 콘솔 경고 메시지

MutationEvent


특징(이자 문제점)

  • 동기적으로 이벤트를 처리한다.
  • 이벤트 캡처링 & 버블링으로 인해 DOM을 수정하는 다른 이벤트 리스너를 건드릴 수 있는데, 이렇게 되면 많은 MutationEvent를 일으킬 수 있고, 이는 JS 스레드를 막히게 하거나 브라우저 충돌을 초래한다.

이벤트 종류

  • DOMAttrModified
    • DOM 요소의 속성이 변경되었을 때 발생하는 이벤트
    • 사파리에서는 지원하지 않는다.
  • DOMAttributeNameChanged
    • DOM 요소의 속성 이름이 변경되었을 때 발생하는 이벤트
    • 파이어폭스에서는 지원하지 않는다.
  • DOMCharacterDataModified
    • DOM 노드 내 텍스트가 변경되었을 때 발생하는 이벤트
  • DOMElementNameChanged
    • DOM 요소 이름이 변경되었을 때 발생하는 이벤트
    • 파이어폭스에서는 지원하지 않는다.
  • DOMNodeInserted
    • DOM 요소의 자식 요소가 들어왔을 때 발생하는 이벤트
  • DOMNodeInsertedIntoDocument
    • DOM 노드를 직접 삽입하거나, 노드가 포함된 하위 트리를 삽입할 때 발생하는 이벤트
    • 노드가 직접 삽입되면 DOMNodeInserted 이벤트가 먼저 발생된다.
  • DOMNodeRemoved
    • DOM 요소의 자식 요소가 제거되었을 때 발생하는 이벤트
  • DOMNodeRemovedFromDocument
    • DOM 노드를 직접 제거하거나, 노드가 포함된 하위 트리를 제거할 때 발생하는 이벤트
    • 노드가 직접 제거되면 DOMNodeRemoved 이벤트가 먼저 발생된다.
  • DOMSubtreeModified
    • 문서에 대한 모든 변경 사항을 알리는 이벤트
    • 위의 모든 이벤트가 발생한 후에 발생된다.

사용법

element.addEventListener('DOMNodeInserted', e => {
  // ...
});

MutationObserver


특징

  • 비동기적으로 이벤트를 처리한다.
  • 다른 스크립트가 끝날 때까지 대기한다.
  • 배열 안에 담아서 기록하며, 모든 노드나 특정 노드의 변화를 관찰하는 것이 가능하다.
  • 이벤트가 아니므로 다른 이벤트와 충돌할 일이 없다. 즉, JS 스레드가 막히거나 브라우저 충돌의 가능성이 적다.

사용법

// mutationList: 일어난 변경 각각을 나타내는 MutationRecord 배열
// observer: 콜백을 호출한 MutationObserver 자체
const observer = new MutationObserver((mutationList, observer) => {
  if (mutationList[0].addedNodes.length > 0) {
		// ...
  }
});

// DOM 노드 변경 알림 수신 설정
// element: 변경을 감지할 DOM 노드 또는 하위 트리 루트로서의 Node(Element 포함)
// options: 알림 수신 설정 옵션
observer.observe(element, { childList: true, subtree: true });

// 알림 큐를 비우고, 큐에서 대기 중인 알림(MutationRecord)으로 구성된 배열 반환
const mutations = observer.takeRecords();
if (mutations.length > 0) {
	// ...
}

// DOM 노드 변경 알림 수신 해제 (observe()로 다시 수신 가능)
observer.disconnect();
  • MutationRecord: 노드에 일어난 변경을 나타내는 인터페이스

    • addedNodes: 새로 추가된 노드가 들어있는 배열(NodeList), 혹은 빈 배열
    • attributeName: 바뀐 속성(attribute)의 이름을 나타내는 문자열 혹은 null
    • attributeNamespace: 바뀐 속성(attribute)의 네임스페이스를 나타내는 문자열 혹은 null
    • nextSibling: 추가됐거나 제거된 노드의 다음 형제 혹은 null
    • oldValue
      • typeattributes라면 변경 전 속성 값
      • typecharacterData라면 변경 전 노드의 텍스트 데이터
      • typechildList라면 null
    • previousSibling: 추가됐거나 제거된 노드의 이전 형제 혹은 null
    • removedNodes: 제거된 노드가 들어있는 배열(NodeList), 혹은 빈 배열
    • target
      • typeattributes라면 속성이 변경된 노드
      • typecharacterData라면 CharacterData 노드
      • typechildList라면 자식이 변경된 노드
    • type: attributes(속성 변경), characterData(CharacterData 노드 변경), childList(노드 트리 변경)
  • MutationObserverInit: observe()에 인자로 들어가는 options의 인터페이스

    • subtree

      • true일 경우 target이 루트인 하위 트리 전체를 주시
      • 기본값은 false
    • childList

      • true일 경우 대상 노드에 자식이 추가되거나 제거되는 것을 감지
      • subtreetrue면 자손들의 자식 변경도 감지
      • 기본값은 false
    • attributes

      • true일 경우 노드 또는 노드 속성 변경 감지
      • 기본값은 false지만, attributeFilterattributeOldValue가 지정된 경우 true
      • 만약 둘 다 지정하지 않은 상태에서 false로 설정한 경우 TypeError 발생
    • attributeFilter

      • 변경을 감지할 속성 이름의 배열
      • 지정하지 않으면 모든 속성 변경에 대해 알림 생성
    • attributeOldValue

      • true일 경우 노드의 속성 변경을 감지했을 때 바뀌기 전의 값 기록
      • 기본값은 false
    • characterData

      • true일 경우 대상 노드 내의 텍스트가 바뀌는 것을 감지
      • subtreetrue면 자손들의 텍스트 변경도 감지
      • 기본값은 false지만, characterDataOldValue가 지정된 경우 true
        • 만약 characterDataOldValuetrue인 상태에서 true로 설정한 경우 TypeError 발생
    • characterDataOldValue

      • true일 경우 노드의 텍스트 변경을 감지했을 때 바뀌기 전의 값 기록
      • 기본값은 false
      interface MutationObserverInit {
          /** Set to a list of attribute local names (without namespace) if not all attribute mutations need to be observed and attributes is true or omitted. */
          attributeFilter?: string[];
          /** Set to true if attributes is true or omitted and target's attribute value before the mutation needs to be recorded. */
          attributeOldValue?: boolean;
          /** Set to true if mutations to target's attributes are to be observed. Can be omitted if attributeOldValue or attributeFilter is specified. */
          attributes?: boolean;
          /** Set to true if mutations to target's data are to be observed. Can be omitted if characterDataOldValue is specified. */
          characterData?: boolean;
          /** Set to true if characterData is set to true or omitted and target's data before the mutation needs to be recorded. */
          characterDataOldValue?: boolean;
          /** Set to true if mutations to target's children are to be observed. */
          childList?: boolean;
          /** Set to true if mutations to not just target, but also target's descendants are to be observed. */
          subtree?: boolean;
      }
  • options의 경우, 적어도 childListattributescharacterData 중 하나는 true여야 하며, 그렇지 않으면 TypeError가 발생한다.

MutationEvent에서 MutationObserver로 전환하기


DOMNodeInserted

  • DOM 요소가 새로 들어오는 경우에 발생하는 이벤트이다.
  • 파라미터로 들어오는 mutationList 배열의 요소 객체에는 새로 추가되는 DOM 요소를 저장하는 프로퍼티인 addedNodes 배열이 있다.
  • addedNode 요소 개수가 1개 이상이면 새로운 요소가 추가되었다고 판단할 수 있다.
  • 즉, 아래처럼 수정할 수 있다.
    const element = document.querySelector('#root') as HTMLDivElement;
    
    // Before
    element.addEventListener('DOMNodeInserted', e => {
    	// ...
    });
    
    // After 
    const observer = new MutationObserver((mutationList, observer) => {
      if (mutationList[0].addedNodes.length > 0) {
    		// ...
      }
    });
    observer.observe(element, { childList: true, subtree: true });

DOMNodeRemoved

  • DOM 요소가 제거될 때 발생하는 이벤트이다.
  • 파라미터로 들어오는 mutationList 배열의 요소 객체에는 제거된 DOM 요소를 저장하는 프로퍼티인 removedNodes 배열이 있다.
  • removedNodes 요소 개수가 1개 이상이면 제거된 요소가 존재한다고 판단할 수 있다.
  • 즉, 아래처럼 수정할 수 있다.
    const element = document.querySelector('#root') as HTMLDivElement;
    
    // Before
    element.addEventListener('DOMNodeRemoved', e => {
    	// ...
    });
    
    // After 
    const observer = new MutationObserver((mutationList, observer) => {
      if (mutationList[0].removedNodes.length > 0) {
    		// ...
      }
    });
    observer.observe(element, { childList: true, subtree: true });

출처


MutationEvent - Web APIs | MDN

MutationObserver - Web API | MDN

MutationRecord - Web API | MDN

UI Events

DOM 변화 감지하는 방법(Mutation Events, Mutation Observer)

profile
UI, 디자인, 인터랙션에 관심이 있는 주니어 프론트엔드 개발자입니다.

0개의 댓글