[JS] ์น ์ปดํฌ๋ํธ
์ปค๋ฎค๋ํฐ ํ๋ก์ ํธ์์ ๋ชจ๋ฌ, ํค๋, ๋๋กญ๋ค์ด ๊ฐ์ ์์๋ค์ ์ฌ์ฌ์ฉํ ์ ์๊ฒ ๋ถ๋ฆฌํ์๋ค. ๊ทธ๋ฌ๋ค๊ฐ ์๊ธฐ์น ๋ชปํ ์ค๋ฅ๋ฅผ ๋ฐ๊ฒฌํ๋ค...
uncaught SyntaxError: Failed to execute 'define' on 'CustomElementRegistry': "my-profileImg" is not a valid custom element name
์ฐพ์๋ณด๋ ์ปค์คํ ์๋ฆฌ๋จผํธ์ ๋ค์ ๊ท์น์ ๊ดํ ๋ด์ฉ์ด์๋ค. ํ๊ฑฐ๋ฉ๊ฑฐ๋ฉ์ค
๋ด๊ฐ ์ฌ์ฉํ๊ณ ์๋ ๊ฒ ์ปค์คํ ์๋ฆฌ๋จผํธ์๊ตฌ๋! ์ ํํ๋ ์น ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ๊ณ ์์๋ค...
์ ๋ง ๋ง์ ๊ฒ์ ์์๊ฐ๊ณ ์๋ ๊ฒ ๊ฐ๋ค.....
์น ์ปดํฌ๋ํธ
๊ธฐ๋ฅ์ ๋๋จธ์ง ์ฝ๋๋ก๋ถํฐ ์บก์ํํ์ฌ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปค์คํ ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ๊ณ ์น ์ฑ์์ ํ์ฉํ ์ ์๋๋ก ํด์ฃผ๋ ๋ค์ํ ๊ธฐ์ ๋ค์ ๋ชจ์
์ฆ, ์์ฃผ ์ฐ์ด๊ฑฐ๋ ๋๋ฌด ๊ธด ์ฝ๋๋ฅผ ๊ฐ์ง ๊ธฐ๋ฅ์ ๋ฐ๋ก ๋ถ๋ฆฌํด์ ๊ด๋ฆฌํ๊ฒ ๋ค! ๋ผ๋ ์ทจ์ง์ด๋ค. ์ด๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ์ด ๋์์ง๊ณ ์ฝ๋ ์ถฉ๋์ ๊ฑฑ์ ์ด ์๋ค.
์๋ฅผ ๋ค์ด ์๋ ๊ฐ์ด ์ ๋ง ๊ธด ์ฝ๋๊ฐ ์๋ค๊ณ ํ์... ํค๋๋ ๋ชจ๋ ํ์ด์ง์์ ์ฌ์ฉ๋ ํ ๋๊น ๊ณ์ํด์ ์ ๊ธด ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค...
...
<body>
<header>
<div class="header-contents">
<div class="nav-btn left">
<img id="nav-back-img" src="../assets/img/navigate-back.png" alt="๋ค๋ก๊ฐ๊ธฐ">
</div>
<div class="project-name">์๋น์ค ํ์ดํ</div>
<div class="nav-btn right">
<img id="nav-back-img" src="../assets/img/navigate-forward.png" alt="์์ผ๋ก๊ฐ๊ธฐ">
</div>
</div>
</header>
<main>
...
</main>
</body>
...
์ด๊ฑธ custom element๋ก ์์ฑํด์ ๋ฐ๊พธ๋ฉด?
...
<body>
<my-header></my-header>
<main>
...
</main>
<script type="module" src="../components/header.js"></script>
</body>
...
์ด๋ ๊ฒ๋ ์งง์์ง๋ค!!! ๊ฐ๋ ์ฑ๋ ์ข์์ง๊ณ ๊ตฟ๊ตฟ
< ๊ตฌ์ฑ ์์ >
1. Custom elements: ์ฌ์ฉ์ ๋ง์ถคํ HTML element๋ฅผ ๋ง๋ค ์ ์๊ฒ ํด์ฃผ๋ JS API ์ธํธ
2. Shadow DOM: ๋ฉ์ธ DOM๊ณผ ๋ ๋ฆฝ์ ์ผ๋ก ๋ ๋๋ง๋๋ DOM ํธ๋ฆฌ๋ฅผ ์ ์ดํ๊ธฐ ์ํ JS API ์ธํธ
3. HTML ํ ํ๋ฆฟ: <template>, <slot>๊ณผ ๊ฐ์ด ๋ ๋๋ง ์ ๋ํ๋์ง ์๋ ํ ํ๋ฆฟ
element๊ฐ ๋ญ์ง ๋ชจ๋ฅธ๋ค๋ฉด...
HTML element(์์)
์์ ํ๊ทธ๋ก ์์ํด์ ์ข ๋ฃ ํ๊ทธ๋ก ์ด๋ฃจ์ด์ง ๋ชจ๋ ๋ช ๋ น์ด๋ค

ํ๊ทธ ์ด๋ฆ
- element์ ์ญํ ์ง์ & ๊ธฐ๋ฅ์ ํฌํจํ๊ณ ์์.
์์ฑ๋ช
- element์ ์ ์ฉ๋ ์คํ์ผ ๋ฑ๋ฑ...
์์ฑ ๊ฐ
- ์๋ฌธ์๋ก! โจ HTML5 ํ์ค์์๋ ์์ฑ๋ช ์ ๋๋ฌธ์๋ฅผ ๊ตฌ๋ถ X
- ""('') ์ฌ์ฉํ๊ธฐ
๋ด์ฉ
- ํ๋ฉด์ ํ์๋ ๋ด์ฉ
์์ ํ๊ทธ์ ์ข ๋ฃ ํ๊ทธ
- element์ ์์๊ณผ ๋ ์ง์
- ์ข ๋ฃ ํ๊ทธ๊ฐ ์ ์ฐ์ผ ๋๋ ์์ ex. <img>, <br> ...
์ด์ ํ๋์ฉ ์์๊ฐ๋ณด์.
1๏ธโฃ Custom Elements
์ฌ์ฉ์๊ฐ ์ํ๋ ๊ธฐ๋ฅ์ ๊ฐ์ถ HTML element๋ฅผ ๋ง๋ค๊ฒ ํด์ค๋ค. ์์ฃผ ์ฌ์ฉ๋๊ณ ๋ฐ๋ณต๋๋ ์์๋ค์ ๋ชจ์๋์์ ํค์๋๋ก ๋์ฒดํ๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
class MyComponent extends HTMLElement {...}
customElements.define("my-component", MyComponent)
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ์ ์์ ๊ฐ๋ค.
HTMLElement๋ฅผ extendsํ ํด๋์ค ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ๋ค. HTMLElement๋ window ๋ธ๋ผ์ฐ์ ์ ๋ด์ฅ ๊ฐ์ฒด์ด๋ค.
์์์ customElements.define()์ ์ฌ์ฉํ๊ณ ์๋ค.
customElement ๋ํ window ๋ธ๋ผ์ฐ์ ์ ๋ด์ฅ ๊ฐ์ฒด์ด๋ฉฐ, ํธ์ถ ์ CustomElementRegistry ๊ฐ์ฒด์ ์ฐธ์กฐ๋ฅผ ๋ฆฌํดํ๋ค.
์ฌ๊ธฐ์ ์ด ์ปค์คํ ์๋ฆฌ๋จผํธ๋ฅผ ์ ์ดํ๊ธฐ ์ํ ๋ผ์ดํ ์ฌ์ดํด ์ฝ๋ฐฑ์ด ์กด์ฌํ๋ค.
- connectedCallback: component๊ฐ DOM์ ์ถ๊ฐ ๋ฌ์ ๋ ํธ์ถ
- disconnectedCallback: component ๋ด์ element๊ฐ ์ ๊ฑฐ๋ ๋ ๋ง๋ค ํธ์ถ
- observedAttributes: ๋ณ๊ฒฝ์ ๊ฐ์งํ ์์ฑ๋ค์ ๋ชจ๋ํฐ๋งํ๊ธฐ ์ํ ๋ฉ์๋
- attributeChangedCallback: observedAttributes์์ ๋ชจ๋ํฐ๋ง ์ค์ธ ์์ฑ๋ค์ด ๋ณ๊ฒฝ๋์์ ๋ ํธ์ถ
- adoptedCallback: element๊ฐ ์๋ก์ด document๋ก ์ด๋๋ ๋๋ง๋ค ํธ์ถ
class MyComponent extends HTMLElement {
// element ์์ฑ์ ๋ฌด์กฐ๊ฑด ์์ฑ!!
constructor() {
super();
...
}
connectedCallback() {...}
disconnectedCallback() {...}
static get observedAttributes() {return [...];}
attributeChangedCallback(name, oldValue, newValue) {...}
adoptedCallback() {...}
}
2๏ธโฃ Shadow DOM
๋ ๋ฆฝ์ ์ธ DOM ํธ๋ฆฌ๋ฅผ ์์ฑํ๋ ๊ธฐ์ . ์บก์ํ๋ฅผ ํตํด ๋งํฌ์ ๊ตฌ์กฐ, ์คํ์ผ, ๋ก์ง์ ์จ๊ธฐ๊ณ ํ์ด์ง ๋ด ๋ค๋ฅธ ์ฝ๋๋ก๋ถํฐ ๋ถ๋ฆฌ๋์ด ์ฝ๋ ์ถฉ๋์ด ์๊ฒ ๋ง๋ค์ด ์ค๋ค. ์ฆ, ๋ ๋ฆฝ์ ์ธ ์ค์ฝํ๋ฅผ ๊ฐ์ง๋ค.
๋ํ ๋ธ๋ผ์ฐ์ ๊ฐ shadow DOM์ ๋ณ๋๋ก ์ฒ๋ฆฌํ์ฌ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํ ์ ์๋ค.
์ธ๋ถ JS์์์ shadowRoot ์ ๊ทผ ๊ฐ๋ฅ์ฑ์ ๋ฐ๋ผ ๋ชจ๋๊ฐ ๋๋๋ค.
- ์ด๋ฆฐ ๋ชจ๋: ์ธ๋ถ ์ ๊ทผ ๊ฐ๋ฅ (mode: "open")
- ๋ซํ ๋ชจ๋: ์ธ๋ถ ์ ๊ทผ ๋ถ๊ฐ๋ฅ ⇒ ํฌ๋กค๋ง ๋ฐฉ์ง ๊ฐ๋ฅ. (mode: "closed")
< ๊ตฌ์กฐ >
- Shadow Host: Shadow DOM์ด ๋ถ์ฐฉ๋๋ ์ผ๋ฐ DOM์ ์์
- Shadow Root: Shadow ํธ๋ฆฌ์ ๋ฃจํธ ๋ ธ๋
- Shadow Tree: Shadow DOM ๋ด๋ถ์ DOM ํธ๋ฆฌ
- Shadow boundary: ๊ฒฝ๊ณ
โจ ์๋ document tree์ shadow tree๋ฅผ ํฉ์ณ์ ๋ ๋๋ง์ ์ํด Flattened Tree๋ฅผ ๊ตฌ์ฑํ๋ค.
class MyComponent extends HTMLElement {
constructor() {
super();
// Shadow DOM ์ฌ์ฉ
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
const navBtns = this.shadowRoot.querySelector('.nav-btn.left');
navBtns.classList.add('hidden');
}
}
์์์ ์ค๋ช ํ custom element ๊ตฌ์ฑ์์ ์ฌ์ฉํ ์ ์๋ค.
this.shadowRoot.appendChild(template.content.cloneNode(true)) ์ ๋ํด ์ค๋ช ํ์๋ฉด...
shadowRoot์ ์์ ๋ ธ๋๋ฅผ ์ถ๊ฐํ๊ฒ ๋ค๋ ๊ฑฐ๋ค. template.content.cloneNode(true) ๋ ์๋์์ ์ค๋ช ํ๊ฒ ๋ค.
์ฝ๋ฐฑ์์ ํน์ element๋ฅผ ํธ์ถํ ๋๋ this.shadowRoot ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
3๏ธโฃ HTML ํ ํ๋ฆฟ
๋คํ๋จผํธ๊ฐ ์ฒ์ ๋ก๋๋ ๋ ๋ ๋๋ง๋์ง ์์ง๋ง JavaScript๋ฅผ ์ฌ์ฉํ๋ ๋ฐํ์์ ๋ํ๋๋ HTML ์กฐ๊ฐ์ ํฌํจํ๋ค.
๋ด๋ถ ์์๋ก content๋ฅผ ๊ฐ์ง๋ค.
์์์ shadowRoot์ template.content.cloneNode(true) ๋ฅผ ์ถ๊ฐํ๋ค.
์ฌ๊ธฐ์ template๋ template element๊ฐ ์ง์ ๋ const ๋ณ์์ด๋ค. ๋ฐ๋ผ์ ์ ์ฝ๋๋ template element ์์ ์ ์ธ๋ ๋ด์ฉ์ ๋ณต๋ถํ๊ฒ ๋ค๋ ๋ป์ด๋ค.
const template = document.createElement('template');
template.innerHTML = `
<link rel="stylesheet" type="text/css" href="../assets/style/header.css">
<header>
<div class="header-contents">
<div class="nav-btn left">
<img id="nav-back-img" src="../assets/img/navigate-back.png" alt="๋ค๋ก๊ฐ๊ธฐ">
</div>
<div class="project-name">์๋น์ค ํ์ดํ</div>
<div class="nav-btn right">
<img id="nav-back-img" src="../assets/img/navigate-forward.png" alt="์์ผ๋ก๊ฐ๊ธฐ">
</div>
</div>
</header>
`;
๋งจ ์์ ์์์์ header๋ฅผ template ์์๋ก ๋ฐ๋ก ๋นผ๋ฉด ์์ ๊ฐ์ ์ฝ๋๊ฐ ๋๋ค.
์ง๊ธ์ style์ stylesheet๋ฅผ ๋ถ๋ฌ์ค๊ณ ์์ง๋ง, style element๋ฅผ ๋ง๋ค์ด์ template์ ๋ถ์ฌ๋ ๋๋ค.
const styleEl = document.createElement('style');
styleEl.textContent = `
header {
background-color: #f8f8f8;
}
.header-contents {
padding: 10px;
}
`;
// template์ content์ style ์์๋ฅผ ์ถ๊ฐ (๋ณดํต ๋งจ ์์ ์ถ๊ฐ)
template.content.prepend(styleEl);
์ด์ custom element, shadow DOM, HTML ํ ํ๋ฆฟ์ ์ ์ ํ๊ฒ ์กฐํฉํด์ ์ฌ์ฉํ๋ฉด ๋๋ง์ ์ปค์คํ ์น ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์๋ค!!
๊ทธ๋ผ ์ด์ 3๊ฐ์ ํ์ผ ๋ง๊ณ ! 1๊ฐ์ ์๋ฐ์คํฌ๋ฆฝํธ ํ์ผ๋ก ์ปดํฌ๋ํธ๋ฅผ ๊ด๋ฆฌํ ์ ์๋ค.
๋ค๋ง ํ ๊ฐ์ง ์์ฌ์ด ์ ์ HTML๊ณผ CSS ์ฝ๋๊ฐ ์๋ฐ์คํฌ๋ฆฝํธ ํ์ผ ์์ ๋ค์ด๊ฐ๊ธฐ ๋๋ฌธ์ ์ ๋ถ ์ฃผํฉ์์ผ๋ก ๋์จ๋ค..... ๊ฐ๋ ์ฑ์ด ์ข ๋จ์ด์ง๊ธด ํ๋ค...
์ด ๋ถ๋ถ์ ํด๊ฒฐํ ๊ฒ์ด jsx ์ธ ๋ฏํ๋ค!