본문 바로가기
Frontend/JavaScript

JavaScript : 웹 컴포넌트(Web Component)란?

by 코딩쥐 2024. 7. 31.

웹 컴포넌트

기능을 캡슐화 시켜서 재사용이 가능한 하나의 커스텀 엘리먼트를 생성하여 웹에서 활용할 수 있도록 한다. 웹 컴포넌트의 경우 순수 자바스크립트에서 사용이 가능하다는 장점이 있어 높은 상호운용성을 가지고 있다. 웹 컴포넌트는 세 가지 주요 기술로 구성된다. 

  • Custom element
    사용자 정의 요소 및 동작을 사용자가 원하는 대로 정의할 수 있는 JavaScript API 이다. 

  • Shadow DOM
    메인 document DOM으로부터 독립적으로 렌더링 되어 캡슐화된 DOM트리를 추가 및 제어할 수 있다.


  • HTML 템플릿 
    <template>과 <slot> 엘리먼트를 사용하여 렌더링 된 페이지에 나타나지 않은 마크업 템플릿을 작성할 수 있다.

Cutom Element

아래의 명령어를 사용하면 해당 태그 이름으로 커스텀 엘리먼트가 등록이 된다. 뒤에 들어갈 클래스(상속받을 클래스)의 경우 반드시 HTMLElement(DOM tree에서 모든 엘리먼트의 부모에 해당하는 인터페이스)를 상속받은 클래스가 들어가야 한다. 사용할 태그명의 경우에는 이미 쓰이는 태그명은 사용할 수 없으며, 중간에 '-'가 반드시 들어가야 한다. 

  • window.customElements.define('사용할-태그명', class extends HTMLElement{});

 

라이프 사이클 메서드

커스텀 엘리먼트의 클래스 정의 내에 정의되어 자신의 생명주기(life-cycle)동안 동작에 영향을 주는 콜백함수이다.

메서드 설명
connectedCallback 커스텀 엘리먼트가 Document가 DOM에 연결되었을 때 호출
disconnectedCallback 커스텀 엘리먼트가 Document의 DOM으로부터 연결 해제 시 호출 
adoptCallback 커스템 엘리먼트가 새문서로 이동할 때 호출 
attributeChangedCallback( attrName, oldVal, newVal) 커스텀엘리먼트의 속성 중 하나가 추가, 제거, 변경될 때 호출 
static get observedAttributes() 모니터링 할 속성을 정의할 때 쓰며 관찰하기를 원하는 속성 이름을 포함하는 배열을 return해야 한다.

 

아래 예제에서 커스텀 엘리먼트가 DOM에 연결되었을 때 connectedCallback함수가 호출되면서, 그 안에 있는 메서드도 같이 호출된 것을 볼 수 있다. 태그 삭제를 클릭하면 커스텀 엘리먼트가 삭제되면서 DOM으로 부터 연결이 해제되어 disconnectedCallback이 시행된 것을 볼 수 있다. 속성 삭제를 클릭하면 get observedAttributes()가 관찰하는 속성인 "title"이 변경되면서 attributeChangedCallback()이 시행된 것을 볼 수 있다.

 

See the Pen 웹컴포넌트 by coding-ji (@coding-ji) on CodePen.

 

 

Shadow DOM

Custom element만을 가지고 사용하게 되면, 외부의 스타일 시트의 영향을 그대로 받으며 외부의 접근이 허용된다. Shadow DOM을 이용하여 메인 페이지와 분리하여 충돌하지 않고 기본 DOM 구조에 별도로 첨부할 수 있는 독립된 범위가 지정된 DOM 트리를 만들 수 있다. 

  • document.querySelector([selector]).attachShadow({mode : 'open'});
    shadow DOM을 적용시키고 싶은 root element에 attachShadow 옵션을 {mode:'open'}으로 작성한 후에 shadow DOM을 사용할 수 있다. 

  • document.querySelector([selector]).shadowRoot
    Shadow tree의  root 노드에 접근해서 shadowRoot에 요소를 추가할 수 있다.

range타입인 input의 경우 shadow DOM이 있는 모습을 볼 수 있다. shadow DOM을 통해 외부의 스타일 시트에 영향을 받지 않고 독립적인 스타일을 가질 수 있다. 

 

Shadow DOM 용어 정리

  • Shadow host
    shadow DOM이 부착되는 통상적인 DOM 노드

  • Shadow tree
    shadow DOM 내부의 DOM 트리

  • Shadow boundary
    shadow DOM이 끝나고, 통상적인 DOM이 시작되는 장소

  • Shadow root
    shadow 트리의 root 노드

<< Shadow DOM 예제>>

shadow DOM을 열어서 그 안에서 생성이 된 경우에는 외부의 스타일에 영향을 받지 않는 것을 볼 수 있다.  shadow DOM 내에서 스타일을 설정하여, 독립적으로 사용할 수 있는 모습을 볼 수 있다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 100px;
            height: 100px;
            border: 1px solid black;
            background-color: lightcoral;
        }
    </style>
</head>

<body>
    <div id="container"></div>
    <script>
        // shadow DOM 열기
        container.attachShadow({ mode: "open" });

        // div를 만들어서 shadowRoot에 삽입
        let div = document.createElement("div");
        container.shadowRoot.append(div);

        // style을 만들어서 shadowRoot에 삽입
        let style = document.createElement("style");
        style.textContent = `
        div{
        width: 50px;
        height: 50px;
        border: 1px solid lightgray;
        background-color : lightblue;
        }
        `;
        container.shadowRoot.append(style);
    </script>
</body>

</html>


HTML 템플릿

<template>태그

재사용이 가능한 마크업 태그를 생성할 때 사용하는 태그로, <template>태그 안의 태그들은 웹 브라우저가 출력될 때 화면에 보이지 않는다. 해당 <template>를 가져와서 웹 컴포넌트에서 사용이 가능하다. 

  • 템플릿id.content.cloneNode(true)

템플릿만 작성했을 때는 웹 브라우저에 출력이 되지 않는다. 템플릿을 복사(cloneNode(true))를 하고 집어넣으면 사용이 가능하다.

 

<< template 예제>>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="div1"></div>
    <template id="temp1">
        <div>
            <h1>안녕하세요</h1>
            <p>코딩쥐 티스토리입니다.</p>
        </div>
    </template>
    <script>
    //temp1의 내용을 복제하여 div1에 집어넣었다. 
       div1.append(temp1.content.cloneNode(true));
    </script>
</body>
</html>

 

<slot>태그

<slot>은 shadow DOM 사용자가 향후 컨텐츠를 채워넣을 수 있다. <slot> 속성에 name을 설정하고, 사용자가 요소에 해당 name의 slot 속성을 지정하여 <slot>태그 안에 요소를 할당한다. 참고로 slot 안에 default 값을 작성해도, 값이 새로 할당되면 할당된 값으로 변경된다.

  •  <slot name="이름"></slot>
    slot태그에 name을 설정한다. 

  • <태그 slot="이름></태그>
    사용자가 요소를 만들고, 그 안에 slot 속성을 지정한다. 요소를 넣고 싶은 해당 <slot>의 name을 작성한다. 


<< slot 예제>>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <custom-elem>
        <span slot="name">코딩쥐</span>
        <span slot="web">https://coding-ji.tistory.com/</span>
    </custom-elem>
    <template id="temp1">
        <h2>Name : </h2>
        <slot name="name"></slot>
        <h2>Webpage : </h2>
        <slot name="web"></slot>
    </template>
    <script>
        customElements.define("custom-elem", class extends HTMLElement{
            connectedCallback(){
                this.attachShadow({mode: "open"});
                this.shadowRoot.append(temp1.content.cloneNode(true));
            }
        });
    </script>
</body>

</html>