템플릿, 슬롯, 섀도우

웹 구성요소의 이점은 재사용성입니다. UI 위젯을 한 번 만들고 여러 번 재사용할 수 있습니다. 하는 동안 웹 구성 요소를 만들기 위해 JavaScript가 필요하지만 JavaScript 라이브러리는 필요하지 않습니다. HTML 및 관련 API가 필요한 모든 것을 제공합니다.

웹 구성요소 표준은 HTML 템플릿, 맞춤 요소Shadow DOM. 이를 결합하여 원활하게 통합될 수 있고 맞춤설정된 자체 포함 (캡슐화) 재사용 가능한 요소를 빌드할 수 있습니다. 기존 애플리케이션에 적용할 수 있습니다.

이 섹션에서는 사용자가 서비스에서 환경을 평가할 수 있는 웹 구성요소인 <star-rating> 요소를 만듭니다. 별 1개부터 다섯 개로 표시됩니다. 맞춤 요소의 이름을 지정할 때는 모두 소문자를 사용하는 것이 좋습니다. 또한 대시, 이렇게 하면 일반 요소와 사용자설정 요소를 구분하는 데 도움이 됩니다.

<template><slot> 요소, slot 속성, JavaScript를 사용하여 캡슐화된 Shadow DOM입니다. 그런 다음 정의된 요소를 재사용하여 텍스트 섹션을 맞춤설정한 다음 웹 구성 요소나 웹 구성 요소처럼 말이죠. 맞춤 요소 안팎에서 CSS를 사용하는 방법도 간단히 살펴보겠습니다.

<template> 요소

<template> 요소는 JavaScript로 DOM에 복제하여 삽입할 HTML 프래그먼트를 선언하는 데 사용됩니다. 요소의 콘텐츠는 기본적으로 렌더링되지 않습니다. JavaScript를 사용하여 인스턴스화됩니다.

<template id="star-rating-template">
  <form>
    <fieldset>
      <legend>Rate your experience:</legend>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required />
        <input type="radio" name="rating" value="2" aria-label="2 stars" />
        <input type="radio" name="rating" value="3" aria-label="3 stars" />
        <input type="radio" name="rating" value="4" aria-label="4 stars" />
        <input type="radio" name="rating" value="5" aria-label="5 stars" />
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

<template> 요소의 콘텐츠가 화면에 작성되지 않으므로 <form>와 그 콘텐츠는 렌더링되지 않습니다. 예, 이 Codepen은 비어 있습니다. 하지만 HTML 탭을 살펴보면 <template> 마크업을 확인할 수 있습니다.

이 예에서 <form>는 DOM에서 <template>의 하위 요소가 아닙니다. 오히려 <template> 요소의 콘텐츠는 하위 요소임 (HTMLTemplateElement.content에서 반환한 DocumentFragment의 값) 속성 표시되도록 하려면 자바스크립트를 사용하여 콘텐츠를 가져와 해당 콘텐츠를 DOM에 추가해야 합니다.

이 간단한 JavaScript는 사용자설정 요소를 만들지 않았습니다. 오히려 이 예에서는 <template>의 콘텐츠를 <body>에 추가했습니다. 콘텐츠가 표시되고 스타일을 지정할 수 있는 DOM의 일부가 되었습니다.

DOM에 표시된 이전 코드펜의 스크린샷

JavaScript에 별표 평점 1개에 대한 템플릿을 구현하는 것은 그다지 유용하지 않지만 맞춤설정 가능한 별표 평점 위젯이 유용합니다.

<slot> 요소

맞춤설정된 발생당 범례를 포함하는 슬롯이 포함되어 있습니다. HTML은 <slot>를 제공합니다. 요소를 <template> 내에 자리표시자로 사용합니다. 이름이 제공되면 '이름이 지정된 슬롯'을 생성합니다. 이름이 지정된 슬롯을 사용하면 웹 구성 요소 내에서 콘텐츠를 사용자 지정할 수 있습니다. <slot> 요소를 사용하면 맞춤 하위 요소의 위치를 제어할 수 있습니다. 요소가 섀도우 트리 내에 삽입되어야 합니다.

템플릿에서 <legend><slot>로 변경합니다.

<template id="star-rating-template">
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>

name 속성은 요소에 값이 일치하는 slot 속성이 있는 경우 다른 요소에 슬롯을 할당하는 데 사용됩니다. 이름이 지정된 슬롯의 이름입니다. 맞춤 요소에 슬롯과 일치하는 항목이 없으면 <slot>의 콘텐츠가 렌더링됩니다. 따라서 HTML에 콘텐츠 없이 <star-rating></star-rating>만 포함하더라도 렌더링해도 무방한 일반 콘텐츠가 있는 <legend>를 포함했습니다.

<star-rating>
  <legend slot="star-rating-legend">Blendan Smooth</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Hoover Sukhdeep</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Toasty McToastface</legend>
  <p>Is this text visible?</p>
</star-rating>

slot 속성은 <template><slot>의 콘텐츠를 대체합니다. 맞춤 요소에서 슬롯 속성이 있는 요소는 <legend>입니다. 반드시 그럴 필요는 없습니다. 이 템플릿에서 <slot name="star-rating-legend"><anyElement slot="star-rating-legend">으로 대체됩니다. 여기서 <anyElement>는 모든 요소이며 다른 사용자설정 요소도 될 수 있습니다.

정의되지 않은 요소

<template>에서 <rating> 요소를 사용했습니다. 맞춤 요소가 아닙니다. 알 수 없는 요소입니다. 브라우저 실패하지 않습니다. 인식할 수 없는 HTML 요소를 브라우저에서 익명 인라인으로 처리합니다. CSS로 스타일을 지정할 수 있는 요소입니다. <span>와 마찬가지로 <rating><star-rating> 요소에는 적용된 user-agent가 없습니다. 의미론입니다.

<template> 및 콘텐츠는 렌더링되지 않습니다. <template>는 렌더링되지 않습니다. <star-rating> 요소는 아직 정의되지 않았습니다. 요소를 정의할 때까지 브라우저에서는 알 수 없는 모든 요소가 포함됩니다. 지금은 인식할 수 없는 <star-rating>가 익명 인라인 요소로 취급되므로 콘텐츠는 포함된 범례와 세 번째 <star-rating><p>가 대신 <span>에 있는 경우처럼 표시됩니다.

인식할 수 없는 요소를 맞춤 요소로 변환하도록 요소를 정의해 보겠습니다.

맞춤 요소

맞춤 요소를 정의하려면 JavaScript가 필요합니다. 정의되면 <star-rating> 요소의 콘텐츠가 섀도우 루트(shadow root)를 연결합니다. 템플릿의 <slot> 요소가 대체됨 다음과 같은 경우 slot 속성 값이 <slot>의 이름 값과 일치하는 <star-rating> 내 요소의 콘텐츠로 바꿉니다. 하나가 있습니다. 그렇지 않으면 템플릿 슬롯의 콘텐츠가 표시됩니다.

슬롯과 연결되지 않은 맞춤 요소(세 번째 <star-rating><p>Is this text visible?</p>) 내의 콘텐츠는 따라서 표시되지 않습니다.

star-rating라는 맞춤 요소를 정의합니다. 다음과 같이 HTMLElement를 확장합니다.

customElements.define('star-rating',
  class extends HTMLElement {
    constructor() {
      super(); // Always call super first in constructor
      const starRating = document.getElementById('star-rating-template').content;
      const shadowRoot = this.attachShadow({
        mode: 'open'
      });
      shadowRoot.appendChild(starRating.cloneNode(true));
    }
  });

이제 요소가 정의되었으므로 브라우저가 <star-rating> 요소를 발견할 때마다 정의된 대로 렌더링됩니다. 템플릿인 #star-rating-template가 있는 요소로 바꾸세요. 브라우저는 Shadow DOM 트리를 노드에 연결하고 해당 Shadow DOM에 대한 템플릿 콘텐츠 클론 attachShadow()할 수 있는 요소는 제한적입니다.

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

개발자 도구를 살펴보면 <template><form>가 각 맞춤 요소의 섀도 루트의 일부임을 알 수 있습니다. <template> 콘텐츠의 클론은 개발자 도구의 각 맞춤 요소에서 분명하고 브라우저에서 볼 수 있지만, 콘텐츠는 맞춤 요소 자체는 화면에 렌더링되지 않습니다.

각 사용자설정 요소에서 복제된 템플릿 콘텐츠를 보여주는 DevTools 스크린샷

<template> 예에서는 템플릿 콘텐츠를 문서 본문에 추가하여 일반 DOM에 콘텐츠를 추가했습니다. customElements 정의에서 appendChild()를 실행했지만 복제된 템플릿 콘텐츠가 캡슐화된 Shadow DOM입니다.

별표가 스타일이 지정되지 않은 라디오 버튼으로 돌아왔다는 사실을 눈치채셨나요? 표준 DOM이 아닌 Shadow DOM의 일부이므로 Codepen의 CSS 탭 내 스타일 지정은 적용되지 않습니다. 이 탭은 CSS입니다. 스타일은 Shadow DOM이 아닌 문서로 범위가 지정되므로 스타일이 적용되지 않습니다. 이를 위해서는 캡슐화된 스타일을 사용하여 캡슐화된 Shadow DOM 콘텐츠의 스타일을 지정합니다.

그림자 DOM

Shadow DOM은 CSS 스타일의 범위를 각 섀도우 트리로 지정하여 문서의 나머지 부분과 분리합니다. 즉, 외부 CSS는 가 구성 요소에 적용되지 않으며 구성요소 스타일은 문서의 나머지 부분에 영향을 주지 않습니다. 사용자를 안내할 수 있습니다.

콘텐츠를 Shadow DOM에 추가했으므로 <style> 요소를 포함할 수 있습니다. 사용자설정 요소에 캡슐화된 CSS 제공

맞춤 요소로 범위가 지정되므로 스타일이 문서의 나머지 부분에 스며드는 것에 대해 걱정할 필요가 없습니다. 우리는 선택기의 특이성이 크게 줄어듭니다. 예를 들어 맞춤 요소에 사용되는 유일한 입력은 라디오입니다. input[type="radio"] 대신 input를 선택기로 사용할 수 있습니다.

 <template id="star-rating-template">
  <style>
    rating {
      display: inline-flex;
    }
    input {
      appearance: none;
      margin: 0;
      box-shadow: none;
    }
    input::after {
      content: '\2605'; /* solid star */
      font-size: 32px;
    }
    rating:hover input:invalid::after,
    rating:focus-within input:invalid::after {
      color: #888;
    }
    input:invalid::after,
      rating:hover input:hover ~ input:invalid::after,
      input:focus ~ input:invalid::after  {
      color: #ddd;
    }
    input:valid {
      color: orange;
    }
    input:checked ~ input:not(:checked)::after {
      color: #ccc;
      content: '\2606'; /* hollow star */
    }
  </style>
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required/>
        <input type="radio" name="rating" value="2" aria-label="2 stars"/>
        <input type="radio" name="rating" value="3" aria-label="3 stars"/>
        <input type="radio" name="rating" value="4" aria-label="4 stars"/>
        <input type="radio" name="rating" value="5" aria-label="5 stars"/>
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

웹 구성요소는 <template> 내 마크업으로 캡슐화되고 CSS 스타일은 Shadow DOM으로 범위가 지정되고 숨겨집니다. 구성요소 외부의 모든 항목, 렌더링되는 슬롯 콘텐츠, <anyElement slot="star-rating-legend"> 부분은 캡슐화되지 않습니다.<star-rating>

현재 범위를 벗어난 스타일 지정

Shadow DOM 내에서 문서의 스타일을 지정하고 Shadow DOM의 콘텐츠 스타일을 전역 스타일입니다. Shadow DOM이 종료되고 일반 DOM이 시작되는 섀도우 경계는 순회할 수 있지만 알게 되었습니다.

섀도우 트리는 Shadow DOM 내의 DOM 트리입니다. 섀도우 루트는 섀도우 트리의 루트 노드입니다.

:host 의사 클래스는 섀도우 호스트 요소인 <star-rating>를 선택합니다. 섀도우 호스트는 Shadow DOM이 연결된 DOM 노드입니다. 호스트의 특정 버전만 타겟팅하려면 :host()을 사용하세요. 이렇게 하면 클래스 또는 속성 선택기와 같이 전달된 매개변수와 일치하는 섀도우 호스트 요소만 선택됩니다. 선택 모든 맞춤 요소를 사용하는 경우 전역 CSS에서 star-rating { /* styles */ }를 사용하거나 템플릿 스타일에서 :host(:not(#nonExistantId))를 사용할 수 있습니다. 즉, 구체성을 정의하면 전역 CSS가 우선합니다.

::slotted() 의사 요소가 Shadow DOM 경계를 교차함 섀도우 DOM을 검색합니다. 선택기와 일치하면 슬롯 요소가 선택됩니다. 이 예에서 ::slotted(legend)는 세 가지 범례와 일치합니다.

전역 범위의 CSS에서 Shadow DOM을 타겟팅하려면 템플릿을 수정해야 합니다. part 속성은 스타일을 지정하려는 모든 요소에 추가할 수 있습니다. 그런 다음 ::part() 의사 요소를 사용합니다. 전달된 매개변수와 일치하는 섀도우 트리 내의 요소를 일치시킵니다. 의사 요소의 앵커 또는 원본 요소는 다음과 같습니다. 호스트 또는 맞춤 요소 이름(이 경우 star-rating) 매개변수는 part 속성의 값입니다.

템플릿 마크업이 다음과 같이 시작된 경우:

<template id="star-rating-template">
  <form part="formPart">
    <fieldset part="fieldsetPart">

다음을 사용하여 <form><fieldset>를 타겟팅할 수 있습니다.

star-rating::part(formPart) { /* styles */ }
star-rating::part(fieldsetPart) { /* styles */ }

파트 이름은 클래스와 유사하게 작동합니다. 한 요소는 공백으로 구분된 여러 개의 부분 이름을 가질 수 있고 여러 요소는 같은 부분 이름을 가져야 합니다.

Google에는 맞춤 요소 생성을 위한 유용한 체크리스트가 있습니다. 또한 선언적 Shadow DOM 알아보기

이해도 확인

템플릿, 슬롯, 그림자에 관한 지식을 테스트하세요.

기본적으로 Shadow DOM 외부의 스타일은 내부 요소의 스타일을 지정합니다.

참입니다.
다시 시도하세요.
거짓입니다.
정답입니다.

<template> 요소에 관한 올바른 설명은 무엇인가요?

페이지에 콘텐츠를 표시하는 데 사용되는 일반적인 요소입니다.
다시 시도하세요.
자리표시자 요소입니다.
다시 시도하세요.
HTML의 프래그먼트를 선언하는 데 사용되는 요소로, 기본적으로 렌더링되지 않습니다.
정답입니다.