[웹 컴포넌트] Template and Slot

Chad Lee·2023년 2월 14일
2

web-components

목록 보기
4/5

들어가며

: <template><slot>은 유연한 DOM 구조를 구현하게 해주는 elements로 물론, 단독 사용도 가능하지만, Shadow DOM과 함께 사용하면 재사용성 측면에서 주는 이점이 크다.

<template> 의 경우 로드 시 직접 render되지 않고 보통 clone 을 통해 HTML 코드에 담겨져 render 한다. <slot> 의 경우는 html에서 slot의 name 을 attribute로 사용하여 적용한다.

Shadow DOM과 함께 사용할 때도 동일하게 사용 한다.

기본 설명

template

: <template> 을 이용한 재사용은 그대로 유용하지만 Shadow DOM과 함께 사용하면 더 강력해 진다.

Custom Elements 에서 사용한 예제를 이용한다면 모양이 아래와 같을 것이다.

Shadow DOM에 붙일 돔 구조를 생성하는데 꽤 많은 코드가 들어간다.

class MyElement extends HTMLElement {
	constructor() {
    // 항상 super를 호출 해야 한다.
		super();
		// Create spans
		const wrapper = document.createElement("span");
		wrapper.setAttribute("class", "wrapper");
		const icon = document.createElement("span");
		icon.setAttribute("class", "icon");
		icon.setAttribute("tabindex", 0);
		const info = document.createElement("span");
		info.setAttribute("class", "info");
		
		// Take attribute content and put it inside the info span
		const text = this.getAttribute("data-text");
		info.textContent = text;
		
		// Insert icon
		const img = document.createElement("img");
		img.src = this.hasAttribute("img")
		  ? this.getAttribute("img")
		  : "img/default.png";
		img.alt = this.hasAttribute("alt")
		  ? this.getAttribute("alt")
		  : "";
		icon.appendChild(img);
			}
		}

customElements.define('my-element', MyElement);

같은 코드를 <template> 이용하여 구현하면 아래와 같은 모양이 된다.

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

      let template = document.getElementById("my-elements");
      let templateContent = template.content;

      const shadowRoot = this.attachShadow({ mode: "open" });
      shadowRoot.appendChild(templateContent.cloneNode(true));
    }
  }
);

물론 해당 <template> 은 별도 정의되어 있고 참조 할 수 있어야 한다.

cloneNode 를 이용하여 root의 자식으로 붙여 사용한다.

<template> 내부에서 <style> 을 이용할 수 있다. 이는 스타일링을 캡슐화하는데 큰 이점이 있다.

CSS의 경우 cascading 의 특징 때문에 적용한 스타일이 다른 컴포넌트에도 영향을 미치지만 이런식으로 캡슐화 되면 다른 컴포넌트에 영향을 미치지 않는다. 아래와 같이 적용 한다.

<my-element>
  <div slot="title">Slot 사용법</div>
  <div slot="description">slot의 사용법을 서술 합니다.</div>
</my-element>

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

      this._tpl = document.createElement("template");
      this._tpl.innerHTML = `<style>
          ::slotted(*) {
            color: red;
          }
        </style>
        <slot name="title">title 입력 위치</slot>
        <slot name="description">description 입력 위치</slot>
      `;
      this._root = this.attachShadow({ mode: "open" });
      this._root.appendChild(this._tpl.content.cloneNode(true));
    }
  }
);

slot

: <slot> 은 place-holder 역할로 자리를 비우고 있지만 사용자가 향후 컨텐츠를 채워 넣을 수 있는 요소다. 더 간단히 말하자면 정의한 <slot> 에 해당 slot의 name 이 attribute로 설정된 요소를 끼워 넣는다. <slot>은 attribute로 name 가지며 해당 attribute 기준으로 slot을 채운다.

💡 **Shadow DOM** 과 함께 써야 실제 slot에 맵핑 된 요소가 채워진다.

아래와 같이 사용 가능하다.

// custom elements 정의
window.customElements.define(
  "my-element",
  class extends HTMLElement {
    constructor() {
      super();
			// shadow root 생성
      this._root = this.attachShadow({ mode: "open" });
    }
    connectedCallback() {
      this._root.innerHTML = `
      <div>
				// slot 위치 
        <slot name='title'> 제목이 위치할 자리입니다. </slot>
	      <slot name='description'> 설명이 위치할 자리입니다. </slot>
      </div>
      `;
    }
  }
);

<my-element>
  <!-- 위치할 slot 이름과 맵핑 -->
  <div slot="title">slot 사용법</div>
  <div slot="description">slot의 사용법을 서술 합니다.</div>
</my-element>

정의 되었으나 채워지지 못한 slot은 말 그대로 place-holder 역할을 수행한다. 위 코드 같은 경우

<slot name='title'> 이 채워지지 않았다면 ‘제목이 위치할 자리입니다.’ 가 출력 된다.

여기서 주의가 필요한 부분이 있는데 만약 로드 된 HTML에 <slot> 이 포함 되어 있다면 template> 과는 다르게 render가 된다. 그렇기 때문에 <template> 의 자식으로 <slot> 을 사용 하는 방법이 유리하다.

마치며

: shadow DOM과 함께 template을 이용하면 재사용성을 높이고 스타일링도 함께 격리할 수 있어 괜찮은 조합이다. 다만 실제 프로젝트를 위해서는 직접 createElement로 모든 element를 생성하는것 보다 구조가 한눈에 보이는 template literal 로 관리하는게 좋아 보이는다. element의 구조가 보여야 코드가 쉽게 눈에 들어온다. React의 jsx도 같은 고민의 결과 일거라 생각한다. 그렇다면 template 템플릿 엔진 까지는 아니라도 <template>, <style> 등의 요소가 포함된 template literal을 관리하기 위한 유틸함수가 필요 할 것 같다.

예를 들면 underscore의 html 함수나, styled-component의 css 함수와 같은 역할을 수행 하면 조금 더 편하게 사용 가능 할 것 같다.

0개의 댓글