웹 컴포넌트와 섀도우 돔

SangBooom·2022년 8월 15일
0

아래 내용들은 자바스크립트 완벽가이드 15장 중의 일부분입니다.

15.6 웹 컴포넌트

개요

input prefix에 검색 아이콘이 있고 사용자가 x 아이콘을 클릭하면 input 요소에 입력된 내용을 지우는 검색박스 가 필요하다. 이 검색 박스 하나를 추가할 때마다 아이콘을 넣고 입력된 내용을 지우는 이벤트 핸들러를 등록하고 등 많은 작업을 해야되는데 최근 리액트, 뷰 , 앵귤러같은 프레임워크가 나오고 검색박스 같은 사용자 인터페이스를 여러번 사용할 수 있게 만드는 환경을 제공했다.

웹 컴포넌트는 이런 프레임워크의 대안으로 브라우저에 네이티브로 추가 된 기능이다~
프레임워크에 의존하지 않고 자바스크립트와 새로운 태그만 써서 재사용하기 쉬운 UI 컴포넌트를 만든다.

15.6.1 웹 컴포넌트 사용

<script type="module" src="components/search-box.js">

웹 컴포넌트를 사용하려면 컴포넌트를 정의한 자바스크립트 파일을 모듈로 불러와야한다.

<search-box placeholder="search..."></search-box>

웹 컴포넌트 HTML 태그 이름을 직접 정의하는데, 중요한 건 이 태그 이름에 반드시 하이픈이 들어가야 된다는 것이다. 따라서 HTML의 미래 버전에서 하이픈이 없는 새로운 태그를 도입하더라도 웹 컴포넌트와는 충돌 할 일이 절대없다!

<search-box>
	<img src="images/search-icon.png" slot="left"/>
	<img src="images/cancel-icon.png" slot="right"/>
</search-box>

slot 속성은 자식 요소를 어디에 표시할지 정하는 속성이다.

웹 컴포넌트는 자바스크립트 모듈로 작성되고

걱정할 필요없다! 웹 컴포넌트에서는 자연스럽게 일어나는 일이다. 웹 브라우저의 HTML 파서는 아주 유연하며 자신이 이해하지 못하는 요소에 대해서 관대하게 처리한다. 브라우저는 컴포넌트가 정의되기 전에 웹 컴포넌트 태그를 만났을 경우에 범용 HTMLElement를 DOM트리에 추가한다. 그리고 나중에 커스텀 요소가 정의되면 그 범용 요소를 알맞게 업그레이드 한다.

DocumentFragment 노드

형제 관계인 노드를 그룹으로 묶을 때 임시로 부모가 되는 또 다른 타입의 노드 단위다.

Document의 가벼운 버전으로 많이 사용되고, 활성화된 문서 트리 구조가 아니기 때문에
내부의 트리를 변경해도 문서나 성능에 아무 영향도 주지 않으며 리플로우도 방지할 수 있다.

HTML

<ul id="list"></ul>

JavaScript

const list = document.querySelector('#list')
const fruits = ['Apple', 'Orange', 'Banana', 'Melon']

const fragment = new DocumentFragment()

fruits.forEach((fruit) => {
  const li = document.createElement('li')
  li.textContent = fruit
  fragment.appendChild(li)
})

list.appendChild(fragment)

출처 : https://developer.mozilla.org/ko/docs/Web/API/DocumentFragment#예제

15.6.2 HTML 템플릿

태그는 웹페이지에 자주 등장하는 컴포넌트를 최적화하기에 적합하다.

웹브라우저는 태그와 그 자식 요소를 절대 렌더링하지 않으므로 태그는 자바스크립트에서만 사용할 수 있다. 이 태그의 목적은 테이블 행 또는 웹 컴포넌트의 내부 구성 요소 같은 기본적인 HTML 구조가 웹 페이지에 여러 번 반복해야 할 때 을 써서 한 번만 정의하고 필요한 만큼 자바스크립트로 복사해서 쓰는 것이다.

자바스크립트에서 태그는 HTMLTemplateElement 객체로 나타낸다.
이 객체에는 content 프로퍼티가 단 하나만 존재하며, 프로퍼티 값은 의 자식 노드로 이루어진 DocumentFragment다. 이 DocumentFragment를 복사해서 필요한 만큼 문서에 삽입하면 된다.

15.6.3 커스텀 요소

커스텀 요소는 자바스크립트 클래스와 HTML 태그 이름을 묶어서 해당 태그가 자동으로 클래스의 인슽턴스가 되게 합니다. customElements.define() 메서드의 첫번쨰 인자는 웹 컴포넌트의 태그 이름이고 두번째 인자는 HTMLElement의 서브클래스이다. 해당 태그 이름을 가진 기존 요소는 모두 새로 생성된 클래스 인스턴스로 업그레이드 된다! 브라우저는 나중에 HTML을 파싱할 때 해당 태그 이름을 만날 때마다 자동으로 인스턴스를 만든다.

customElements.define() 에 전달하는 클래스는 HTMLElement를 상속해야되고 HTMLButtonElement 같은 구체적인 타입을 상속해선 안된다.

브라우저는 커스텀 요소 클래스에서 수명 주기 메서드(lifecycle)을 자동으로 호출하고, 커스텀 요소 인스턴스를 문서에 삽입할 때 connectedCallback() 메서드를 호출하고 많은 요소에서 이 메서드를 초기화에 사용한다.

customElements.define("inline-circle”, class InlineCircle extends HIMLElement { 
	// 브라우저는 <inline-circle> 요소가 문서에 삽입될 때 이 메서드를
	// 호출합니다. disconnectedCallback() 메서드도 있지만 이 예제에서는
	// 사용하지 않습니다.
	
	// 초기화에 사용
	connectedCallback() {
		// 원에 필요한 스타일
		this.style.display = "inline-block"; 
		this.style.borderRadius = "50%";
		this.style.border = "solid black 1px";
		this.style.transform = "translateY(10%)";
		// 이미 정의된 크기가 없다면 현재 폰트 크기에 맞춰 기본 크기를 설정합니다. 
		if (!this.style.width) {
			this.style.width = "0.8em";
			this.style.height = "0.8em"; 
		}
	}

	// 정적 프로퍼티 observedAttributes에 ,이벤트'로 등록할 속성을 지정합니다. 
	static get observedAttributes() { return ["diameter", '’color1']; }

	// 이 콜백은 위에 나열한 속성이 바뀔 때 호출됩니다.
	// 커스텀 요소가 처음 파싱될 때도 호출됩니다. 
	attributeChangedCallback(name, oldValue, newValue) {
		switch(name) { 
			case "diameter":
				// diameter 속성이 바뀌면 크기를 업데이트합니다. 
				this.style.width = newValue; this.style.height = newVal니e;
				break;
			case "color":
				// color 속성이 바뀌면 색깔 스타일을 업데이트합니다. 
				this.style.backgroundColor = newValue; 
				break;
		}
	}

	// 요소 속성에 대응하는 자바스크립트 프로퍼티를 정의합니다.
	// 이들 게터와 세터는 단순히 대응하는 속성을 가져오고 설정하기만 합니다.
	// 자바스크립트 프로퍼티가 설정되면 해당 프로퍼티는 attributeChangedCallbackO을 // 호출해 요소 스타일을 업데이트합니다.
	get diameter() { return this.getAttribute(“diameter"); }
	set diameter(diameter) { this.setAttribute(“diameter", diameter); } 
	get color() { return this.getAttribute("color"); }
	set color(color) { this.setAttribute("color", color);
}

15.6.4 섀도우 DOM

위 커스텀 요소는 제대로 캡슐화가 되지 않았다. diameter나 color 속성을 설정하면 style 속성도 변경되지만, 실제 HTML 요소에서 이런일이 일어날 리 없다. 커스텀 요소를 진정한 웹 컴포넌트로 만들기 위해서는 섀도우 DOM이라고 불리는 강력한 캡슐화 매커니즘을 사용해야 한다.

섀도우 호스트 요소는 모든 HTML 요소와 마찬가지로 자손 요소와 Text 노드로 구성된 일반적인 DOM 트리의 루트다.

섀도우 루트는 섀도우 호스트에서 뻗어 나오는 자손 요소의 또 다른 루트이다.

섀도우 루트의 자손은 일반적인 DOM 트리에 속하지않고, 호스트 요소의 children 배열에도 포함되지 않고, querySelector() 같은 일반적인 DOM 순회 메서드에서 열거되지도 않습니다.

이와 대비해서 섀도우 호스트의 일반적인 DOM 자식을 라이트 DOM 이라고 부른다.

HTML , 요소를 생각해 보면 섀도우 DOM의 목적을 이해하기 쉽다. 이들 요소에서는 미디어 제어에 필요한 사용자 인터페이스가 포함되어이 있지만, 재생과 일시 정지 버튼을 비롯한 다른 UI 요소들은 DOM 트리에 노출되지 않으며 자바스크립트로 조작할 수도 없다. (메서드로 호출하기만 하면 된다는 뜻)

섀도우 DOM 캡슐화

섀도우 DOM의 핵심 특징은 캡술화다. 섀도우 루트의 자손 요소는 일반적인 DOM 트리에 독립적이다.

섀도우 DOM은 매우 중요한 세가지 종류의 캡술화를 제공한다.

  1. 섀도우 루트를 생성할 때 열린 모드와 닫힌 모드를 선택할 수 있다.
    닫힌 섀도우 루트는 완전히 밀봉되고 접근도 불가능하다.
    하지만 섀도우 루트는 대부분 열린 모드로 생성되며 섀도우 호스트에 shadowRoot 프로퍼티가 생기므로 필요하다면 자바스크립트로 섀도우 루트의 요소에 접근할 수 있다.
attachShadow({mode: "open"});
  1. 섀도우 DOM의 요소는 라이트 DOM에서의 스타일을 상속하고, 라이트 DOM에서 정의한 CSS 변수를 섀도우 DOM의 스타일에서도 사용할 수 있긴 하지만, 라이트 DOM의 스타일과 섀도우 DOM의 스타일은 거의 대부분 완전히 독립적이다.웹컴포넌트 제작자와 사용자가 스타일시트 충돌을 걱정할 필요는 없다.
  2. 섀도우 DOM 안에서 일어나는 load같은 일부 이벤트는 섀도우 DOM으로 제한된다. 반면, 포커스,마우스,키보드 이벤트 같은 이벤트에는 버블링이 적용된다.

섀도우 DOM 슬롯과 라이트 DOM 자식 요소

섀도우 호스트인 HTML 요소는 두개의 트리를 가진다. 하나는 호스트 요소의 라이트 DOM 자손 요소인 children 배열이고, 다른 하나는 섀도우 루트와 그 자손 요소이다.

  • 섀도우 루트의 자손 요소는 항상 섀도우 호스트 안에 표시된다.
  • 섀도우 루트의 자손 요소에 요소가 있다면 호스트 요소의 라이트 DOM 자식 요소는 그 의 자식인 것처럼 해당 슬롯의 섀도우 DOM 콘텐츠 대신 표시됩니다. 섀도우 DOM에 이 없다면 라이트 DOM 콘텐츠는 절대 표시 되지 않습니다. 섀도우 DOM에 이 있지만 섀도우 호스트에 라이트 DOM
    자식 요소가 없다면 슬롯의 섀도우 DOM 콘텐츠가 표시 됩니다.

15.6.5 예제 웹 컴포넌트

컴포넌트를 재사용 가능하고 자유롭게 설정할 수 있도록 만드는 건 상당히 어려운 일이므로 라이브러리를 많이 사용합니다.

쉐도우 돔을 사용하지 않고 돔의 분리를 할 수 있는 방법으로 iframe을 사용할 수 있는데,
실제 쉐도우 돔 mode: close의 polyfill은 iframe으로 작성되었다고 한다.

출처 : https://ui.toast.com/weekly-pick/ko_20170721#컴포넌트-커스텀-엘리먼트--쉐도우-돔--dom-oop

웹 컴포넌트 이해에 도움 준 레퍼런스:

profile
끊임없이 떨어지는 물방울이 바위를 뚫는다

0개의 댓글