[Web] Web components

hyeondoonge·2024년 2월 12일
1

개요

웹 컴포넌트는 그 기능을 나머지 코드로부터 캡슐화하여 재사용 가능한 커스텀 엘리먼트를 생성하
고 웹 앱에서 활용할 수 있도록 해주는 다양한 기술들의 모음입니다. - mdn web docs

이는 새로운 기술이 아니고, 브라우저에서 오랜시간 사용해온 기술이라고 한다. 흔히 알고있는 html의 video 또한 이 기술이 활용되었다.

왜 사용할까?

  • 선언적이고 일관적인 코드를 작성하여 UI 구조의 이해를 쉽게함
    ex) HTMLUListElement와 HTMLLIElement를 각각 확장한 웹 컴포넌트 예시
class List extends HTMLUListElement{
  constructor() {
    this.list = ["현정", "율무"]
    this.$list.innerHTML = `
      ${this.list.map((name) => `<my-item name=${name}></my-item>`}`
    }
}

class Item extends HTMLLIElement{
  constructor() {
      this.innerHTML = this.getAttribute("name")     
    }
}
  • 생명주기에 접근할 수 있는 함수들을 제공하여 최적화 가능
  • 외부에서 일부 DOM에 접근하지못하게 캡슐화할 수 있음
  • html element를 재사용할 수 있음

web component를 이루고 있는 구성요소는 오피셜하게, 크게 3가지 custom elements, shadow dom, template and slot가 있는데 얘네가 뭔지 알아보자!

Custom elements: 사용자 정의 요소 및 동작을 정의

customized built in element, autonomus custom elements 두가지 종류로 나뉜다.
이 custom elements이 컴포넌트를 만드는 가장 기본 요소여서, 이것만 알아도 web component기반의 UI를 구축할 수 있을 것이다.

사용자 정의 내장 요소(customized built in element)

  • 상속 대상인 element의 멤버변수, 함수들을 상속받을 수 있게되어 따로 멤버의 관리가 불필요함
  • autonomous element와 달리 실제 DOM에서 상속 대상 요소로 태그가 표시되어 시맨틱함
  • shadowDOM을 붙일 수 없음

// Web Component 정의
class CarouselImage extends HTMLImageElement {
  constructor() {
    super();
  }
  connectedCallback() {
	console.log(`이미지의 규격 width:
		${this.width}, height: ${this.height}`)
  }
}

customElements.define('carousel-image', CarouselImage, { extends: 'img' })

// HTML, Web Component 사용
<img is="carousel-image" src="https://placehold.co/600x400/png" width=300 height=300></img

자율적인 사용자 정의 요소(autonomous custom elements)

  • HTMLElement에서 상속받음
  • shadowDOM을 붙일 수 있음

웹 컴포넌트에 대응될 엘리먼트 태그를 정의하는 함수, Define

spec: define(name, constructor[, options])

  • name: 커스텀 엘리먼트의 이름. 이름 규칙을 따르도록해야함.
  • constructor: 생성자 함수
  • options: 엘리먼트가 정의되는 방법을 관리. 현재 extends 옵션 사용 가능

Life cycle hook, 생명주기훅

  • observedAttributes: 정적 속성. 변경 알림이 필요한 모든 속성의 이름을 포함해야함
  • constructor(): web component 인스턴스 생성 시 호출
  • connectedCallback(): 커스텀 엘리먼트가 다큐먼트의 DOM에 추가될 때마다 호출
  • disconnectedCallback(): 커스텀 엘리먼트가 다큐먼트의 DOM으로부터 연결 해제될 때마다 호출
  • adoptedCallback(): 커스텀 엘리먼트가 새로운 다큐먼트로 이동되었을 때 호출
  • attributeChangedCallback(name, oldValue, newValue): 커스텀 엘리먼트의 어트리뷰트가 추가, 제거 또는 교체되었을 때 호출

사용자 정의 상태 의사 클래스의 사용(experimental)

  • 해당 기술은 널리 지원되고있지는 않아, 그냥 이런게 있구나~하면될 것 같음
    - 사파리에서 지원하지 않아 오류가 발생할 수 있음. 또한 typescript에서는 ElementInternals의 states 속성에 대한 타이핑이 지원되지 않고있음.
  • automous 사용자 정의 요소 사용시, 상태를 정의하고 활용할 수 있음.
  • ElementInternals 타입의 객체내부에 상태가 정의되며, 이 상태를 css의 의사클래스처럼 사용할 수 있음.

Shadow Dom

캡슐화된 DOM트리를 첨부. 관련 기능을 제어할 수 있는 API 제공 ⇒ 문서 내 다른 요소와의 충돌을 방지할 수 있음

  • dom에 새로 부착하게되면, 완전히 다른 돔과는 독립적인 요소를 다룰 수 있게되는 기술.

  • DOM에서는 #shadow root로 접혀진 상태로 보임
  • Shadow DOM 내부의 어떤 코드도 외부에 영향을 줄 수 없음. 이 반대도 동일함
  • Shadow DOM의 mode를 open 또는 closed 설정을 통해 접근제어를 할 수 있음.
    • open일 때, 섀도 호스트의 shadowRoot 속성을 통해 접근가능해짐
    • closed 일 때, shadowRoot가 null로 반환됨
<div id="host">
</div>

const $host = document.querySelector("#host")
const shadow = $host.attachShadow({ mode: "open" }) // shadow tree
const $node = document.createElement("div")
$node.innerHTML = 'hidden'
shadow.appendChild($node)

아래와 같이 html의 template을 이용해 선언적으로 정의할 수도 있음.

<div id="host">
  <template shadowrootmode="open"> // 섀도 루트를 렌더링하는 의미이고, 실제 DOM에는 표시되지않음.
    <span>I'm in the shadow DOM</span>
  </template>
</div>

💄 shadow dom에 스타일 설정하기

아래의 방식처럼 shadow dom에 정의된 스타일은 다른 외부 요소에 영향을 미치지 않음. 반대로 외부 스타일이 Shadow DOM 내부 요소에 영향을 주지 못함.
ex) tailwind의 스타일이 shadow dom 내부의 요소에 적용되지 않음

스타일을 설정하는 방식에는 두 가지 방식이 있으며, 둘 중 애플리케이션 성격 등 개인의 취향에 따라 선택하면 됨. 정답은 없다!

  • CSSStyleSheet 객체를 생성하여 섀도 루트에 첨부
  • template 요소의 선언에 스타일 요소를 추가

Template, Slot

<template> <slot> 마크업 템플릿 작성, 렌더링된 페이지에는 표시되지 않음. 말 그대로 템플릿, 문서 내 여러 곳에서 재사용할 수 있음

동일한 마크업 구조가 반복해서 재사용된다면, 이때 템플릿 기술을 html 재사용에 활용할 수 있음

Template

1. template 선언

<template id="#my-paragraph">
  <p>My paragraph</p>
</template>

2. template 사용

const $paragraphTemplate = document.getElementyById("my-paragraph")
const content = $paragraphTemplate.content
const $container = document.createElement("div")
$container.append(content.clondNode(true))

Slot

  • template을 유연하게 만든 버전? declarative하게 다른 인자들을 전달하여 재사용 달성가능
  • name attribute로 식별되며, placeholder를 정의하여 틀은 유지하면서도 변하는 인풋을 넣을 수 있음
  • template과 함께쓰는 예제가 많은데, 단독으로도 사용가능함.

설명만 봐서는 이해가 가지않을 수 있다...😅 아래 예제를 보며 이해해보자. 이는 아주 간단한 사례로서, 프로필 유저를 보여주는 컴포넌트와 같이 아이디, 이미지 소스와 같은 데이터만 다르게 해서 보여줘야될 경우에 이러한 template, slot을 활용할 수 있다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <script>
      customElements.define(
        "user-profile",
        class extends HTMLElement {
          constructor() {
            super();
            const template = document.querySelector("#profile-template");
            const shadowRoot = this.attachShadow({ mode: "open" });
            shadowRoot.appendChild(template.content.cloneNode(true));
          }
        }
      );
    </script>
  </head>
  <body>
    <!-- template -->
    <template id="profile-template">
      <style>
        div {
          padding: 10px;
        }
        #default {
          color: grey;
        }
      </style>
      <div>
        <strong>유저 아이디</strong>
        <slot name="id">user id</slot>
      </div>
      <div>
        <strong>프로필 이미지</strong>
        <slot name="profile-image">
          <span id="default">* 자신을 대표하는 이미지를 추가해보세요!</span>
        </slot>
      </div>
      <div>
        <strong>소개</strong>
        <slot name="description">
          <span id="default">* 자신에 대한 설명을 추가해보세요!</span>
        </slot>
      </div>
    </template>
    <!-- declare user-profile element -->
    <user-profile>
        <span slot="id">hyeondoonge</span>
        <span slot="description">반갑습니다</span>
    </user-profile>
    <div style="height: 1px; background-color: rgb(74, 74, 74);"></div>
    <user-profile>
        <span slot="id">migu</span>
        <img style="width: 200px; display: block" slot="profile-image" src="https://flexible.img.hani.co.kr/flexible/normal/960/960/imgdb/resize/2019/0121/00501111_20190121.JPG"></img>
    </user-profile>
  </body>
</html>
  • 아래는 위 HTML을 동작시켰을 때 만들어지는 DOM임
  • user-profile의 내부 요소들이 slot을 참조하고있는데, 실제 slot이 해당 요소들로 대체됨. 만약 대체될 내용이 없다면 그대로 slot의 기본내용을 그려냄.

  • 실제로 돌려보면 아래와 같이 출력됨.

장단점

장점

  • 캡슐화, 재사용을 가장 큰 장점으로 내세우고 있는 기술
  • 선언적으로 사용가능한 생명주기 훅들이 제공이 되기도해서, 복잡한 로직없이 이벤트 제거, 구독 제거 등에 활용할 수 있음
  • 웹 표준 기술로서 서로 다른 프레임워크를 사용하는 프로젝트가 있다하더라도, 그들 간에 재사용 가능함.
  • 최초 사용 전 define해주기만하면, 이후에 사용시 컴포넌트 코드를 import할 필요가 없음. 수많은 import 로직이 사라짐

단점

  • tag명에 정의되지않은 요소이름을 작성하는 에러가 있어도 잡아주지 않고, 존재하지않는 태그로 취급함
  • 배포된 기능 중, 일부 브라우저에서만 지원되는 것들이 있다는 것 ex. customized elements
  • shadow dom의 경우 난무하게될 경우, seo가 저하될 수 있음
  • shadow dom 사용 시, 외부 스타일을 로드하는 과정에서 FOUC(flash of unstyled content)가능성
  • shadow DOM 사용 시, 전역 스타일의 적용이 불가능해짐.

참고

https://developer.mozilla.org/en-US/docs/Web/API/Web_components
https://github.com/mdn/web-components-examples

0개의 댓글

관련 채용 정보