WEB Component는 기능별로 재사용 가능한 코드 블록을 캡슐화하여 커스텀 엘리먼트를 생성하고 웹, 앱에서 활용할 수 있도록 해주는 다양한 기술들의 모음이라고 MDN에서 정의하고 있습니다. MDN 정의를 읽으면서 React에서 사용하는 Component 개념이 떠올랐다. 매우 비슷한 개념이지만 Web Component와 React Component는 부분 집합 관계로 이루어져 있습니다. WEB Component 개념에 React Component 개념이 포함되어 있습니다.
컴포넌트는 독립적인 소프트웨어 모듈로써, 소프트웨어 시스템에서 독립적인 업무 또는 독립적인 기능을 수행하는
모듈
로서 이후 시스템을 유지보수 하는 데 있어 교체 가능한 부품입니다.
W3C에서 제한적인 HTML Element의 한계를 개선하기 위해서 Custom Element를 만드는 기술인 웹 컴포넌트 표준 및 명세를 만들었습니다. 웹 컴포넌트의 개념이 MDN에 비해서 조금 모호한 느낌을 받아서 간단한 예시를 보여 드리겠습니다.
<div>{현재 시간}</div> // HTML 엘리먼트에서는 현재 시간을 의미하는 엘리먼트가 존재하지 않습니다.
<current-time>{현재 시간}<current-time> // 개발자가 직접 커스텀 엘리먼트를 만들어서 확실한 의미를 부여할 수 있습니다.
이러한 한계로 인해 Web Component라는 개념이 탄생하게 되었습니다.
사실 웹 컴포넌트는 이전부터 존재했던 개념이었습니다. 2012년에 웹 컴포넌트 - NAVER D2에 대해서 상세한 설명을 하였습니다. 그렇다면 많은 프레임워크 컴포넌트가 존재하는데 Web Component가 이슈화되었을까요?
‘Google I/O 2016’에서 ‘#UseThePlatform’ 키워드로 웹 표준에 대한 중요성을 각인시켜주었습니다.
프레임워크들은 다양한 문제를 해결할 강력한 도구이지만 무거운 소스코드는 앱을 무겁게 만들고 리소스를 사용자에게 전가 시키며 프레임워크 종속적인 코드를 생산합니다.
그러한 문제들을 프레임워크 대신 브라우저 기능을 사용하여 해결하면 프레임워크를 가볍게 만들어도 되고 더 적은 자바스크립트 코드를 사용하게 되어 표준 코드로 만든 성능 좋은 Awesome APP! 이 된다는 결론입니다.
구글의 #UseThePlatform 키워드는 프레임워크로 무거운 앱을 만들지 말고 만들려는 기능을 표준대로 코딩하자는 의미를 내포하고 있는 거 같습니다. 이러한 상황을 보면서 구글의 영향력이 개발자들한테 매우 큰 바람을 일으키는 거 같다는 느낌도 받게 되었습니다!!
기존의 HTML 요소 외에 사용자 인터페이스에서 원하는 대로 사용할 수 있는 사용자 정의 요소 및 해당 동작을 정의할 수 있는 JavaScript API 세트입니다.
HTML5 엘리먼트가 지원하지 않는 사용자 Custom Elements를 만들 수 있습니다.
<!DOCTYPE html>
<html>
<body>
<div class="layout-container">
<!-- html 문법에서는 의미를 가지지 않는 div태그로써 layout에 대한 정보를 명확하게 알 수 없습니다. -->
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<layout-container>
<!-- HTML5에 속한 Elemen에서는 div로 의미 없는 Element를 만들었지만 -->
<!-- Custom Elements를 사용하면 의미가 정확한 Element를 만들 수 있습니다. -->
</layout-container>
</body>
</html>
HTML 엘리먼트와 JavaScript Class를 한 몸으로 만들어서 Custom Elements의 Life Cycle을 조작할 수 있습니다.
class LayoutContainer extends HTMLElement {
constructor() {
// 클래스 초기화. 속성이나 하위 노드는 접근할 수는 없습니다.
super();
}
static get observedAttributes() {
// 모니터링 할 속성 이름
return ["layout-container"];
}
connectedCallback() {
// DOM에 추가되었다. 렌더링 등의 처리.
}
disconnectedCallback() {
// DOM에서 제거되었다. 엘리먼트를 정리하.
}
attributeChangedCallback(attrName, oldVal, newVal) {
// 속성이 추가/제거/변경되었다.
// class에서의 this는 Custom Elements의 인스턴스를 가르키고 있습니다.
this[attrName] = newVal;
}
}
// <layout-container> 태그가 LayoutContainer 클래스를 사용하도록 연결하는 역할입니다..
customElements.define("layout-container", LayoutContainer);
HTML5에 속한 엘리먼트를 상속받아서 Custom Elements를 만들 수 있습니다.
class LayoutContainer extends HTMLDivElement {
...
}
customElements.define('layout-container', LayoutContainer, {extends: 'div'});
Custom Elements의 Tag 이름을 작성할 때는 예약어 및 업데이트 예정인 요소와 이름 충돌을 막기 위해서 케밥 표기법(-)을 사용해야 합니다.
<iframe>
을 통해서 스코프를 독립적으로 관리할 수 있었지만, 최선이 아니었습니다.const $publicContainer = document.createElement('div');
document.body.appendChild($publicContainer).innerHTML =
'<style>div { background-color: #82b74b; }</style><div>Public Layout</div>';
const $privateContainer = document.createElement('div');
document.body
.appendChild($privateContainer)
.attachShadow({ mode: 'open' }).innerHTML =
'<style>div { background-color: #ccc; }</style><div>Private Layout</div>';
attachShadow({ mode: 'open' })
를 사용하여 글로벌 스코프에서 간단하게 분리할 수 있습니다. 다른 방식으로는 슬롯 조합을 이용하는 방식도 존재합니다. 슬롯 조합은 <slot name="blabla">
을 사용하 <Element slot='blabla'>
서로를 연결하는 조합입니다.
- 쉐도우 돔: 아래의 코드에서 h1, p등 쉐도우 루트에 붙어있는 DOM
- 쉐도우 루트:
#shadow-root
- 쉐도우 호스트: 쉐도우 루트의 부모. 아래의 코드에서
div#slot-test
- 라이트 돔: 도큐먼트의 쉐도우 호스트에 붙어있는 노드들. 아래 코드에서
span
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="test-slot">
<!-- Light DOM -->
<span slot="title">Hello</span>
<span slot="desc">world</span>
</div>
<script>
// Shadow DOM
document
.querySelector('#test-slot')
.attachShadow({ mode: 'open' }).innerHTML = `
<h1>
<slot name="title"></slot>
</h1>
<p>
<slot name="desc"></slot>
</p>
`;
</script>
</body>
</html>
<template>
과 <slot>
엘리먼트는 렌더링 된 페이지에 나타나지 않는 마크업 템플릿을 작성할 수 있게 해줍니다. 그 후, 커스텀 엘리먼트의 구조를 기반으로 여러 번 재사용할 수 있습니다.
<section class="main-container">
<!-- Template Element Area -->
</section>
<template id="list-container">
<ul class="items">
<li class=item>
<!-- value -->
</li>
</ul>
</template>
<script>
const $mainContainer = document.querySelector('.main-container');
const $template = document.getElementById('list-container');
const $clone = $template.importNode($template.content, true);
const $li = $clone.querySelector('.item');
$li.textContent = 'Hello Template Element';
$mainContainer.appendChild($clone);
</script>
Template Element와 Custom Elements를 컴포넌트로 구성하려면 HTML, JS 두 개의 파일이 필요하다는 단점으로 인해 현재 Template Element의 Web Component에서의 영향력이 떨어지고 있는 상태입니다.
글을 마치기 전에 작성하지 않은 부분에 대해서 말씀드리겠습니다. W3C 표준 명세에서는 Web Component 스펙으로 3가지가 아닌 4가지가 공표하고 있습니다. HTML Import라는 하나의 표준이 더 존재하지만 제가 포스터에서 작성하지 않은 이유가 있습니다.
<link>
가 CSS 적용을 위한 <link>
유사하며, 차라리 EM Module을 사용하는 방식이 깔끔하다고 생각합니다.이러한 단점으로 인해서 HTML Import는 MDN에서도 보이지 않고 있으며 다른 대책으로 Web Component를 만드는 현상이 발생하고 있습니다. (lit-html을 사용하는 경우가 많습니다).
현재 Web Component는 여러 충돌이 있었지만 빠르게 발전하고 있으며 Google에서는 polymer라는 라이브러리도 발표하며 많은 사람에게 영향을 주고 있습니다. 10년 후에는 React, Vue, Angular 등의 프레임워크(라이브러리)를 사용하지 않으며, 순수 JS로 코딩을 하는 날이 오지 않을까 하는 제 생각이 반영되어 블로그를 작성하게 되었습니다. (미리미리 준비하는 자세!!)
또한 현재 대부분의 개발은 React를 사용하고 있으며 React의 Virtual DOM 개념에 대해서 모호하게 알고 있다고 생각하고 있었습니다. 이번 Web Component 블로그를 작성하면서 React Component 및 Virtual DOM이 어떻게 만들어지고 있는지에 대해서 생각을 정리할 수 있는 시간이었습니다.