shadow DOM API는 웹 컴포넌트의 중요한 측면인 캡슐화의 핵심 파트이며 숨겨진 DOM을 요소에 부착하는 방법을 제공한다.
각 html 요소들에는 이런 shadow DOM이 부착되어 있다.
개발자 도구에서 F1 혹은 settings를 눌러서 (즉, 개발자도구에서 톱니바퀴 클릭하면 나오는 화면)
Elements > Show user agent shadow DOM을 체크해주면 각각의 html 요소에 숨겨진 요소들을 확인할 수 있다.
elem.attachShadow()
메서드를 사용하면 어떤 요소든 shadow root를 부착할 수 있다. 이 메서드는 매개변수로 옵션 객체를 갖는데, 그 옵션은 mode
이며 open
혹은 closed
값을 갖는다.
class로 웹 컴포넌트 만들기
class란? constructor, prototype을 사용해 상속 기능을 만들 수 있는 문법이다.
constructor는 오브젝트를 안전하게 뽑아주는 기계.
extends는 다른 class를 상속할 수 있게 도와주는 문법. 예를 들어 class A extends B {} 면, B의 기능을 A로 상속할 수 있게 해주는 것임.
아래 코드에서 보면, 내가 만들고 싶은 html 요소를 만들기 위해 extends를 사용해 HTMLElement로부터 기능들을 상속 받으면 된다. (
class <내가 만들고 싶은 웹 컴포넌트> extends HTMLElement {}
)
extends로 상속 기능을 받을 때는 super()를 쓰는데, super()는 'extends로 상속받은 부모 class의 constructor()를 의미한다'
즉, 내가 만들고 싶은 웹 컴포넌트에 constructor()를 추가하려면 super() 다음에 this.~로 코드를 이어 나가면 된다.
웹 컴포넌트 html을 클래스 형태로 만들고 define에 작성하면 된다.
class 클래스 extends HTMLElement { connectedCallback() { this.innerHTML = ...줄이고 싶은 html 내용들 ~~ } } customElements.define("태그 작명", 클래스);
attachShadow() 메서드로 shadowRoot라는 공간을 만들어 준 뒤, 여기에 원하는 html을 숨겨 넣는 것이다.
class MyCounter extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow( { mode: 'open' } );
}
connectedCallback() {
this.render();
}
render() {
this.shadow.innerHTML = `
<h1>Counter</h1>
${this.count}
<button id='btn'>Increment</button>
`;
}
}
customElements.define('my-counter', MyCounter);
set이 붙은 함수들을 setter라고 부른다. 데이터를 수정하는 함수들. (파라미터 1개 이상 존재해야)
get이 붙은 함수들을 getter라고 부른다. 데이터를 가져오는 함수들. (get은 파라미터가 있으면 안 되고, 함수 내에 return 존재해야. )
getter, setter => 새로 뽑힌 오브젝트 내용들을 쉽게 수정 관리. 오브젝트 내의 함수들을 괄호 없이 쓰게 해주는 건데, 직접 원본 데이터를 만지는 게 아니라 간접적으로 다룸으로써 데이터의 무결성을 보존.
static은 클래스 함수 자체에 메서드를 설정할 수 있는 방법임. 메서드를 프로퍼티 형태로 직접 할당하게 해준다.
class MyCustomElemet extends HTMLElemnt { constructor() { super(); } // 커스텀 요소가 생성될 때마다 실행 connectedCallback() { this.render(); } // 해당 요소가 새로운 문서로 이동될 때마다 호출 adoptCallback() { } // 요소의 속성 변동 사항(추가,제거,업데이트,교체)을 관찰하고 호출함 // observedAttributes() 속성에 나열된 특성에서만 호출 attributeChangedCallback(attrName, oldVal, newVal) { this.render(); } // attributeChangedCallback 에서 관찰하는 항목을 반환. static get observedAttributes() { return ['title']; } get title() { return this.getAttribute('title'); } // 커스텀 요소가 제거될 때 호출 disconnectedCallback() { alert('bye bye'); } // 커스텀 메서드 render() { this.innerHTML = ` <h1>${this.title}</h1> ` } } customElements.define('my-counter', MyCustomElemet);
getter, setter도 결국 함수야. 근데, 이걸 원본을 다루지 않고 간접적으로 다룸으로써 데이터 무결성 지키는 방법이야.
그냥 함수로 작성해도 되고 편하게 쓰려면 get, set을 써도 됨. 꼭 get, set 써야 하는 건 아니라는 의미.
아래 예제 코드에서 보면,
get count() { return this.getAttribute('count'); }
를 작성해야 html에서 count 속성을 다룰 수 있어. index.html에서 count='20'을 넣어도 위 get 함수가 없으면 undefined가 뜸.
또한, set count(newValue) { this.setAttribute('count', newValue); }
를 작성해줘야 setAttribute를 쓸 수 있는 듯. 예를 들어 index.html 코드에서 아래처럼 setAttribute() 쓸 수 있게 되는 것.
<button onclick="update();">Update to 100</button>
<script>
function update() {
let counter = document.querySelector('#counter');
counter.setAttribute('count', 100);
}
</script>
(counter.js)
class MyCounter extends HTMLElement {
constructor() {
super();
// shadow DOM 열어주고
this.shadow = this.attachShadow( { mode : "open" } );
}
// getter, setter
get count() {
return this.getAttribute('count');
}
set count(newValue) {
this.setAttribute('count', newValue);
}
// attributeChangedCallback()에서 관찰하는 항목 반환
static get observedAttributes() {
return ['count']; // 여기에 속성값(문자열)들을 배열로 [a, b, c, ... ] 이렇게 추가하면 되는듯
}
// 변동사항 추적
attributeChangedCallback(prop, oldVal, newVal) {
if (prop === 'count') {
this.render();
let btn = this.shadow.querySelector('#btn');
btn.addEventListener('click', this.inc.bind(this));
}
}
inc() {
this.count++;
}
// 커스텀 요소 생성될 때마다 실행
connectedCallback() {
this.render();
let btn = this.shadow.querySelector('#btn');
btn.addEventListener('click', this.inc.bind(this));
}
render() {
this.shadow.innerHTML = `
<h1>Counter</h1>
${this.count}
<button id='btn'>Increment</button>
`;
}
}
customElements.define('my-counter', MyCounter);
(index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Counter</title>
</head>
<body>
<my-counter id="counter" count="20"></my-counter>
<script src="./counter.js"></script>
<br><br>
<button onclick="update();">Update to 100</button>
<script>
function update() {
let counter = document.querySelector('#counter');
counter.setAttribute('count', 100);
}
</script>
</body>
</html>
How to create a Web Component using Vanilla JS
shadow DOM 사용하기
사용자 정의 요소 사용하기
정적 메서드와 정적 프로퍼티
https://alexband.tistory.com/53