728x90

자바스크립트만 사용해서(제이쿼리, 리액트, 뷰 등을 사용하지 않고) 웹 컴포넌트를 만드는 방법에 대해 알아보겠습니다.

바닐라 자바스크립트란?
프레임워크나 라이브러리가 포함되지 않은 순수한 자바스크립트를 의미합니다.

웹 컴포넌트
HTML 엘리먼트를 위해 만들어진 재사용 가능한 자바스크립트 코드를 의미합니다. 재사용이 가능하다는 것은 코드가 함수 등으로 작성되어 언제든 임의의 HTML 엘리먼트에 적용할 수 있다는 것을 의미합니다.

TLDR;

  • 함수를 이용해 만드는 방법은 쉽고 빠르게 작성할 수 있어 웹 컴포넌트를 구현할 때 가장 많이 쓰이는 방식
  • MutationOvserver는 IE11 이하 버전을 지원하지 않으며, 모든 엘리먼트의 추가 / 제거를 감시해 성능 문제를 일으킬 수 있음
  • 커스텀 엘리먼트를 만드는 방법은 깔끔하지만, 커스텀 엘리먼트를 중첩해서 사용할 수 없음.

함수를 이용한 방법

함수를 실행해 엘리먼트에 자바스크립트를 적용하는 방법입니다.

<span class="custom">Click me</span>
function customComponent(elements) {
  for (const element of elements) {
    element.onClick = function() {
      element.textContent = "Already clicked";
    };
  }
}

customComponent(document.querySelectorAll(".custom"));

위 방법은 동적으로 자바스크립트에서 HTML을 생성하는 경우, 함수를 실행시켜주는 것을 잊어버린다면 적용이 되지 않는다는 단점이 있습니다. 하지만, 간단하게 구현할 수 있고 호환성 문제와 성능 저하 문제 없이 구현할 수 있다는 장점이 있어서 웹 컴포넌트를 구현할 때 많이 쓰이는 방법입니다.

MutationObserver를 이용한 방법

MutationOvserver를 이용해 classdataset과 같은 HTML 속성 값의 변화를 감지하는 방법이 있습니다.

MutationObserver이란?

함수를 이용해서 엘리먼트에 자바스크립트를 적용하는 방법은 동적으로 추가된 엘리먼트를 놓칠 수 있다는 문제가 있습니다. 이러한 문제를 피하기 위해 DOM의 변화를 감지하는 MutationObserver를 사용할 수 있습니다.

기본적인 사용 방법은 다음과 같습니다.

// 변화를 감지할 노드를 선택
const targetNode = document.querySelector(".target");

// 변화 감지에 대한 옵션 설정
const config = { attributes: true, childList: true, subtree: true };

// 변화가 감지될 때 실행할 콜백 함수
const callback = function (mutationList, observer) {
  for (const mutation of mutations) {
    if (mutation.type === "attributes") {
      console.log(mutation.attributeName + ' 속성이 수정되었습니다.');
    } else if (mutation.type === "childList") {
      console.log('자식 노드가 추가되거나 제거되었습니다.');
    }
  }
};

// 실행
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);

disconnect 함수를 이용해 관찰을 중지할 수 있습니다.

observer.disconnect();

option 설정

사용하는 옵션에 따라 mutation의 실행 방법이 달라지므로 정확한 설정 값이 필요합니다.

속성 설명
childList true / false 대상 노드의 하위 요소가 추가되거나 제거되는 것을 감지합니다.
attributes true / false 대상 노드의 속성 변화를 감지합니다.
characterData true / false 대상 노드의 데이터 변화를 감지합니다.
subtree true / false 대상의 하위의 하위의 요소들까지의 변화를 감지합니다.
attributeOldValue true / false 변화 이전의 속성 값을 기록합니다.
characterDataOldValue true / false 변화 이전의 데이터 값을 기록합니다.
attributeFilter ["...", "..."] 모든 속성의 변화를 감지할 필요가 없는 경우
속성을 배열로 설정합니다.

위 표는 모든 옵션에 대한 설명입니다.

구현 방법

<span class="custom">Click me</span>
for (const element of document.querySelectorAll(".custom")) {
  element.onClick = function() {
    element.textContent = "Already clicked";
  };
}

const config = { attributeFilter: ["class"], childList: true, subtree: true };

const callback = function (mutations) {
  for (const mutation of mutations) {
    if (mutation.type === "attributes") {
      if (mutation.target.classList.contains("custom")) {
        mutation.target.onclick = function() {
          element.textContent = "Already clicked";
        };
      } else {
        mutation.target.onClick = null;
      }
    } else if (mutation.type === "childList") {
      for (const addedNode of mutation.addNodes) {
        if (addedNode instanceof Element) {
          if (addNode.classList.contains("custom")) {
            addedNode.onclick = function() {
              element.textContent = "Already clicked";
            };
          }
        }
      }
    }
  }
};

new MutationOvserver(callback)
  .observe(document, config);

함수와 비교했을 때 코드가 많이 복잡하고 한 눈에 보기 어렵다는 단점이 있습니다. 엘리먼트의 속성 값의 변화와 자식 엘리먼트의 변화에 따라 다루는 방법이 달라 코드가 매우 길어집니다.

또한, 위 방법은 모든 엘리먼트의 추가 / 제거를 감지해서 반복적으로 엘리먼트가 추가되거나 제거되는 상황에서는 성능 문제를 일으킬 수 있으며, IE11 이하 버전에서는 동작하지 않습니다.

customElements를 이용한 방법

customElements를 이용해 웹 컴포넌트를 구현할 수 있습니다.

customElements란?

Window 인터페이스 내 customElements 속성은 요소를 등록하거나 이전에 등록한 요소에 대한 정보를 받아올 수 있는 CustomElementRegistry 객체의 참조를 반환할 수 있습니다.

예제

customElements를 사용하는 가장 흔한 예시는 새로운 요소를 등록하기 위해 CustomElementRegistry.define() 메소드에 적용하는 것입니다.

const customElementRegistry = window.customElements;
customElementRegistry.define("my-custom-element", MyCustomElement);

다음과 같이 줄여서 사용할 수 있습니다.

customElements.define(
  "element-details",
  class extends HTMLElement {
    constructor() {
      super();

      const template = document
        .getElementById("element-details-template")
        .content;
      const shadowRoot = this.attachShadow({mode: "open"})
        .appendChild(template.cloneNode(true));
    }
  }
);

구현

<custom-element>Click me</custom-element>
customElements.define(
  "custom-element",
  class extends HTMLElement {
    constructor() {
      super();

      this.onclick = function() {
        element.textContent = "Already clicked";
      };
    }
  }
);

customElements를 이용한 방법은 코드의 가독성도 좋고 깔끔하다는 장점이 있지만, 여러 엘리먼트에 동일한 커스텀 엘리먼트를 중첩해서 적용할 수 없다는 단점이 있습니다.

 

 

728x90
728x90