์น ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ๋ฉฐ ์ปค์คํ ์๋ฆฌ๋จผํธ ์์ฑ, ์ฐ๊ฒฐ, ์์ฑ ๋ณ๊ฒฝ, ์ฐ๊ฒฐ ํด์ ๋ฅผ ์ค๋ช ํฉ๋๋ค. ํ๋ ์น ์ฑ์ ์ํ ๊ฒฌ๊ณ ํ๊ณ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ ๊ตฌ์ถ๋ฒ์ ์์๋ณด์ธ์.
์น ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ: ์ปค์คํ ์๋ฆฌ๋จผํธ ์์ฑ ๋ฐ ๊ด๋ฆฌ ๋ง์คํฐํ๊ธฐ
์น ์ปดํฌ๋ํธ๋ ํ๋ ์น ๊ฐ๋ฐ์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ณ ์บก์ํ๋ UI ์๋ฆฌ๋จผํธ๋ฅผ ๊ตฌ์ถํ๊ธฐ ์ํ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ์น ์ปดํฌ๋ํธ์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ดํดํ๋ ๊ฒ์ ๊ฒฌ๊ณ ํ๊ณ ์ ์ง๋ณด์ ๊ฐ๋ฅํ๋ฉฐ ์ฑ๋ฅ์ด ๋ฐ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋๋ ๋ฐ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ด ์ข ํฉ ๊ฐ์ด๋์์๋ ์น ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ์ ๋ค์ํ ๋จ๊ณ๋ฅผ ํ์ํ๋ฉฐ, ์ปค์คํ ์๋ฆฌ๋จผํธ ์์ฑ ๋ฐ ๊ด๋ฆฌ๋ฅผ ๋ง์คํฐํ๋ ๋ฐ ๋์์ด ๋๋ ์์ธํ ์ค๋ช ๊ณผ ์ค์ฉ์ ์ธ ์์ ๋ฅผ ์ ๊ณตํฉ๋๋ค.
์น ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ?
์น ์ปดํฌ๋ํธ๋ ์บก์ํ๋ ์คํ์ผ๊ณผ ๋์์ ๊ฐ์ง ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปค์คํ HTML ์๋ฆฌ๋จผํธ๋ฅผ ๋ง๋ค ์ ์๊ฒ ํด์ฃผ๋ ์น ํ๋ซํผ API์ ์งํฉ์ ๋๋ค. ์ด๋ ์ธ ๊ฐ์ง ์ฃผ์ ๊ธฐ์ ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค:
- ์ปค์คํ ์๋ฆฌ๋จผํธ(Custom Elements): ์์ ๋ง์ HTML ํ๊ทธ์ ๊ทธ์ ์ฐ๊ด๋ ์๋ฐ์คํฌ๋ฆฝํธ ๋ก์ง์ ์ ์ํ ์ ์๊ฒ ํด์ค๋๋ค.
- ์๋ DOM(Shadow DOM): ์ปดํฌ๋ํธ๋ฅผ ์ํ ๋ณ๋์ DOM ํธ๋ฆฌ๋ฅผ ์์ฑํ์ฌ ์บก์ํ๋ฅผ ์ ๊ณตํ๋ฉฐ, ์ ์ญ ๋ฌธ์์ ์คํ์ผ๊ณผ ์คํฌ๋ฆฝํธ๋ก๋ถํฐ ๋ณดํธํฉ๋๋ค.
- HTML ํ ํ๋ฆฟ(HTML Templates): ํจ์จ์ ์ผ๋ก ๋ณต์ ํ์ฌ DOM์ ์ฝ์ ํ ์ ์๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ HTML ์ค๋ํซ์ ์ ์ํ ์ ์๊ฒ ํด์ค๋๋ค.
์น ์ปดํฌ๋ํธ๋ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๊ณ , ์ ์ง๋ณด์์ฑ์ ํฅ์์ํค๋ฉฐ, ๋ณต์กํ ์ฌ์ฉ์ ์ธํฐํ์ด์ค๋ฅผ ๋ชจ๋ํ๋๊ณ ์กฐ์ง์ ์ธ ๋ฐฉ์์ผ๋ก ๊ตฌ์ถํ ์ ์๊ฒ ํฉ๋๋ค. ๋ชจ๋ ์ฃผ์ ๋ธ๋ผ์ฐ์ ์์ ์ง์๋๋ฉฐ, ์ด๋ค ์๋ฐ์คํฌ๋ฆฝํธ ํ๋ ์์ํฌ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจ๊ป ์ฌ์ฉํ๊ฑฐ๋, ์ฌ์ง์ด ํ๋ ์์ํฌ ์์ด๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์น ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ
์น ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ๋ ์ปค์คํ ์๋ฆฌ๋จผํธ๊ฐ ์์ฑ๋์ด DOM์์ ์ ๊ฑฐ๋ ๋๊น์ง ๊ฑฐ์น๋ ์ฌ๋ฌ ๋จ๊ณ๋ฅผ ์ ์ํฉ๋๋ค. ์ด๋ฌํ ๋จ๊ณ๋ฅผ ์ดํดํ๋ฉด ์ ์ ํ ์์ ์ ํน์ ์์ ์ ์ํํ์ฌ ์ปดํฌ๋ํธ๊ฐ ์ ํํ๊ณ ํจ์จ์ ์ผ๋ก ๋์ํ๋๋ก ๋ณด์ฅํ ์ ์์ต๋๋ค.
ํต์ฌ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- constructor(): ์๋ฆฌ๋จผํธ๊ฐ ์์ฑ๋๊ฑฐ๋ ์ ๊ทธ๋ ์ด๋๋ ๋ ํธ์ถ๋ฉ๋๋ค. ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ์ด๊ธฐํํ๊ณ ์๋ DOM์ ์์ฑํ๋(ํ์ํ ๊ฒฝ์ฐ) ๊ณณ์ ๋๋ค.
- connectedCallback(): ์ปค์คํ ์๋ฆฌ๋จผํธ๊ฐ ๋ฌธ์์ DOM์ ์ฐ๊ฒฐ๋ ๋๋ง๋ค ํธ์ถ๋ฉ๋๋ค. ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ, ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ถ๊ฐ, ์ปดํฌ๋ํธ์ ์ด๊ธฐ ์ฝํ ์ธ ๋ ๋๋ง๊ณผ ๊ฐ์ ์ค์ ์์ ์ ์ํํ๊ธฐ์ ์ข์ ๊ณณ์ ๋๋ค.
- disconnectedCallback(): ์ปค์คํ ์๋ฆฌ๋จผํธ๊ฐ ๋ฌธ์์ DOM์์ ์ฐ๊ฒฐ ํด์ ๋ ๋๋ง๋ค ํธ์ถ๋ฉ๋๋ค. ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ ๊ฑฐ, ํ์ด๋จธ ์ทจ์ ๋ฑ ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํด์ผ ํ๋ ๊ณณ์ ๋๋ค.
- attributeChangedCallback(name, oldValue, newValue): ์ปค์คํ
์๋ฆฌ๋จผํธ์ ์์ฑ ์ค ํ๋๊ฐ ์ถ๊ฐ, ์ ๊ฑฐ, ์
๋ฐ์ดํธ ๋๋ ๊ต์ฒด๋ ๋๋ง๋ค ํธ์ถ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ์ปดํฌ๋ํธ ์์ฑ์ ๋ณํ์ ๋์ํ๊ณ ๊ทธ์ ๋ฐ๋ผ ๋์์ ์
๋ฐ์ดํธํ ์ ์์ต๋๋ค.
observedAttributes
์ ์ (static) getter๋ฅผ ์ฌ์ฉํ์ฌ ๊ด์ฐฐํ ์์ฑ์ ์ง์ ํด์ผ ํฉ๋๋ค. - adoptedCallback(): ์ปค์คํ ์๋ฆฌ๋จผํธ๊ฐ ์ ๋ฌธ์๋ก ์ด๋๋ ๋๋ง๋ค ํธ์ถ๋ฉ๋๋ค. iframe์ผ๋ก ์์ ํ๊ฑฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค๋ฅธ ๋ถ๋ถ ๊ฐ์ ์๋ฆฌ๋จผํธ๋ฅผ ์ด๋ํ ๋ ๊ด๋ จ์ด ์์ต๋๋ค.
๊ฐ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋ ์ฌ์ธต ๋ถ์
1. constructor()
์์ฑ์(constructor)๋ ์ปค์คํ ์๋ฆฌ๋จผํธ์ ์ ์ธ์คํด์ค๊ฐ ์์ฑ๋ ๋ ๊ฐ์ฅ ๋จผ์ ํธ์ถ๋๋ ๋ฉ์๋์ ๋๋ค. ๋ค์๊ณผ ๊ฐ์ ์์ ์ ํ๊ธฐ์ ์ด์์ ์ธ ๊ณณ์ ๋๋ค:
- ์ปดํฌ๋ํธ์ ๋ด๋ถ ์ํ๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
this.attachShadow({ mode: 'open' })
๋๋this.attachShadow({ mode: 'closed' })
๋ฅผ ์ฌ์ฉํ์ฌ ์๋ DOM์ ์์ฑํฉ๋๋ค.mode
๋ ์๋ DOM์ด ์ปดํฌ๋ํธ ์ธ๋ถ์ ์๋ฐ์คํฌ๋ฆฝํธ์์ ์ ๊ทผ ๊ฐ๋ฅํ์ง(open
) ์๋์ง(closed
)๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. ๋๋ฒ๊น ์ ์ฉ์ดํ๊ฒ ํ๊ธฐ ์ํด ์ผ๋ฐ์ ์ผ๋กopen
์ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค.- ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ฉ์๋๋ฅผ ์ปดํฌ๋ํธ ์ธ์คํด์ค์ ๋ฐ์ธ๋ฉํ์ฌ(
this.methodName = this.methodName.bind(this)
์ฌ์ฉ) ํธ๋ค๋ฌ ๋ด์์this
๊ฐ ์ปดํฌ๋ํธ ์ธ์คํด์ค๋ฅผ ์ฐธ์กฐํ๋๋ก ๋ณด์ฅํฉ๋๋ค.
์์ฑ์ ์ฌ์ฉ ์ ์ค์ ๊ณ ๋ ค์ฌํญ:
- ์์ฑ์์์๋ DOM ์กฐ์์ ์ํํด์๋ ์ ๋ฉ๋๋ค. ์๋ฆฌ๋จผํธ๊ฐ ์์ง DOM์ ์์ ํ ์ฐ๊ฒฐ๋์ง ์์๊ธฐ ๋๋ฌธ์ ์์ ์ ์๋ํ๋ฉด ์๊ธฐ์น ์์ ๋์์ด ๋ฐ์ํ ์ ์์ต๋๋ค. DOM ์กฐ์์
connectedCallback
์ ์ฌ์ฉํ์ธ์. - ์์ฑ์์์ ์์ฑ์ ์ฌ์ฉํ์ง ๋ง์ธ์. ์์ฑ์ด ์์ง ์ฌ์ฉ ๊ฐ๋ฅํ์ง ์์ ์ ์์ต๋๋ค. ๋์
connectedCallback
์ด๋attributeChangedCallback
์ ์ฌ์ฉํ์ธ์. super()
๋ฅผ ๋จผ์ ํธ์ถํด์ผ ํฉ๋๋ค. ๋ค๋ฅธ ํด๋์ค(์ผ๋ฐ์ ์ผ๋กHTMLElement
)๋ฅผ ์์ํ๋ ๊ฒฝ์ฐ ์ด๋ ํ์์ ๋๋ค.
์์ :
class MyCustomElement extends HTMLElement {
constructor() {
super();
// ์๋ ๋ฃจํธ ์์ฑ
this.shadow = this.attachShadow({mode: 'open'});
this.message = "Hello, world!";
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
alert(this.message);
}
}
2. connectedCallback()
connectedCallback
์ ์ปค์คํ
์๋ฆฌ๋จผํธ๊ฐ ๋ฌธ์์ DOM์ ์ฐ๊ฒฐ๋ ๋ ํธ์ถ๋ฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ ์์
์ ์ํํ๊ธฐ์ ๊ฐ์ฅ ์ ํฉํ ๊ณณ์
๋๋ค:
- API์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- ์ปดํฌ๋ํธ ๋๋ ๊ทธ ์๋ DOM์ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
- ์ปดํฌ๋ํธ์ ์ด๊ธฐ ์ฝํ ์ธ ๋ฅผ ์๋ DOM์ ๋ ๋๋งํฉ๋๋ค.
- ์์ฑ์์์ ์ฆ๊ฐ์ ์ธ ๊ด์ฐฐ์ด ๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ ์์ฑ ๋ณ๊ฒฝ์ ๊ด์ฐฐํฉ๋๋ค.
์์ :
class MyCustomElement extends HTMLElement {
// ... ์์ฑ์ ...
connectedCallback() {
// ๋ฒํผ ์๋ฆฌ๋จผํธ ์์ฑ
const button = document.createElement('button');
button.textContent = 'Click me!';
button.addEventListener('click', this.handleClick);
this.shadow.appendChild(button);
// ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ (์์)
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
this.data = data;
this.render(); // UI ์
๋ฐ์ดํธ๋ฅผ ์ํด render ๋ฉ์๋ ํธ์ถ
});
}
render() {
// ๋ฐ์ดํฐ ๊ธฐ๋ฐ์ผ๋ก ์๋ DOM ์
๋ฐ์ดํธ
const dataElement = document.createElement('p');
dataElement.textContent = JSON.stringify(this.data);
this.shadow.appendChild(dataElement);
}
handleClick() {
alert("Button clicked!");
}
}
3. disconnectedCallback()
disconnectedCallback
์ ์ปค์คํ
์๋ฆฌ๋จผํธ๊ฐ ๋ฌธ์์ DOM์์ ์ฐ๊ฒฐ ํด์ ๋ ๋ ํธ์ถ๋ฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ ์์
์ ๋งค์ฐ ์ค์ํฉ๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
- ํ์ด๋จธ๋ ์ธํฐ๋ฒ์ ์ทจ์ํฉ๋๋ค.
- ์ปดํฌ๋ํธ๊ฐ ์ ์ ํ๊ณ ์๋ ๋ชจ๋ ๋ฆฌ์์ค๋ฅผ ํด์ ํฉ๋๋ค.
์์ :
class MyCustomElement extends HTMLElement {
// ... ์์ฑ์, connectedCallback ...
disconnectedCallback() {
// ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ ๊ฑฐ
this.shadow.querySelector('button').removeEventListener('click', this.handleClick);
// ํ์ด๋จธ ์ทจ์ (์์)
if (this.timer) {
clearInterval(this.timer);
}
console.log('์ปดํฌ๋ํธ๊ฐ DOM์์ ์ฐ๊ฒฐ ํด์ ๋จ.');
}
}
4. attributeChangedCallback(name, oldValue, newValue)
attributeChangedCallback
์ ์ปค์คํ
์๋ฆฌ๋จผํธ์ ์์ฑ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ํธ์ถ๋์ง๋ง, observedAttributes
์ ์ (static) getter์ ๋์ด๋ ์์ฑ์ ๋ํด์๋ง ํธ์ถ๋ฉ๋๋ค. ์ด ๋ฉ์๋๋ ๋ค์๊ณผ ๊ฐ์ ์์
์ ํ์์ ์
๋๋ค:
- ์์ฑ ๊ฐ์ ๋ณํ์ ๋ฐ์ํ์ฌ ์ปดํฌ๋ํธ์ ๋์์ด๋ ๋ชจ์์ ์ ๋ฐ์ดํธํฉ๋๋ค.
- ์์ฑ ๊ฐ์ ๊ฒ์ฆํฉ๋๋ค.
์ฃผ์ ํน์ง:
- ๊ด์ฐฐํ๊ณ ์ ํ๋ ์์ฑ ์ด๋ฆ์ ๋ฐฐ์ด์ ๋ฐํํ๋ `observedAttributes`๋ผ๋ ์ ์ (static) getter๋ฅผ ๋ฐ๋์ ์ ์ํด์ผ ํฉ๋๋ค.
attributeChangedCallback
์observedAttributes
์ ๋์ด๋ ์์ฑ์ ๋ํด์๋ง ํธ์ถ๋ฉ๋๋ค.- ์ด ๋ฉ์๋๋ ๋ณ๊ฒฝ๋ ์์ฑ์
name
, ์ด์ ๊ฐ์ธoldValue
, ๊ทธ๋ฆฌ๊ณ ์๋ก์ด ๊ฐ์ธnewValue
์ธ ๊ฐ์ง ์ธ์๋ฅผ ๋ฐ์ต๋๋ค. - ์์ฑ์ด ์๋ก ์ถ๊ฐ๋ ๊ฒฝ์ฐ
oldValue
๋null
์ด ๋ฉ๋๋ค.
์์ :
class MyCustomElement extends HTMLElement {
// ... ์์ฑ์, connectedCallback, disconnectedCallback ...
static get observedAttributes() {
return ['message', 'data-count']; // 'message'์ 'data-count' ์์ฑ์ ๊ด์ฐฐ
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'message') {
this.message = newValue; // ๋ด๋ถ ์ํ ์
๋ฐ์ดํธ
this.renderMessage(); // ๋ฉ์์ง ๋ค์ ๋ ๋๋ง
} else if (name === 'data-count') {
const count = parseInt(newValue, 10);
if (!isNaN(count)) {
this.count = count; // ๋ด๋ถ ์นด์ดํธ ์
๋ฐ์ดํธ
this.renderCount(); // ์นด์ดํธ ๋ค์ ๋ ๋๋ง
} else {
console.error('์๋ชป๋ data-count ์์ฑ ๊ฐ:', newValue);
}
}
}
renderMessage() {
// ์๋ DOM์ ๋ฉ์์ง ๋์คํ๋ ์ด ์
๋ฐ์ดํธ
let messageElement = this.shadow.querySelector('.message');
if (!messageElement) {
messageElement = document.createElement('p');
messageElement.classList.add('message');
this.shadow.appendChild(messageElement);
}
messageElement.textContent = this.message;
}
renderCount(){
let countElement = this.shadow.querySelector('.count');
if(!countElement){
countElement = document.createElement('p');
countElement.classList.add('count');
this.shadow.appendChild(countElement);
}
countElement.textContent = `Count: ${this.count}`;
}
}
attributeChangedCallback ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ:
- ์ ๋ ฅ ๊ฒ์ฆ: ์ฝ๋ฐฑ์ ์ฌ์ฉํ์ฌ ์ ๊ฐ์ ๊ฒ์ฆํ์ฌ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
- ์ ๋ฐ์ดํธ ๋๋ฐ์ด์ฑ: ๊ณ์ฐ ๋น์ฉ์ด ๋ง์ด ๋๋ ์ ๋ฐ์ดํธ์ ๊ฒฝ์ฐ, ๊ณผ๋ํ ์ฌ๋ ๋๋ง์ ํผํ๊ธฐ ์ํด ์์ฑ ๋ณ๊ฒฝ ํธ๋ค๋ฌ๋ฅผ ๋๋ฐ์ด์ฑํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
- ๋์ ๊ณ ๋ ค: ๋ณต์กํ ๋ฐ์ดํฐ์ ๊ฒฝ์ฐ, ์์ฑ ๋์ ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ๊ณ ํ๋กํผํฐ setter ๋ด์์ ์ง์ ๋ณ๊ฒฝ ์ฌํญ์ ์ฒ๋ฆฌํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
5. adoptedCallback()
adoptedCallback
์ ์ปค์คํ
์๋ฆฌ๋จผํธ๊ฐ ์๋ก์ด ๋ฌธ์๋ก ์ด๋๋ ๋(์: ํ iframe์์ ๋ค๋ฅธ iframe์ผ๋ก ์ด๋๋ ๋) ํธ์ถ๋ฉ๋๋ค. ์ด๋ ๋ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋์ด์ง๋ง, ๋ฌธ์ ์ปจํ
์คํธ์ ๊ด๋ จ๋ ๋ ๋ณต์กํ ์๋๋ฆฌ์ค์์ ์์
ํ ๋ ์์๋๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
์์ :
class MyCustomElement extends HTMLElement {
// ... ์์ฑ์, connectedCallback, disconnectedCallback, attributeChangedCallback ...
adoptedCallback() {
console.log('์ปดํฌ๋ํธ๊ฐ ์ ๋ฌธ์๋ก ์ฑํ๋จ.');
// ์ปดํฌ๋ํธ๊ฐ ์ ๋ฌธ์๋ก ์ด๋๋ ๋ ํ์ํ ์กฐ์ ์ ์ํ
// ์ด๋ ์ธ๋ถ ๋ฆฌ์์ค์ ๋ํ ์ฐธ์กฐ๋ฅผ ์
๋ฐ์ดํธํ๊ฑฐ๋ ์ฐ๊ฒฐ์ ๋ค์ ์ค์ ํ๋ ๊ฒ์ ํฌํจํ ์ ์์
}
}
์ปค์คํ ์๋ฆฌ๋จผํธ ์ ์ํ๊ธฐ
์ปค์คํ
์๋ฆฌ๋จผํธ ํด๋์ค๋ฅผ ์ ์ํ๋ค๋ฉด, customElements.define()
์ ์ฌ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ์ ๋ฑ๋กํด์ผ ํฉ๋๋ค:
customElements.define('my-custom-element', MyCustomElement);
์ฒซ ๋ฒ์งธ ์ธ์๋ ์ปค์คํ
์๋ฆฌ๋จผํธ์ ํ๊ทธ ์ด๋ฆ์
๋๋ค(์: 'my-custom-element'
). ํ๊ทธ ์ด๋ฆ์ ํ์ค HTML ์๋ฆฌ๋จผํธ์์ ์ถฉ๋์ ํผํ๊ธฐ ์ํด ๋ฐ๋์ ํ์ดํ(-
)์ ํฌํจํด์ผ ํฉ๋๋ค.
๋ ๋ฒ์งธ ์ธ์๋ ์ปค์คํ
์๋ฆฌ๋จผํธ์ ๋์์ ์ ์ํ๋ ํด๋์ค์
๋๋ค(์: MyCustomElement
).
์ปค์คํ ์๋ฆฌ๋จผํธ๋ฅผ ์ ์ํ ํ์๋ ๋ค๋ฅธ HTML ์๋ฆฌ๋จผํธ์ฒ๋ผ HTML์์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
<my-custom-element message="Hello from attribute!" data-count="10"></my-custom-element>
์น ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ๋ฅผ ์ํ ๋ชจ๋ฒ ์ฌ๋ก
- ์์ฑ์๋ ๊ฐ๋ณ๊ฒ ์ ์งํ์ธ์: ์์ฑ์์์ DOM ์กฐ์์ด๋ ๋ณต์กํ ๊ณ์ฐ์ ํผํ์ธ์. ์ด๋ฌํ ์์
์๋
connectedCallback
์ ์ฌ์ฉํ์ธ์. disconnectedCallback
์์ ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ์ธ์: ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํญ์disconnectedCallback
์์ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ ๊ฑฐํ๊ณ , ํ์ด๋จธ๋ฅผ ์ทจ์ํ๊ณ , ๋ฆฌ์์ค๋ฅผ ํด์ ํ์ธ์.observedAttributes
๋ฅผ ํ๋ช ํ๊ฒ ์ฌ์ฉํ์ธ์: ์ค์ ๋ก ๋ฐ์ํด์ผ ํ๋ ์์ฑ๋ง ๊ด์ฐฐํ์ธ์. ๋ถํ์ํ ์์ฑ์ ๊ด์ฐฐํ๋ฉด ์ฑ๋ฅ์ ์ํฅ์ ์ค ์ ์์ต๋๋ค.- ๋ ๋๋ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์ ๊ณ ๋ คํ์ธ์: ๋ณต์กํ UI ์ ๋ฐ์ดํธ์ ๊ฒฝ์ฐ, LitElement๋ uhtml๊ณผ ๊ฐ์ ๋ ๋๋ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก์ธ์ค๋ฅผ ๋จ์ํํ๊ณ ์ฑ๋ฅ์ ํฅ์์ํค๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
- ์ปดํฌ๋ํธ๋ฅผ ์ฒ ์ ํ ํ ์คํธํ์ธ์: ๋จ์ ํ ์คํธ๋ฅผ ์์ฑํ์ฌ ์ปดํฌ๋ํธ๊ฐ ์๋ช ์ฃผ๊ธฐ ์ ๋ฐ์ ๊ฑธ์ณ ์ฌ๋ฐ๋ฅด๊ฒ ๋์ํ๋์ง ํ์ธํ์ธ์.
์์ : ๊ฐ๋จํ ์นด์ดํฐ ์ปดํฌ๋ํธ
์น ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ์ ์ฌ์ฉ์ ๋ณด์ฌ์ฃผ๋ ๊ฐ๋จํ ์นด์ดํฐ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค:
class CounterComponent extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this.count = 0;
this.increment = this.increment.bind(this);
}
connectedCallback() {
this.render();
this.shadow.querySelector('button').addEventListener('click', this.increment);
}
disconnectedCallback() {
this.shadow.querySelector('button').removeEventListener('click', this.increment);
}
increment() {
this.count++;
this.render();
}
render() {
this.shadow.innerHTML = `
<p>Count: ${this.count}</p>
<button>Increment</button>
`;
}
}
customElements.define('counter-component', CounterComponent);
์ด ์ปดํฌ๋ํธ๋ ๋ด๋ถ count
๋ณ์๋ฅผ ์ ์งํ๊ณ ๋ฒํผ์ ํด๋ฆญํ ๋ ๋์คํ๋ ์ด๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค. connectedCallback
์ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ถ๊ฐํ๊ณ disconnectedCallback
์ ์ด๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
๊ณ ๊ธ ์น ์ปดํฌ๋ํธ ๊ธฐ์
1. ์์ฑ ๋์ ํ๋กํผํฐ ์ฌ์ฉํ๊ธฐ
์์ฑ์ ๊ฐ๋จํ ๋ฐ์ดํฐ์ ์ ์ฉํ์ง๋ง, ํ๋กํผํฐ๋ ๋ ๋ง์ ์ ์ฐ์ฑ๊ณผ ํ์ ์์ ์ฑ์ ์ ๊ณตํฉ๋๋ค. ์ปค์คํ ์๋ฆฌ๋จผํธ์ ํ๋กํผํฐ๋ฅผ ์ ์ํ๊ณ getter์ setter๋ฅผ ์ฌ์ฉํ์ฌ ์ ๊ทผ ๋ฐ ์์ ๋ฐฉ์์ ์ ์ดํ ์ ์์ต๋๋ค.
class MyCustomElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this._data = null; // ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ธฐ ์ํด private ํ๋กํผํฐ ์ฌ์ฉ
}
get data() {
return this._data;
}
set data(value) {
this._data = value;
this.renderData(); // ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ์ปดํฌ๋ํธ ๋ค์ ๋ ๋๋ง
}
connectedCallback() {
// ์ด๊ธฐ ๋ ๋๋ง
this.renderData();
}
renderData() {
// ๋ฐ์ดํฐ ๊ธฐ๋ฐ์ผ๋ก ์๋ DOM ์
๋ฐ์ดํธ
this.shadow.innerHTML = `<p>Data: ${JSON.stringify(this._data)}</p>`;
}
}
customElements.define('my-data-element', MyCustomElement);
๊ทธ๋ฐ ๋ค์ ์๋ฐ์คํฌ๋ฆฝํธ์์ data
ํ๋กํผํฐ๋ฅผ ์ง์ ์ค์ ํ ์ ์์ต๋๋ค:
const element = document.querySelector('my-data-element');
element.data = { name: 'John Doe', age: 30 };
2. ํต์ ์ ์ํด ์ด๋ฒคํธ ์ฌ์ฉํ๊ธฐ
์ปค์คํ ์ด๋ฒคํธ๋ ์น ์ปดํฌ๋ํธ๊ฐ ์๋ก ๋ฐ ์ธ๋ถ ์ธ๊ณ์ ํต์ ํ๋ ๊ฐ๋ ฅํ ๋ฐฉ๋ฒ์ ๋๋ค. ์ปดํฌ๋ํธ์์ ์ปค์คํ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค๋ฅธ ๋ถ๋ถ์์ ์ด๋ฅผ ์์ ํ ์ ์์ต๋๋ค.
class MyCustomElement extends HTMLElement {
// ... ์์ฑ์, connectedCallback ...
dispatchCustomEvent() {
const event = new CustomEvent('my-custom-event', {
detail: { message: 'Hello from the component!' },
bubbles: true, // ์ด๋ฒคํธ๊ฐ DOM ํธ๋ฆฌ๋ฅผ ๋ฐ๋ผ ๋ฒ๋ธ๋ง๋๋๋ก ํ์ฉ
composed: true // ์ด๋ฒคํธ๊ฐ ์๋ DOM ๊ฒฝ๊ณ๋ฅผ ๋์ ์ ์๋๋ก ํ์ฉ
});
this.dispatchEvent(event);
}
}
customElements.define('my-event-element', MyCustomElement);
// ๋ถ๋ชจ ๋ฌธ์์์ ์ปค์คํ
์ด๋ฒคํธ ์์
document.addEventListener('my-custom-event', (event) => {
console.log('์ปค์คํ
์ด๋ฒคํธ ์์ :', event.detail.message);
});
3. ์๋ DOM ์คํ์ผ๋ง
์๋ DOM์ ์คํ์ผ ์บก์ํ๋ฅผ ์ ๊ณตํ์ฌ ์คํ์ผ์ด ์ปดํฌ๋ํธ ์ํ์ผ๋ก ์์ด ๋๊ฐ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. ์๋ DOM ๋ด์์ CSS๋ฅผ ์ฌ์ฉํ์ฌ ์น ์ปดํฌ๋ํธ์ ์คํ์ผ์ ์ง์ ํ ์ ์์ต๋๋ค.
์ธ๋ผ์ธ ์คํ์ผ:
class MyCustomElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this.shadow.innerHTML = `
<style>
p {
color: blue;
}
</style>
<p>์ด๊ฒ์ ์คํ์ผ์ด ์ ์ฉ๋ ๋จ๋ฝ์
๋๋ค.</p>
`;
}
}
์ธ๋ถ ์คํ์ผ์ํธ:
์ธ๋ถ ์คํ์ผ์ํธ๋ฅผ ์๋ DOM์ผ๋ก ๋ก๋ํ ์๋ ์์ต๋๋ค:
class MyCustomElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
const linkElem = document.createElement('link');
linkElem.setAttribute('rel', 'stylesheet');
linkElem.setAttribute('href', 'my-component.css');
this.shadow.appendChild(linkElem);
this.shadow.innerHTML += '<p>์ด๊ฒ์ ์คํ์ผ์ด ์ ์ฉ๋ ๋จ๋ฝ์
๋๋ค.</p>';
}
}
๊ฒฐ๋ก
์น ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๋ง์คํฐํ๋ ๊ฒ์ ํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๊ฒฌ๊ณ ํ๊ณ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ฅผ ๊ตฌ์ถํ๋ ๋ฐ ํ์์ ์ ๋๋ค. ๋ค์ํ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋๋ฅผ ์ดํดํ๊ณ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ ์ ์ง๋ณด์๊ฐ ์ฝ๊ณ ์ฑ๋ฅ์ด ๋ฐ์ด๋๋ฉฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค๋ฅธ ๋ถ๋ถ๊ณผ ์ํํ๊ฒ ํตํฉ๋๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ์ด ๊ฐ์ด๋๋ ์์ธํ ์ค๋ช , ์ค์ฉ์ ์ธ ์์ , ๊ทธ๋ฆฌ๊ณ ๊ณ ๊ธ ๊ธฐ์ ์ ํฌํจํ์ฌ ์น ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ์ ๋ํ ํฌ๊ด์ ์ธ ๊ฐ์๋ฅผ ์ ๊ณตํ์ต๋๋ค. ์น ์ปดํฌ๋ํธ์ ํ์ ๋ฐ์๋ค์ฌ ๋ชจ๋์์ด๊ณ ์ ์ง๋ณด์ ๊ฐ๋ฅํ๋ฉฐ ํ์ฅ ๊ฐ๋ฅํ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ์ธ์.
๋ ์์๋ณด๊ธฐ:
- MDN ์น ๋ฌธ์: ์น ์ปดํฌ๋ํธ์ ์ปค์คํ ์๋ฆฌ๋จผํธ์ ๋ํ ๊ด๋ฒ์ํ ๋ฌธ์.
- WebComponents.org: ์น ์ปดํฌ๋ํธ ๊ฐ๋ฐ์๋ฅผ ์ํ ์ปค๋ฎค๋ํฐ ๊ธฐ๋ฐ ๋ฆฌ์์ค.
- LitElement: ๋น ๋ฅด๊ณ ๊ฐ๋ฒผ์ด ์น ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ธฐ ์ํ ๊ฐ๋จํ ๊ธฐ๋ณธ ํด๋์ค.