• MDN Webdoc의 표준, Template & Slot

    2023. 5. 23.

    by. mason.jeong

    웹 페이지에서 동일한 마크업 구조를 반복적으로 사용해야 하는 경우, 동일한 구조를 반복해서 코딩하는 것보다  일종의 템플릿을 사용하면 반복 작업을 줄일 수 있습니다. 요즘은 React, Vuejs의 컴포넌트를 사용하여 재사용성을 높이고 기능의 캡슐화까지 이루었습니다. 그에 맞춰 웹의 표준을 관리하는 MDN에서는 웹 컴포넌트라는 아키텍처를 고안하여 여러 가지 기술을 적용했습니다. Template과 Slot 은 그중에 하나입니다.

     

    오래전부터 template 태그가 존재했습니다. template 태그를 어떻게 사용했을까요 ?

    <template id="my-card" class="">
    	<p class="title">{title}</p>
        <#if{body}>
        	<span class="body-text">{body}<span>
        <#end>
        <#else>
        	<span class="body-empty">내용이 없습니다.</span>
        <#end>
        <p class="footer">footer</p>
    </template>

     

    기본적으로 Template 태그는 화면에 표시되지 않는 속성을 가지고 있지만 자바스크립트에서 참조할 수 있어 템플릿의 내용을 복제하여 실제 DOM으로 추가하는 것이 가능합니다.

    let card = document.getElementById('my-card');
    let cardBody = card.content;
    
    card.title = '제목';
    card.body = '입니다';
    
    document.body.appendChild(cardBody);

     

    사실 {title}, {body} #if, #else 등을 자바스크립트 기본 표현식처럼 동작하게 하는 것은 template에서 지원하는 기능은 아닙니다. 추가적인 외부 라이브러리를 이용해야 합니다. 

     

    일반적으로 자바스크립트만을 가지고 코딩하는 경우는 아래와 같이 사용할 수 있었습니다.

    <template id="my-card">
    	<div class="block">
        	<h2 class="title"></h2>
            <span class="body"></span>
        </div>
    </templatE>
    
    <script>
    	let $template = document.getElementById('my-card').html;
        
        $template.getElementsByClass('title').text = '제목';
        $template.getElementsByClass('body').text = '입니다';
        
        document.body.appendChild($template);
    </script>

     

    위에서 잠깐 이야기한 것처럼 템플릿 태그는 브라우저에 의해 렌더링 되지 않습니다만. 참조가 가능합니다. 공식 문서를 살펴보면 마크업이 DOM에 숨겨져 있으며 렌더링 되지 않는다고 표현되어 있습니다. 또한 템플릿이 사용될 때까지 스크립트가 실행되지 않고 이미지 또한 로드되지 않으며 오디오가 재생되지 않는다고 합니다. 또한 당연한 이야기 이겠지만 기본적으로 돔 트리와는 별개의 영역에서 생성되어 실렉터를 이용하여 템플릿의 하위 노드가 반환되지 않습니다.

     

    템플릿 태그는 head, body, frameset 등의 어느 위치에 있는지 상관없이 해당 요소에 허용되는 모든 유형의 내용을 포함할 수 있다고 합니다. 어디서나라는 이야기는 HTML 파서가 허용하지 않는 곳에서 <template>을 안전하게 사용할 수 있다는 것을 의미합니다. 또한 <table>또는 <select>의 하위 항목으로도 배치할 수 있습니다.

     

    이전 포스팅에서 커스텀 엘리먼트와 쉐도우 돔에 대해서 설명했었는데요, 이번에는 템플릿 태그를 쉐도우 돔에서 사용해 보도록 하겠습니다.

     

    <template id="card">
    <style>
        p {
        	color: white;
        	background-color: #666;
        	padding: 5px;
        }
    </style>
    <p>내 카드</p>
    </template>
    
    <script>
        customElements.define('my-card', class extends HTMLElement {
            constructor() {
                super();
                let card = document.getElementById('card');
                let cardContent = card.content;
    
                const shadowRoot = this.attachShadow({ mode: 'open' })
                    .appendChild(cardContent.cloneNode(true));
            }
        };
    </script>

     

    바뀐 건 쉐도우돔에 들어가는 마크업이 템플릿 태그로 교체되었습니다. 템플릿 내에서 선언되는 스타일 태그는 렌더링 되지 않으므로 공통 스타일에 영향을 미치지 않습니다. 바뀐건 단지 쉐도우돔에 들어가는 태그일 뿐입니다. 왜 이렇게 사용하는 게 유용할까요? 템플릿 태그에는 Slot이라는 좋은 기능이 있기 때문입니다.

     

    Slot 은 동적인 환경에 대응할 수 있습니다. 내가 만들어놓은 템플릿의 일부와 스타일을 사용하면서 추가로 필요한 마크업과 스크립트를 템플릿 태그에 주입해 줄 수 있습니다.

     

    위에서 생성된 my-card 커스텀 태그를 살펴보겠습니다. 커스텀 태그는 현재 아래와 같이 사용할 순 있지만 하위 요소가 렌더링 되진 않습니다.

    <my-card>하위 요소</my-card>

    "하위 요소"라는 텍스트는 렌더링 되지 않습니다. 이런 경우 Slot을 사용해 자식 요소를 렌더링 하도록 할 수 있습니다. template 태그의 내용을 일부만 변경해 보겠습니다.

     

    <my-card>테스트</my-card>
    
    <template id="card">
        <style>
            p {
                color: white;
                background-color: black;
                padding: 5px;
            }
        </style>
        <p>
        	<!-- slot 이 추가되었습니다. -->
            <slot></slot>
        </p>
    </template>

     

    렌더링을 해보니 추가한 자식 요소가 렌더링 되는 것을 확인할 수 있습니다.

     

    여기서는 slot을 하나만 사용하여 자식 요소를 렌더링 하게 하였지만 각 슬롯에 이름을 지정하여 여러 요소를 동적으로 렌더링 해줄 수 있습니다. 여러 가지 슬롯이 있는 예제를 살펴보겠습니다. 템플릿 태그를 아래와 같이 수정해 보겠습니다.

    <my-card>
        <h2 slot="title">제목</h2>
        <p slot="body">내용</p>
    </my-card>
    
    <template id="card">
        <style>
            p {
                color: white;
                background-color: black;
                padding: 5px;
            }
        </style>
        <p>
            <slot name="title">제목영역</slot>
            <slot name="body">컨텐츠 영역</slot>
            <slot name="footer">푸터 영역</slot>
        </p>
    </template>

    각 이름에 맞는 슬롯을 지정해 주면 내가 원하는 내용을 동적으로 전달할 수 있습니다. 만약 템플릿 태그에 슬롯이 존재하지만 사용 시 추가하지 않았다면 footer 영역처럼 기본 자식 요소가 렌더링 됩니다.

     

     

    템플릿과 슬롯을 알아보았는데요. 슬롯은 Vuejs의 개념을 먼저 익히고 난 뒤 알게 되어서 쉽게 이해가 되었지만 처음 접하는 분들에게는 조금 생소하거나 헷갈릴 수 있을 것 같습니다. 이름에 의미가 있기때문에 slot 이라는 이름 자체를 이해하면 도움이 될 것 같습니다. 요즘은 프로젝트를 하게 되면 보통 리액트나 뷰를 쓰기 때문에 잘 사용되진 않지만 백엔드 베이스의 프로젝트를 하게 되면 유용하게 사용해볼 수 있을것 같습니다만. 이 자체로만 사용하기에는 개념이 생소하기 때문에 여러 가지 일관된 방식으로 라이브러리화가 필요할 것 같습니다. 

     

    궁금한 점은 댓글 주시면 아는 선에서 답변드리도록 하겠습니다.

    감사합니다.

     

     

    댓글