[JavaScript] Web Components : Custom Elements
기본 설명
Custom Elements는 사용자 HTML Element를 만들게 해준다. 그리고 이는 DOM의 모든 기능을 다 사용할 수 있다.
기본적으로 두 가지 타입으로 생성한다.
- 표준 HTML 요소를 상속하지 않은 Elements. 요소는 상속하지 않지만 HTML Element는 상속 한다.
- 표준 HTML 요소를 상속한 Element.
이렇게 생성한 Element는 Lifecycle callback을 class 안에 정의하여 특정 시점에 동작 하도록 한다.
기본 사용법
독립적인 사용자 정의 요소
1
2
3
4
5
6
7
8
9
10
class MyComponent extends HTMLElement {
constructor() {
// 항상 super를 호출 해야 한다.
super();
this.innerHTML = `<h1>This is my Element</h1>`;
}
}
customElements.define("my-component", MyComponent);
customElements.define에서 정의할 때 아래 사항을 꼭 유념해야 한다.
Name for the new custom element. Note that custom element names must contain a
hypen.
새 커스텀 엘리먼트의 이름입니다. 커스텀 요소 이름에는하이픈이 포함되어야 합니다.
constructor 메서드 안에 this는 customElements를 가리킨다.
<my-element>를 사용해 보면 <h1> element가 생성 되고 This is My Element가 표현 된다.
Built-in 사용자 정의 요소
먼저 상속할 Element를 extends하여 class를 정의 한다.
1
2
3
4
5
6
7
8
class ButtonComponent extends HTMLInputElement {
constructor() {
super();
this.style.border = '1px solid';
this.addEventListener('click', (e) => e.preventDefault());
}
}
정의한 사용자 요소를 등록하빈다. 이때 상속한 Element를 인수로 넘겨 준다.
1
2
3
customElements.define("button-component", ButtonComponent, {
extends: "button",
});
마지막으로 <button> 사용 시 is attribute에 my-button을 사용자 요소로 사용함을 알려 준다.
1
<button is='button-component'>Click</button>
생명 주기 콜백
custom component가 생성되고 삭제 되기 까지 네 가지 상태에 따른 별도의 callback을 정의할 수 있습니다.
connectedCallback: 사용자 정의 요소가 문서에 연결된 요소에 추가될 때마다 호출됩니다. 이것은 노드가 이동 될 때마다 발생할 것이며, 요소의 내용이 완전히 해석되기 전에 발생할 지도 모릅니다.
disconnectedCallback: 사용자 정의 요소가 document DOM에서 연결 해제 되었을 때마다 호출됩니다.
adoptedCallback: 사용자 정의 요소가 새로운 document로 이동 되었을 때마다 호출됩니다.
attributeChangedCallback: 사용자 정의 요소의 특성들 중 하나가 추가되거나, 제거되거나, 변경될 때마다 호출됩니다. 어떤 특성이 변경에 대해 알릴지는 static get
observedAttributes메서드에서 명시됩니다.
위 생명 주기 콜백을 보면 vue, react 등에서 사용했던 componentDidMount, ‘mounted’와 비슷한 모양입니다.
사용법도 아래와 같이 비슷합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
connectedCallback() {
console.log('연결 완료');
}
disconnectedCallback() {
console.log('연결 해제 완료');
}
adoptedCallback() {
console.log('Element가 다른 page로 이동 하였습니다.');
}
attributeChangedCallback(name, oldValue, newValue) {
console.log('name이 변경 되었습니다.');
}
static get observedAttributes() {
// 변경을 관찰하고자 하는 attribute를 나열한다.
// 아래 반환값들이 변경되면 attributeChangedCallback 이 호출된다.
return ['autoFocus', 'disabled', 'form', 'value'];
}
Web Components 사용하기
예를 들어서 <custom-input> 이라고 입력하면
<label><input> 이렇게 2개의 태그가 안에 출현하게 만들고 싶다면 아래와 같이 사용하면 된다.
1
2
3
4
5
6
class InputComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = `<label>이름을 입력하세요.</label><input>`;
}
}
customElements.define('input-component', InputComponent);
1
2
<script type="module" src="components/input-component.js" defer></script>
<input-component></input-component>
HTML 태그를 innerHTML보다 효율적으로 짜고 싶다면
1
2
3
4
5
6
7
8
9
class InputComponent extends HTMLElement {
connectedCallback() {
let label = document.createElement('label');
this.appendChild(label);
this.innerHTML = `<label>이름을 입력해주세요.</label><input>`
}
}
customElements.define('input-component', InputComponent);
위와 방식으로 사용하면 innerHTML보다 html 생성 속도가 10배 빨라진다.
파라미터 문법 구현 가능
1
2
3
4
5
6
7
class InputComponent extends HTMLElement {
connectedCallback() {
let name = this.getAttribute('name');
this.innerHTML = `<label>${name} 인풋입니다.</label><input>`;
}
}
customElements.define('input-component', InputComponent);
1
<input-component name='비밀번호'></input-component>
attribute 변경 감지하기
attribute가 바뀌면, html도 자동으로 업데이트 해줄 수 있다.
name이라는 custom attribute가 변경될 때마다, 특정 코드를 실행할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class InputComponent extends HTMLElement {
connectedCallback() {
let name = this.getAttribute('name');
this.innerHTML = `<label>${name}을 입력하세요.</label><input>`;
}
static get observedAttributes() {
return ['name'];
}
attributeChangedCallback() {
// attribute 변경 시 실행할 코드
this.innerHTML = `<label>${name}이 변경되었습니다.</label><input>`;
}
}
customElements.define('input-component', InputComponent);
사용예시
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Component</title>
<script type="module" src="components/header-component.js" defer></script>
</head>
<body>
<!-- 헤더 컴포넌트 -->
<header-component></header-component>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class HeaderComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<header>
<nav>
<a href="#" id="home-link">Home</a>
<a href="#" id="about-link">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
`;
this.addEventListener('click', (e) => {
e.preventDefault();
console.log('click');
});
}
}
customElements.define('header-component', HeaderComponent);