Zeno ZENO

[Part 9] Web Components — static template 패턴 & 메모리 최적화

innerHTML을 매번 파싱하는 비용을 줄이는 static template 패턴. Heap/Stack 메모리 구조, cloneNode와 ES Modules type=module을 실전 코드로 정리합니다.

[Part 9] Web Components — static template 패턴 & 메모리 최적화

Part 8에서는 Web Components를 form과 연동하는 방법을 알아봤다.

이번 글에서는 Web Components를 더 효율적으로 만드는 방법을 알아본다.

컴포넌트를 하나만 만들 때는 성능 문제가 크게 느껴지지 않는다.

하지만 같은 컴포넌트를 수십 개, 수백 개 만들면 불필요한 작업이 반복될 수 있다.

이때 사용할 수 있는 패턴이 static template 패턴이다.

1. 왜 최적화가 필요할까?

같은 컴포넌트를 여러 번 사용하는 경우를 생각해보자.

<user-card name="사용자 A"></user-card>
<user-card name="사용자 B"></user-card>
<user-card name="사용자 C"></user-card>

각 user-card가 매번 같은 HTML 구조와 CSS를 새로 만든다면 반복 작업이 많아진다.

간단한 컴포넌트에서는 큰 문제가 아니지만, 구조가 복잡해질수록 비용이 커질 수 있다.

2. 비효율적인 방식

class UserCard extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <article>
        <h3>사용자 이름</h3>
        <p>사용자 설명</p>
      </article>
    `;
  }
}

customElements.define('user-card', UserCard);

이 방식은 이해하기 쉽다.

하지만 user-card가 여러 번 생성되면 같은 HTML 문자열을 계속 파싱하게 된다.

파싱은 문자열을 브라우저가 이해할 수 있는 DOM 구조로 바꾸는 과정이다.

같은 구조를 계속 파싱하는 것은 불필요한 작업이 될 수 있다.

3. Template을 사용하는 방식

Template을 사용하면 HTML 구조를 미리 보관해둘 수 있다.

<template id="user-card-template">
  <article>
    <h3><slot name="name"></slot></h3>
    <p><slot name="description"></slot></p>
  </article>
</template>

컴포넌트에서는 이 template을 찾아 복사한다.

class UserCard extends HTMLElement {
  connectedCallback() {
    const template = document.querySelector('#user-card-template');

    const shadow = this.attachShadow({
      mode: 'open'
    });

    shadow.appendChild(
      template.content.cloneNode(true)
    );
  }
}

이 방식은 구조 관리가 더 편하다.

하지만 컴포넌트가 생성될 때마다 document.querySelector()로 template을 찾는 작업이 반복된다.

4. static이란?

static은 class 자체에 속하는 값을 의미한다.

일반 속성은 객체마다 따로 존재한다.

하지만 static 속성은 class에 하나만 존재한다.

class Counter {
  static count = 0;
}

console.log(Counter.count);

Web Components에서는 공통 template을 static으로 보관할 수 있다.

그러면 여러 인스턴스가 같은 template을 공유할 수 있다.

5. static template 패턴

class UserCard extends HTMLElement {
  static template = document.createElement('template');

  static {
    this.template.innerHTML = `
      <style>
        article {
          padding: 16px;
          border: 1px solid #ddd;
          border-radius: 8px;
        }
      </style>

      <article>
        <h3><slot name="name"></slot></h3>
        <p><slot name="description"></slot></p>
      </article>
    `;
  }

  connectedCallback() {
    const shadow = this.attachShadow({
      mode: 'open'
    });

    shadow.appendChild(
      UserCard.template.content.cloneNode(true)
    );
  }
}

customElements.define('user-card', UserCard);

이 방식에서는 template을 class가 한 번만 만든다.

각 user-card는 이미 만들어진 template을 복사해서 사용한다.

6. 코드 리뷰

AD

제휴 광고 · 일부 링크는 수수료를 받을 수 있습니다

옆커폰

static template은 UserCard class 자체에 연결된 template이다.

document.createElement('template')은 JavaScript로 template 요소를 만든다.

static { ... } 블록은 class가 정의될 때 한 번 실행된다.

UserCard.template.content.cloneNode(true)는 class가 보관한 template을 복사한다.

7. 왜 cloneNode(true)를 사용할까?

template.content를 그대로 넣으면 원본이 이동할 수 있다.

컴포넌트마다 독립적인 DOM 구조가 필요하므로 복사해서 사용해야 한다.

UserCard.template.content.cloneNode(true)

true는 자식 요소까지 전부 복사하겠다는 뜻이다.

8. static에 넣으면 안 되는 것

static은 모든 인스턴스가 공유한다.

따라서 개별 컴포넌트마다 달라지는 상태를 static에 넣으면 안 된다.

예를 들어 각 카드의 이름, 선택 여부, 열림 상태 같은 값은 인스턴스별로 달라야 한다.

이런 값은 this._value 같은 인스턴스 속성으로 관리해야 한다.

9. render와 template 분리하기

실무에서는 template은 구조를 담당하고, render는 데이터를 반영하는 방식으로 나누는 것이 좋다.

class UserCard extends HTMLElement {
  static template = document.createElement('template');

  static {
    this.template.innerHTML = `
      <article>
        <h3 id="name"></h3>
      </article>
    `;
  }

  connectedCallback() {
    const shadow = this.attachShadow({
      mode: 'open'
    });

    shadow.appendChild(
      UserCard.template.content.cloneNode(true)
    );

    this.render();
  }

  render() {
    const name = this.getAttribute('name') || '이름 없음';

    this.shadowRoot
      .querySelector('#name')
      .textContent = name;
  }
}

template은 기본 구조를 제공한다.

render는 현재 attribute나 state 값을 화면에 반영한다.

10. 언제 static template을 사용할까?

항상 static template을 써야 하는 것은 아니다.

간단한 컴포넌트라면 innerHTML만으로도 충분하다.

하지만 다음 상황에서는 static template 패턴이 유용하다.

  • 같은 컴포넌트를 많이 생성할 때
  • 컴포넌트 내부 구조가 복잡할 때
  • 스타일 코드가 길 때
  • UI 라이브러리나 디자인 시스템을 만들 때

11. 정리

static template 패턴은 Web Components의 반복 작업을 줄이는 데 도움을 준다.

  • template은 재사용할 HTML 구조를 보관한다.
  • static template은 class 단위로 template을 공유한다.
  • 컴포넌트가 많을수록 반복 작업을 줄일 수 있다.
  • cloneNode(true)로 복사해서 각 인스턴스에 넣는다.
  • 공통 구조는 static에 두고 개별 상태는 인스턴스에서 관리한다.

다음 Part에서는 Shadow DOM을 사용할지 말지 판단하는 기준을 알아본다.

AD

제휴 광고

일부 링크는 제휴 링크이며, 구매 또는 가입 시 일정 수수료를 받을 수 있습니다.

AD

'Web components' 카테고리의 다른 글

전체보기