Web Component

1. 웹 컴포넌트에 대해

컴포넌트(Component)의 사전적 단어 뜻은 '요소, 부품' 이라는 뜻을 담고 있다.
프로그래밍에서는 컴포넌트는 재사용이 가능한 최소한의 독립된 모듈을 뜻한다.


코드를 짜다 보면 어느 순간에 반복되는 코드들에 대해 실증이 나게 된다.
프론트엔드 부분에서는 같은 HTML,CSS를 공유하지만 다른 데이터를 사용하는 경우가 있다.

중복_코드_예시_이미지
중복_코드_예시_이미지

이 이미지는 Bootstrap을 이용해 카드를 생성한 것이다.
카드를 보면 내용만 같고 HTML,CSS구조는 같은 것을 볼 수 있다.


이러한 부분을 해결하고 싶었고 대표적인 방법은 React.js, Vue.js 등 여러 프론트엔드 프레임 워크를 사용하는 방법이 있다.
프론트엔드 프레임워크들은 컴포넌트를 통해서 중복되는 코드를 재사용하게 하고 상태 관리를 통해 효율적으로 관리를 하게 도와준다.


처음에는 이러한 코드 재사용에 관한 문제를 프레임워크를 통해서 찾으려 했었지만 이러한 프레임워크에 대해 의존성이 높아지지 않고 언어만으로 컴포넌트의 개념을 사용하고 싶었고 역시나 이미 해결법은 있었다.
내가 찾은 방법은 vanilla JS를 통해 코드를 재사용 하는 방법으로 Web Components(웹 컴포넌트)이다.

웹 컴포넌트 로고
웹 컴포넌트 로고


웹 컴포넌트는 재사용 가능한 Custom Element를 생성하고 웹 앱에서 활용이 가능하게 해준다.
아래와 같이 카드를 두가지를 만든다고 할때 위에 파란색제목 카드는 일반적인 코드로 구성한 경우고 아래쪽 빨간색 제목 카드는 웹 컴포넌트를 사용해 코드를 구성한 경우 이다.


예제1
예제1


단순히 코드 자체를 보면 위에 코드처럼 그냥 직접 html에 짜는게 낳을 수 있어 보이지만 만약 이 card라는 컴포넌트가 여러개일 경우는 아래와 같이 짜는게 효율 적이다.

쇼핑몰 사이트를 구성하다고 할때 상품 리스트를 상품하나당 card에 넣어서 한페이지에 50개의 card를 브라우저에 보여진다고 했을 경우 코드의 길이가 몇배로 차이가 나게 된다.
그리고 만약 card의 구조가 바뀌게 된다고 했을때 "웹 컴포넌트" 의 경우 단 한번만 바꾸어주어 모든 card를 바꿀 수 있지만 위의 코드는 card의 개수 만큼 바꾸어 주게 된다.

또한 위의 코드에서는 나오지 않았지만 JavaScript로 반복문으로 백엔드에서 값을 받아오는데로 화면에 넣어 줄 수 있어서 React.js, Vue.js와 같이 상태 관리도 할 수 있다.



2. 웹 컴포넌트 만들기

웹 컴포넌트는 아래 코드와 같이 클래스로 생성한다.

<만든-태그명></만든-태그명>

<script>
class 클래스명 extends HTMLElement{
	connectedCallback(){
		this.innerHTML = `<p>웹 컴포넌트</p>`
	}
}

customElements.define("만든-태그명", 클래스명);
</script>

HTMLElement을 자식으로 갇는 Class를 만들어 준 다음 사용자 정의 요소가 문서의 DOM에 처음 연결될 때 호출되는 connectedCallback()안에 HTML를 삽입하는 코드를 만들면 된다.

그리고 그 클래스를 customElements.define("만든-태그명", 클래스명)로 내가 원하는 태그로 정의해 줄 수 있다.
이제 <만든-태그명></만든-태그명>으로 태그를 정의해서 사용을 할 수 있다.

단, 여기서 커스텀 태그명에는 규칙이 하나 있는데 - (하이픈)으로 이름을 연결하여 사용해야 한다.





파라미터 사용하기

위에서 정의한 커스텀 엘리먼트들에 속성값으로 파라미터를 전달해서 각 컴포넌트마다 다르게 값을 출력 할 수 있다.

<custom-text data-contents="HTML"></custom-text>
<custom-text data-contents="CSS"></custom-text>
<custom-text data-contents="JavaScript"></custom-text>

<script>
  class customClass extends HTMLElement {
    connectedCallback() {
      let content = this.dataset.contents;
      this.innerHTML = `
                <p>전달 값은 ${content} 입니다.</p>`;
    }
  }

  customElements.define("custom-text", customClass);
</script>

예제2
예제2

위 코드는 dataset를 파라미터로 전달하여 dataset에 저장되어 있는 값으로 화면에 출력 하도록 하였다.
이 방식을 이용해서 dataset뿐만 아니라 다른 속성값으로도 값을 전달 할 수 있다.



3. 쉐도우 돔 (ShadowDOM)

웹 컴포넌트를 만들어서 사용하다보면 css가 겹치는 것을 알 수 있다.
컴포넌트 밖이 아닌 컴포넌트안의 코드에다가 스타일을 지정해 주어도 컴포넌트 밖의 코드에도 영향을 주게 된다.
그렇기 때문에 웹 컴포넌트를 캡슐화 하는 것이 필요하다.
ShadowDOM은 이러한 캡슐화의 방법이며, 숨겨지고 분리된 DOM을 엘리먼트에 달아서 해결 할 수 있다.

우선 이 ShadowDOM을 크롬 개발자모드에서 확인하기 위해서는 크롬에서 Show User agent shadow DOM 설정을 해주어야 한다.

예제
예제

이렇게 설정하고 나서 ./src/03-example/03-example1.html를 실행 하고 개발자 모드를 보면 <div>태그 안에 다음과 같이 ShadowDOM안에 <p>태그가 숨겨져 있는 것을 볼 수 있다.

예제1
예제1

결과 화면 또한 Hello로 잘 나오게 된다.


3.1. ShadowDOM 사용하기

ShadowDOM의 사용법은 간단하다.

  1. attachShadow로 open, closed 옵션중 선택
  2. shadowRoot에 내가 넣으려는 HTML을 삽입

attachShadow옵션 open을 사용하면 JavaScript로 ShadowDOM에 대해 액세스 할 수 있게 되고 closed를 사용하면 액세스 할 수 없게 된다. (closed모드에서 shadowRoot를 값을 호출시 null을 반환한다.)


이것에 대한 자세한 설명은 여기에서 확인 할 수 있다.



3.2. ShadowDOM에 HTML, CSS넣기

<p>일반 태그 내용 입니다.</p>
<custom-text></custom-text>

<script>
  class customClass extends HTMLElement {
    connectedCallback() {
      this.attachShadow({ mode: "open" });
      this.shadowRoot.innerHTML = `
                <p>커스텀 태그 내용 입니다.</p>
                <style>p{color:red}</style>
                    `;
    }
  }

  customElements.define("custom-text", customClass);
</script>

웹 컴포넌트를 만드는 법에 ShadowDOM 생성 방법을 넣어서 위의 코드와 같이 HTML,CSS를 넣어 주면 다음과 같이 나오게 됩니다.

예제
예제

<p>태그에 CSS를 주었지만 ShadowDOM에서 넣은 값은 ShadowDOM에만 적용되고 외부의 <p>태그에는 적용되지 않는 모습을 볼 수 있습니다.


3.3. 이벤트 리스너 사용하기

<p>일반 태그 내용 입니다.</p>
<custom-text></custom-text>

<script>
  class customClass extends HTMLElement {
    connectedCallback() {
      this.attachShadow({ mode: "open" });
      this.shadowRoot.innerHTML = `
                <p>커스텀 태그 내용 입니다.</p>
                <style>p{color:red}</style>
                    `;
      this.addEventListener("click", () => alert("알림창 입니다!"));
    }
  }

  customElements.define("custom-text", customClass);
</script>

JS의 이벤트 리스너를 작동 하고 싶을 때는 HTML를 작성할 때와 같이 this.addEventListener으로 작성을 하면된다.

예제
예제


3.4. 템플릿 사용하기

innerHTML을 통해서 값을 넣게 되면 코드의 가독성이 떨어지게 됩니다. template 을 만들어서 이러한 문제를 해결 할 수 있습니다.

<template> 태그에 컴포넌트화할 코드를 작성하고 id값을 지정한 다음에 this.shadowRoot.append(템플릿id이름.content.clonNode(true))의 형식으로 innerHTML처럼 template에 있는 코드를 커스텀 엘리먼트로 화면에 출력 할 수 있습니다.