IT일상

[Web Components] VanillaJS에서 React처럼 개발하기. HTML Custom Elements 소개

  • 프론트엔드
Profile picture

Written by solo5star

2023. 2. 23. 17:55

본 글에서는 Custom Element를 적용해보는 위주로 다룹니다. 상세한 내용을 확인하려면 아래의 링크를 참고해주세요.

<cr-button id="addShortcut" tabindex="0" noink="" title="바로가기 추가" aria-label="바로가기 추가" role="button" aria-disabled="false">
  <!-- ... -->
</cr-button>

브라우저에서 이와 같은 엘리먼트를 보신 적 있으신가요? 분명 HTML엔 없는 버튼인데, 뭔가 사이트 측에서 커스텀한 느낌입니다.

1

위의 코드는 사실 크롬에서 새 탭(chrome://new-tab-page)을 열었을 때 나타나는 "바로가기 추가" 에 해당되는 엘리먼트입니다.

이와 같이 사용자가 정의한 엘리먼트를 Custom Element 라고 합니다.

Custom Element

<body>
  <my-alert></my-alert>
</body>

<script>
customElements.define('my-alert', class MyAlert extends HTMLElement {
  constructor() {
    super();

    this.innerHTML = `
      <style>
        div {
          padding: 12px;
          background-color: yellow;
          color: red;
        }
      </style>

      <div>경고가 발생하였습니다</div>
    `;
  }
});
</script>

조금 길지만, Custom Element의 innerHTML 을 제외하면 그렇게 많진 않습니다. 보시면 아시겠지만, innerHTML은 Custom Element가 가지는 내부 HTML 코드입니다.

2 3

코드를 실행했을 때, 이렇게 나타납니다.

이걸 활용하면 반복되는 번거로운 태그들(div, img 등)을 하나로 묶어 관리할 수 있습니다. 이 외에도 다른 기능들을 알아봅시다.

connectedCallback / disconnectedCallback

Custom Element가 document에 추가되었을 때 호출됩니다. 기본적으로 document에 추가된 직후 한 번만 호출되지만, 만약 document에서 지웠다가 추가하는 것을 반복할 경우 여러 번 호출될 수 있습니다.

customElements.define('my-input', class MyInput extends HTMLElement {
  constructor() {
    super();

    this.innerHTML = `
      <style>
        input {
          padding: 10px;
          border: 1px solid grey;
        }
      </style>
      
      <input>
      <p>당신의 입력은?: <span></span></p>
    `;
  }

  onInput(event) {
    this.querySelector('span').innerText = event.target.value;
  }

  connectedCallback() {
    console.log('connected');
    this.querySelector('input').addEventListener('change', this.onInput.bind(this));
  }

  disconnectedCallback() {
    console.log('disconnected');
    this.querySelector('input').removeEventListener('change', this.onInput);
  }
});

setTimeout, setInterval, addEventListener / clearTimeout, clearInterval, removeEventListener 등, DOM의 생성과 소멸 시 수행해야 할 동작이 있을 때 유용합니다.

observedAttributes / attributesChangedCallback

observedAttributes는 관찰할 속성(attribute)들 입니다. attributesChangedCallbackobservedAttributes로 관찰한 속성에 변경이 생겼을 때 호출됩니다.

customElements.define('my-alert', class MyAlert extends HTMLElement {
  constructor() {
    super();

    this.innerHTML = `
      <style>
        div { padding: 12px; }

        [data-severity="warn"] { background: yellow; }
        [data-severity="error"] { background: red; }
        [data-severity="info"] { background: grey; }
      </style>
      
      <div data-severity="warn">문제가 발생하였습니다.</div>
    `;
  }

  static get observedAttributes() {
    return ['severity'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'severity') {
      this.querySelector('div').dataset.severity = newValue;
    }
  }
});

Custom Element를 사용하는 이유?

6

잘 비유된 그림이 있어 가져와 봤습니다. 보편적으로는, Javascript가 HTML, CSS를 직접 조작하여 UI 로직을 수행합니다. Javascript가 조작하는 HTML, CSS는 굉장히 중첩되어 있는 div일 수도 있고, 아주아주~긴 CSS 클래스 이름일 수도 있습니다.

<body>
  <div class="app__container">
    <div class="app__header">
      <div class="app__title">
        <div class="app__title-main-text"></div>
        <div class="app__title-sub-text"></div>
      </div>
      <div class="app__navigation-container">
        <div class="app__navigation-main-items">
          <div>
<body>
  <app-header><!-- ... --></app-header>
  <app-navigation>
    <app-navigation-item></app-navigation-item>
  </app-navigation>
  <!-- ... -->

하지만 Custom Element는 컴포넌트 단위로 묶어 작업을 할 수 있게 해주기 때문에, UI 개발을 훨씬 쉽게 해 줍니다.

마무리하며...

이미 이러한 문제를 해결하기 위한 솔루션이 많습니다. 컴포넌트 단위로 작업을 가능케 해주는 React, Vue.js, Svelte 같은 프레임워크를 보통 많이 사용합니다.

따라서 라이브러리를 사용하지 못하는 환경이 아닌 이상 더 많은 도구를 제공해주고, 더 편리하고, 레퍼런스가 많은 React 같은 프론트엔드 프레임워크를 선택할 것입니다.

그렇지만 Vanilla JS에도 기존의 문제를 해결하기 위한, 이러한 방법이 존재한다는 사실을 기억해주면 좋을 것 같습니다!


Profile picture

Written by solo5star

안녕하세요 👋 개발과 IT에 관심이 많은 solo5star입니다

  • GitHub
  • Baekjoon
  • solved.ac
  • about
© 2023, Built with Gatsby