: <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을 채운다.
아래와 같이 사용 가능하다.
// 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 함수와 같은 역할을 수행 하면 조금 더 편하게 사용 가능 할 것 같다.