리액트, 뷰, 앵귤러와 같은 SPA 프레임워크가 주는 최대 이점인 UI의 컴포넌트화를 바닐라 자바스크립트에서도 활용해보자.
웹 컴포넌트는 리액트, 뷰, 앵귤러와 같은 spa 프레임워크에서 제공하는 html코드 조각들을 컴포넌트화 하여 관리하는 것과 같은 기술들을 칭합니다. 리액트의 장점이자 특징 중 하나는 검색바, 헤더, 메뉴, 카드 등의 재사용 가능한 UI조각들을 컴포넌트화 하여 관리하기 용이하게 해준다는 것인데요, 바닐라 자바스크립트에서도 이것이 가능합니다. 바로 웹 컴포넌트 기술을 활용하면 됩니다.
웹 컴포넌트의 가장 큰 장점은 어디에서나 사용할 수 있다는 것입니다. 여러분이 어떤 프레임워크를 사용하고 있는지는 중요하지 않습니다. -vuejs.org
위처럼 vue의 공식 페이지에 올라와 있는 말을 봐도 알 수 있듯, 웹 컴포넌트의 최대 장점은 프레임워크에 종속되지 않고 어디서나 사용할 수 있다는 점입니다. 코드를 부분적으로 캡슐화하는 훌륭한 기능을 제공하고, 거의 모든 브라우저와 웹 애플리케이션에서 사용가능합니다.
이제 웹 컴포넌트를 어떻게 사용하는지 본격적으로 알아보겠습니다. 웹 컴포넌트는 실무적인 관점에서 쉽게 설명하면 커스텀 html태그라고 봐도 무방합니다. 아래처럼 길게 늘어져 있는 코드 블럭(html 태그 블럭들)을 하나의 태그로 명명하여 가독성 좋게 작성할 수 있습니다.
웹 컴포넌트를 만드려면 아래와 같이 class
문법을 사용해야 합니다.
class MyWebComponent extends HTMLElement {...};
window.customElements.define('my-web-component', MyWebComponent);
그리고 html파일에서는 다음과 같이 추가해줍니다.
<my-web-component>
<h1>Hello world!</h1>
</my-web-component>
정리하자면 스크립트 태그 내부 또는 외부 js파일에 우리가 만들고자 하는 커스텀 DOM 요소를 class
키워드를 통해 만들고, customElements.define()
메서드를 통해 해당 클래스를 태그명 작명과 함께 등록한 뒤 사용하고 싶은 곳에 html 태그처럼 쓰면 됩니다.
이제 웹 컴포넌트 클래스 내부를 좀 더 면밀히 살펴보겠습니다.
class MyComponent extends HTMLElement{
constructor() {
super();
/*called when the class is
instantiated
*/
}
connectedCallback() {
/*called when the element is
connected to the page.
This can be called multiple
times during the element's
lifecycle.
for example when using drag&drop
to move elements around
*/
}
disconnectedCallback() {
/*called when the element
is disconnected from the page
*/
}
}
우선 위 코드에서처럼 커스텀 DOM요소를 만들기 위해 만든 클래스는 항상 HTMLElement 클래스를 상속받아야 합니다. 그래야 우리가 이 커스텀 요소를 html tag와 같이 다룰 수 있습니다. 또한 웹 컴포넌트도 클래스이기 때문에 일반 클래스를 다루듯 할 수 있습니다. contructor를 가지고, gettter나 setter도 가집니다.
여기서 주의해서 볼 점은 바로 connectedCallback, disconnectedCallback메서드입니다.
이러한 메서드들은 본 적이 없습니다. 얘네들을 통틀어 웹 컴포넌트 라이프사이클 콜백이라고 부르는데요, 라이프사이클이란 웹 컴포넌트가 생성되고 소멸되기 까지 일정한 주기가 있고, 그 사이에 웹 컴포넌트에게 일어날 수 있는 일들이 정해져 있다는 것입니다. 예를 들면 프로퍼티 값이 변경되거나 하는 일들 말이죠. 참고로 위 코드에 있는 connectedCallback은 컴포넌트가 DOM 트리에 추가되었을 때 트리거 되는 콜백입니다. disconnectedCallback은 반대로 요소가 제거되었을 때 트리거됩니다. 이 두 라이프사이클 콜백을 이용해서 원하는 작업들을 할 수 있습니다. 로그를 남기거나 어떤 상태값을 바꾸는 등의 작업들을 말이죠.
이제 우리의 커스텀 요소에 스타일을 입혀보겠습니다. 기본적인 방법은 다음과 같습니다. this키워드를 활용하면 됩니다.
class MyComponent extends HTMLElement{
connectedCallback() {
this.style.color = 'red';
}
}
다른 방법들도 있습니다. 하지만 그 전에 먼저 Shadow DOM에 대한 이해가 있어야 가능합니다.
shadowDOM이란, 단어 자체에서도 유추할 수 있듯이 숨겨진 DOM트리라는 뜻입니다. 앞서 웹 컴포넌트의 장점이자 특징이 바로 캠슐화라고 했었는데요, 이 캡슐화를 가능하게 해주는 것이 바로 shadow DOM의 활용입니다. html문서에는 shadow DOM이라는 것을 만들어서 기존 DOM에 붙일 수 있습니다.
shadow DOM은 일반 DOM같지만 큰 차이가 있습니다. shadowDOM에서 일어나는 스타일링, 자식의 append, attribute의 설정 등은 외부의 트리에 어떠한 영향도 끼치지 않는다는 점입니다. 그래서 캡슐화를 가능케 하는것이죠.
shadow dom과 관련해 알아야 할 용어들이 몇 가지 있습니다.
그럼 웹 컴포넌트의 캡슐화를 shadow DOM이 어떻게 이루어내는지 살펴보겠습니다.
let shadow = this.attachShadow({mode: 'open'});
다음과 같이 웹 컴포넌트 안에서 shadow DOM을 이용하면 외부 환경과 상관없는 캡슐화를 이룰 수 있습니다. 어떤 환경이든 재사용이 가능한 독립된 컴포넌트가 만들어진 것입니다. 참고로 shadow DOM의 자식 요소들은 외부에서 자식 요소로 간주되지 않습니다. 그래도 요소 안에 요소들을 감싸는 존재인 shadow DOM이 있어 안에서는 자식 요소를 가진 것처럼 보이고, 밖에서는 하나의 요소인것 처럼 보일 수 있습니다.
<!DOCTYPE html>
<html>
<body>
<template>
<h1>Hello Rick!</h1>
</template>
<my-component></my-component>
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
this.addEventListener('click',
() => {
this.style.color === 'red'
? this.style.color = 'blue':
this.style.color = 'red';
});
}
connectedCallback() {
/*called when the element is
connected to the page
*/
this.style.color = 'blue';
const template =
document.querySelector('template');
const clone = document.importNode(template.content, true);
//this.appendChild(clone);
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(clone);
}
}
customElements.define('my-component', MyComponent);
</script>
</body>
</html>
조금 더 자세한 구현 방법은 더 찾아 보면서 학습하시길 권장합니다.
여기까지 웹 컴포넌트란 무엇이고, 대략적으로 어떻게 동작하는지 간단한 예제들로 살펴봤습니다. 웹 컴포넌트의 사용량은 실제로 많이 늘고 있습니다. 웹 컴포넌트가 spa 프레임워크를 대체하기 위한 용도는 아니지만, spa 프레임워크들의 다른 강력한 기능을 사용하는 것이 아닌 그저 컴포넌트화 기능만을 사용하는 것이라면 한 번쯤 프레임워크에서 벗어나 바닐라 자바스크립트로 컴포넌트를 구현해 보아도 좋을 것 같습니다.
참고자료: