웹 컴포넌트는 <hello-world></hello-world>
와 같은 커스텀 HTML 엘리먼트이다. 이름는 HTML 사양에서 공식적으로 지원되는 요소와 충돌하기 않도록 데시(-)가 꼭 포함되어야 한다.
엘리먼트를 컨트롤하기 위해서는 ES2015 class 를 정의해야 한다. 이름은 어떤 것으로든 지정할 수 있지만 HelloWorld는 연습용입니다. 모든 HTML 요소의 기본 속성과 메서드를 나타내는 HTMLElement 인터페이스를 확장해야 합니다.
이 예에서 요소의 텍스트는 'Hello World'로 설정됩니다.
클래스를 특정 요소에 대한 핸들러로 정의하려면 CustomElementRegistry에 클래스를 등록해야 합니다.
customElements.define('hello-world', HelloWorld);
브라우저는 이제 JavaScript가 로드될 때 <hello-world>
엘리먼트와 HelloWorld 클래스를 연결합니다.
이 컴포넌트는 다른 일리먼트와 마찬가지로 CSS 로 스타일링할 수 있습니다.
hello-world {
front-weight: bold;
color: red;
}
이 컴포넌트는 항상 같은 텍스트를 출력하기 때문에 유용하지 않습니다. 다른 일리먼트와 마찬가지로 HTML attributes를 추가할 수 있습니다.
<hello-world name="Craig"></hello-world>
text를 override 하여 "Hello Craig!"를 보여주려고 한다.
HelloWorld 클래스에 constructor() 함수를 추가한다. constructor()는 각 객체가 생성될 때마다 실행한다.
1. super() 함수를 호출하여 부모 HTMLElement를 초기화하고
2. 다른 초기화를 만든다. 이번 케이스에선, 우리는 "World"로 설정된 name 속성을 정의할 것이다.
class HelloWorld extends HTMLElement {
constructor() {
super();
this.name = 'World';
}
...
}
너의 컴포넌트는 name 속성에만 관심이 있습니다. static observedAttributes() 속성은 관찰할 속성 배열을 반환해야 합니다.
// component attributes
static get observedAttributes() {
return ['name'];
}
attributeChangedCallback() 메서드는 속성이 HTML에 정의되거나 JavaScript를 사용하여 변경될 때 호출됩니다. 속성 이름, 이전 값, 새 값이 전달됩니다.
// attribute change
attributeChangedCallback(property, oldValue, newValue) {
if (oldValue === newValue) return;
this[property] = newValue;
}
이 예에서는 name 속성만 업데이트되지만 필요에 따라 속성을 추가할 수 있습니다.
마지막으로, connextedCallback() 메서드에서 메세지를 수정해야 합니다.
connectedCallback() {
this.textContent = `Hello ${this.name}!`;
}
브라우저는 웹 구성 요소의 생명 주기 동안 6가지 메서드를 자동으로 호출합니다. 위의 예에서 처음 4개를 이미 보았지만 여기 전체 목록이 있습니다.
document.querySelector('hello-world').setAttribute('name', 'Everyone');
웹 컴포넌트는 JavaScript 프레임워크에서 찾을 수 없는 몇 가지 고유한 기능을 제공합니다.
위에서 구축한 웹 구성 요소가 작동하는 동안 외부 간섭에 영향을 받지 않으며 CSS 또는 JavaScript가 수정할 수 있습니다. 마찬가지로 구성 요소에 대해 정의한 스타일이 누출되어 다른 사람에게 영향을 줄 수 있습니다.
The Shadow DOM
을 이용하여 웹 컴포넌트에 분리된 DOM을 첨부하여 이 문제를 해결합니다.
const shadow = this.attachShadow({ mode: 'closed' });
모드는 다음과 같습니다.
Shadow DOM은 다른 DOM요소처럼 조작할 수 있습니다.
connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>
p {
text-align: center;
font-weight: normal;
padding: 1em;
margin: 0 0 2em 0;
background-color: #eee;
border: 1px solid #666;
}
</style>
<p>Hello ${ this.name }!</p>`;
}
이제 구성요소가 <p>
요소 내부의 "Hello" 텍스트를 렌더링하고 스타일을 지정합니다. 글꼴 및 색상과 같은 일부 스타일은 명시적으로 정의되지 않았기 때문에 페이지에서 상속되지만 컴포넌트 외부의 JavaScript 또는 CSS로 수정할 수 없습니다.
CSS :host
선택자는 <hello-world>
웹 컴포넌트 내에서 외부 요소의 스타일링을 지정할 수 있습니다.
:host {
transform: rotate(180deg);
}
요소가 특정 클래스를 사용할 때 적용할 스타일을 설정할 수도 있습니다. 예시 <hello-world class="rorate90">
:
:host(.rorate90) {
transform: rotate(180deg);
}
스크립트 내에서 HTML을 정의하는 것은 더 복잡한 웹 구성 요소에 대해 비실용적일 수 있습니다. 템플릿을 사용하면 웹 컴포넌트에서 사용할 수 있는 HTML 청크를 페이지에 정의할 수 있습니다. 여기에는 몇 가지 이점이 있습니다.
템플릿은 <template>
태그에 정의되며 컴포넌트 클래스 내에서 참조할 수 있도록 ID를 할당하는 것이 실용적입니다.
<template id="hello-world">
<style>
p {
text-align: center;
font-weight: normal;
padding: 0.5em;
margin: 1px 0;
background-color: #eee;
border: 1px solid #666;
}
</style>
<p class="hw-text"></p>
<p class="hw-text"></p>
<p class="hw-text"></p>
</template>
웹 컴포넌트 클래스는 이 템플릿에 액세스하고 해당 콘텐츠를 가져오고 요소를 복제하여 사용되는 모든 곳에서 고유한 DOM 조각을 생성할 수 있습니다.
const template = document.getElementBy('hello-world').content.cloneNode(true);
DOM을 수정하고 Shadow DOM에 직접 추가할 수 있습니다.
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
template = document.getElementById('hello-world').content.cloneNode(true),
hwMsg = `Hello ${ this.name }`;
Array.from( template.querySelectorAll('.hw-text'))
.forEach(n => n.textContent = hwMsg);
shadow.append(template);
}
슬롯을 사용하여 템플릿을 커스텀할 수 있습니다. <hello-world>
웹 컴포넌트를 사용하고 싶지만 <h1>
을 Shadow DOM 안에 넣는다면 다음과 같이 코드를 작성해야 합니다.
<hello-world name="Craig">
<h1 slot="msgtext">Hello Default!</h1>
</hello-world>
선택적으로 다른 단락과 같은 다른 요소를 추가할 수 있습니다.
<hello-world name="Craig">
<h1 slot="msgtext">Hello Default!</h1>
<p>This text will become part of the components.</p>
</hello-world>
이제 템플릿 내에서 슬롯을 구현할 수 있습니다.
<template id="hello-world">
<slot name="msgtext" class="hw-text"></slot>
<slot></slot>
</template>
"msgtext"로 설정된 <h1>
요소는 name이 "msgtext"인 <slot>
이 있는 지점에 삽입됩니다. <p>
요소는 slot name이 할당되지 않았지만 unnamed <slot>
에서 사용됩니다. 실제로 템플릿은 다음과 같습니다.
<template id="hello-world">
<slot name="msgtext" class="hw-text">
<h1 slot="msgtext">Hello Default!</h1>
</slot>
<slot>
<p>This text will become part of the component.</p>
</slot>
</template>
실제로는 이렇게 간단하지 않습니다. <slot>
Shadow DOM의 요소는 삽입된 요소를 가리킵니다. 내부 자식의 배열을 반환하는 .assignedNodes()
메서드를 사용해야만 <slot>
을 찾아야만 엑세스할 수 있습니다.
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
hwMsg = `Hello ${ this.name }`;
// append shadow DOM
shadow.append(
document.getElementById('hello-world').content.cloneNode(true)
);
// find all slots with a hw-text class
Array.from( shadow.querySelectorAll('slot.hw-text') )
// update first assignedNode in slot
.forEach( n => n.assignedNodes()[0].textContent = hwMsg );
}
게다가, 웹 컴포넌트 내의 특정 슬롯을 대상으로 지정할 수 있지만 삽입된 요소의 스타일링을 직접 지정할 수는 없습니다.
<template id="hello-world">
<style>
slot[name="msgtext"] { color: green; }
</style>
<slot name="msgtext" class="hw-text"></slot>
<slot></slot>
</template>
템플릿 슬롯은 조금 특이하지만 한 가지 이점은 JavaScript가 실행되지 않는 경우 콘텐츠가 표시된다는 것입니다. 이 코드는 웹 컴포넌트 class가 성공적으로 실행될 때만 대체되는 기본 머리글과 단락을 보여줍니다.