Skip to content

08. Web Components: Custom Elements·Shadow DOM

Web Components는 "프레임워크를 버리자"는 구호가 아니라, 브라우저 수준에서 재사용 가능한 UI 경계를 만들 수 있다는 점에서 가치가 있다.

학습 목표

  1. Custom Elements, Shadow DOM, <template>, <slot>의 역할을 설명할 수 있다.
  2. 커스텀 엘리먼트 생명주기와 이벤트 경계를 이해한다.
  3. ElementInternals를 통한 폼 연동 개념을 이해한다.
  4. Web Components가 잘 맞는 경우와 그렇지 않은 경우를 구분할 수 있다.

1. Web Components는 세 가지 축으로 이해하면 된다

이 네 가지를 한꺼번에 세세히 외우기보다, "브라우저 기본 기능만으로도 재사용 컴포넌트를 만들 수 있다"는 큰 그림을 먼저 잡는 것이 좋다.


2. Custom Elements는 새 HTML 태그를 정의하는 방법이다

js
class AppBadge extends HTMLElement {
  connectedCallback() {
    this.textContent = 'New'
  }
}

customElements.define('app-badge', AppBadge)

핵심 규칙:

  • 이름에 하이픈이 들어가야 한다
  • 보통 HTMLElement를 상속받는다
  • DOM에 연결될 때 connectedCallback()이 호출된다

대표 생명주기는 아래 정도를 먼저 알면 충분하다.

  • connectedCallback()
  • disconnectedCallback()
  • attributeChangedCallback()

3. customized built-in 요소는 실전 기본값이 아니다

원문은 기존 요소 확장 방식도 다루지만, 현재는 주의가 필요하다.

  • <button is="fancy-button"> 같은 customized built-in 요소는 브라우저 이식성이 매끄럽지 않다
  • 특히 Safari 호환성 이슈 때문에 범용 UI 라이브러리 기본 선택지로 보기 어렵다

그래서 학습용 문서에서는 자율 커스텀 엘리먼트(autonomous custom elements) 를 기본 경로로 두는 편이 현실적이다.


4. Shadow DOM은 구조와 스타일 경계를 만든다

js
const shadowRoot = this.attachShadow({ mode: 'open' })
shadowRoot.innerHTML = `
  <style>
    :host { display: inline-block; }
  </style>
  <button><slot></slot></button>
`

Shadow DOM의 핵심은 다음 두 가지다.

  • 내부 마크업과 스타일을 외부 문서와 어느 정도 분리
  • 컴포넌트의 공용 API를 더 명확하게 만들 수 있음

openclosed

모드의미
open외부에서 element.shadowRoot로 접근 가능
closed외부에서 직접 접근 불가

실무에서는 디버깅과 협업 편의 때문에 open을 더 자주 본다.


5. 스타일링은 완전 차단이 아니라 경계 설계다

Shadow DOM을 쓰면 내부 스타일 충돌이 줄어든다.
다만 "외부에서 절대 건드릴 수 없다" 수준으로만 보면 오해가 생긴다.

같이 알아 두면 좋은 선택지:

  • :host
  • :host(...)
  • ::slotted(...)
  • ::part(...)

즉, 캡슐화는 폐쇄가 아니라 "어떤 면을 외부에 열어 둘 것인가"를 설계하는 작업이다.


6. <template><slot>은 재사용 구조의 기본이다

<template>

  • 로드 시 바로 렌더링되지 않는 비활성 마크업
  • 복제해서 여러 컴포넌트의 기반 구조로 사용 가능

<slot>

  • 외부에서 전달한 자식을 Shadow DOM 내부 특정 위치에 투영
  • 기본 슬롯과 이름 있는 슬롯을 지원
html
<app-card>
  <h2 slot="title">제목</h2>
  <p>본문</p>
</app-card>

이 구조를 알면 "내부 구조는 내가 통제하고, 일부 내용만 외부에서 넣게 하겠다"는 설계가 쉬워진다.


7. slotchange와 이벤트 경계

슬롯에 들어가는 콘텐츠가 바뀌면 slotchange로 감지할 수 있다.
또 Shadow DOM에서는 이벤트가 경계를 넘는 방식도 중요하다.

  • 어떤 이벤트는 Shadow DOM 밖으로 전파된다
  • 이때 event.composedevent.composedPath()를 보면 경로를 더 정확히 이해할 수 있다
  • openclosed 모드에 따라 외부에서 보이는 경로 정보가 달라질 수 있다

즉, Web Components에서는 DOM 구조뿐 아니라 이벤트 모델도 같이 바뀐다.


8. ElementInternals는 폼과 상태를 연결한다

원문 후반의 고급 주제 중 하나가 이 부분이다.

  • 커스텀 엘리먼트를 폼 제출에 참여시킬 수 있다
  • setFormValue()로 전송 값을 설정할 수 있다
  • 내부 상태와 접근성 정보를 브라우저와 연결할 수 있다

이 기능 덕분에 Shadow DOM 내부 구현을 유지하면서도, 외부에서는 일반 폼 컨트롤처럼 다루는 길이 열린다.

다만 이건 기본 DOM 입문 주제가 아니라, 이미 폼과 커스텀 엘리먼트 기본기를 갖춘 뒤 들어가는 고급 영역으로 보는 편이 맞다.


9. Declarative Shadow DOM도 알아 둘 가치가 있다

서버 렌더링과 Web Components를 함께 쓸 때 중요한 최신 흐름이 선언적 Shadow DOM이다.

  • <template shadowrootmode="open"> 형태로 서버 HTML에 Shadow DOM 구조를 표현
  • 초기 렌더링과 하이드레이션 사이의 간극을 줄이는 데 도움이 됨

이 기능 하나만으로 Web Components를 선택할 이유가 생기지는 않지만, "브라우저 수준 컴포넌트도 SSR과 연결할 길이 열리고 있다"는 점은 기억할 만하다.


10. Web Components는 언제 잘 맞을까?

잘 맞는 경우

  • 프레임워크를 넘나드는 공용 디자인 시스템
  • 외부 사이트에 삽입하는 위젯
  • 마이크로프론트엔드 경계의 공용 UI

덜 맞는 경우

  • 단일 프레임워크 기반 대형 SPA 전체를 모두 대체하려는 경우
  • 팀이 Shadow DOM과 생명주기 모델에 익숙하지 않은 경우
  • 라이브러리 생태계의 도움을 많이 받아야 하는 복합 앱

즉, Web Components는 만능 대체재가 아니라 브라우저 표준 기반의 강한 경계가 필요할 때 빛난다.


11. 흔한 안티패턴

안티패턴문제점더 나은 방식
모든 컴포넌트를 WC로 재작성하려는 시도팀 생산성 저하 가능경계가 필요한 부분에 선택적으로 사용
customized built-in을 기본 전략으로 채택이식성 문제자율 커스텀 엘리먼트 우선
Shadow DOM을 완전 차단 수단으로만 이해외부 API 설계가 빈약해짐slot, part, 이벤트 계약까지 설계
disconnectedCallback() cleanup 누락리스너와 observer 누수생명주기마다 정리 코드 포함

12. PR 리뷰 체크리스트

  • 이 컴포넌트는 정말 브라우저 수준 경계가 필요한가
  • 공개 API가 속성, 이벤트, 슬롯 구조로 명확히 설계되어 있는가
  • Shadow DOM 내부 리스너와 observer cleanup이 보장되는가
  • customized built-in 대신 더 이식성 높은 선택을 했는가
  • 폼 연동이 필요하다면 ElementInternals가 적절한지 검토했는가

핵심 정리

  • Web Components는 Custom Elements, Shadow DOM, template/slot을 묶어서 이해하면 된다
  • 장점은 재사용과 캡슐화이지만, 모든 UI의 만능 해법은 아니다
  • 현재는 자율 커스텀 엘리먼트, Shadow DOM 경계, ElementInternals, 선언적 Shadow DOM까지 함께 보는 편이 현실적이다