Web Components
๐Ÿ“„

Web Components

Created
Oct 21, 2020 01:48 PM
Tags
web components
js
ย 

Using Custom Elements


High-level View

ย 
Web Components๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” CustomElementRegistry interface๋ฅผ ๊ตฌํ˜„ํ•œ window.customElements ์˜ define() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.
ย 
์ด ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ ์„ธ ๊ฐœ์˜ arguments๋ฅผ ๋ฐ›์•„ custom element๋ฅผ registryํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ๊ฐ๊ฐ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
ย 
  • DOMString : custom element์˜ ์ด๋ฆ„ (kebab-case)
  • class extends HTMLElement : element์˜ ํ–‰๋™์„ ์ •์˜ํ•œ class
  • { extends } : inheritsํ•  Node name (optional)
ย 
๊ฐ€๋ น ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
class WordCount extends HTMLParagraphElement { constructor() { super(); // always call // element functionality } } customElements.define('word-count', WordCount, { extends: 'p' });
ย 
๋ฟ๋งŒ ์•„๋‹ˆ๋ผ connectedCallback, attributeChangedCallback ๋“ฑ lifecycle callbacks๋„ ์กด์žฌํ•˜๋ฉฐ, ์ด๋Š” ์•„๋ž˜์—์„œ ๋‹ค๋ฃจ๋„๋ก ํ•˜๊ฒ ๋‹ค.
ย 
์•”ํŠผ ์œ„์™€ ๊ฐ™์ด registration ํ–ˆ์œผ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
<word-count></word-count> <!-- or --> <p is="word-count"></p>
ย 
๋ฌผ๋ก  js๋กœ๋„ programmaticํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
ย 
document.createElement('word-count'); // or document.createElement('p', { is: 'word-count' });
ย 
๋ฌด์—‡์„ ์‚ฌ์šฉํ•˜๋“  ์ž์œ .
ย 

Working through some simple examples

ย 
๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉฐ ์ข€ ๋” ์ดํ•ดํ•ด๋ณด๋„๋ก ํ•˜์ž.
ย 

Autonomous custom elements

ย 
๋‹ค์Œ์˜ spec์„ ๊ฐ€์ง„ element๋ฅผ ํ•˜๋‚˜ registration ํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.
ย 
  • image icon๊ณผ text string์œผ๋กœ ๊ตฌ์„ฑ๋จ
  • text string์€ hide, icon์€ display
  • icon focused ์‹œ text๊ฐ€ popup์œผ๋กœ ๋‚˜์˜ด
ย 
์ผ๋‹จ ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ class๋ฅผ ํ•˜๋‚˜ ์ •์˜ํ•ด์ค€๋‹ค. ์ด ๋•Œ HTMLElement ๋ฅผ extendsํ•˜๋Š”๊ฒƒ์„ ์œ ์˜.
ย 
class PopupInfo extends HTMLElement { constructor() { super(); // always // functionality } }
ย 
super() ๋Š” ํ•ญ์ƒ constructor() ์ฒซ ๋ฒˆ์งธ์— ์œ„์น˜ํ•ด์•ผ ํ•จ์€ ์žŠ์ง€ ๋ง์ž.
super() ์•„๋ž˜์—๋Š” ์–ด๋–ค ํ–‰๋™์„ ํ•  ๊ฒƒ์ธ์ง€ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์—ฌ๊ธฐ์—๋Š” ๋‹ค์Œ์˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.
ย 
class PopupInfo extends HTMLElement { constructor() { super(); // create a shadow root this.attachShadow({ mode: 'open' }); // create nested elements const wrapper = document.createElement('span'); wrapper.classList.add('wrapper'); const icon = wrapper.appendChild(document.createElement('span')); icon.classList.add('icon'); icon.setAttribute('tabindex', 0); const img = icon.appendChild(document.createElement('img')); img.src = this.hasAttribute('img') ? this.getAttribute('img') : '/img/default.png'; const info = wrapper.appendChild(document.createElement('span')); info.classList.add('info'); info.textContent = this.getAttribute('data-text'); const style = document.createElement('style'); style.textContent = /* css strings */; // attach ccreated elements to the Shadow DOM this.shadowRoot.append(style, wrapper); } } customElements.define('popup-info', PopupInfo);
ย 
Shadow Root๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”? ๊ฑฑ์ •ํ•˜์ง€ ๋ง์ž. ์ง€๊ธˆ์€ ์•Œ ํ•„์š” ์—†๊ณ  ์ด ๋‚ด์šฉ๋„ ์•„๋ž˜์—์„œ ๋‹ค๋ฃฌ๋‹ค. ์ง€๊ธˆ์€ ๊ทธ๋ƒฅ ๋˜ ํ•˜๋‚˜์˜ independentํ•œ DOM์„ ์ƒ์„ฑํ•œ๋‹ค๊ณ  ๋ฐ›์•„๋“ค์ด๋ฉด ๋œ๋‹ค.
ย 
์•„๋ฌดํŠผ ์ด๋ ‡๊ฒŒ registration์„ ํ•ด ์ฃผ๋ฉด ์•ž์„œ ๋งํ–ˆ๋“ฏ์ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
<popup-info img="/img/alt.png" data-text="text contents"></popup-info>
ย 
ย 
์œ„์—์„œ this.hasAttribute() ๊ทธ๋ฆฌ๊ณ  this.getAttribute() ๋ฅผ ํ†ตํ•ด DOM attribute๋ฅผ ์ฐธ์กฐํ•œ๊ฒŒ ๋ณด์ด๋Š”๊ฐ€? ์ด๋“ค์ด ๋ฐ”๋กœ custom element์— ์ •์˜๋œ attribute๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ฒƒ์ด๋‹ค.
ย 

Internal vs External styles

ย 
style์€ ์œ„์™€ ๊ฐ™์ด ์ง์ ‘ css string์„ ์ง‘์–ด๋„ฃ์–ด๋„ ๋˜๋‚˜, ๋‹ค์Œ๊ณผ ๊ฐ™์ด <link> ํƒœ๊ทธ๋ฅผ ์ด์šฉํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ์ด๋‹ค.
ย 
const linkElement = document.createElement('link'); linkElement.setAttribute('rel', 'stylesheet'); linkElement.setAttribute('href', 'style.css'); // attach to the Shadow DOM this.shadowRoot.appendChild(linkElement);
ย 
์ด ํŽธ์ด ์ข€ ๋” ์‰ฝ๊ธฐ๋Š” ํ•œ๋ฐ, ๋ญ ์ด๊ฒƒ๋„ ์•„๋ž˜์—์„œ ๋” ์‰ฌ์šด ๋ฐฉ๋ฒ•์„ ๋ณผ ๊ฒƒ์ด๋‹ค.
ย 

Customized built-in elements

ย 
๋‹ค๋ฅธ ์˜ˆ๋ฅผ ํ•˜๋‚˜ ๋” ๋ณด์ž. expending-list ๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์งˆ custom element์ด๋‹ค.
ย 
class ExpandingList extends HTMLUListElement { constructor() { super(); // nothing } } customElements.define('expanding-list', ExpandingList, { extends: 'ul' });
ย 
์ด๋ฒˆ์—๋Š” ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋ ์ง€ ์ •์˜ํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋ƒฅ registration์„ ํ–ˆ๋‹ค. ๋‹ค๋งŒ ul node๋ฅผ extends ํ–ˆ์„ ๋ฟ์ด๊ณ . ๊ทธ๋Ÿผ ๊ฒฐ๊ณผ๋Š” ์–ด๋–จ๊นŒ?
ย 
<ul is="expanding-list"> <!-- ... --> </ul>
ย 
ย 
๊ฒฐ๊ณผ๋Š” ์ผ๋ฐ˜์ ์ธ <ul> ์„ ์‚ฌ์šฉํ•  ๋•Œ์™€ ๋™์ผํ•˜๋‹ค. ๋ฌผ๋ก  is attribute๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ <expanding-list> ๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค.
ย 

Using the Lifecycle callbacks

ย 
์•ž์„œ ๋งํ–ˆ๋“ฏ์ด ๊ฐ lifecycle๋งˆ๋‹ค ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ callback์ด ์กด์žฌํ•œ๋‹ค.
ย 
  • connectedCallback : custom element๊ฐ€ DOM์— connected๋  ๋•Œ ํ˜ธ์ถœ
    • ๋ชจ๋“  DOM์ด parsing๋˜๊ธฐ ์ „์—๋„ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์Œ
    • node moved ์‹œ์—๋„ ํ˜ธ์ถœ๋จ
    • disconnected ์‹œ์—๋„ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋Š” isConnected property๋ฅผ ์ด์šฉํ•ด ํŒ๋ณ„์ด ๊ฐ€๋Šฅ
  • disconnectedCallback : custom element๊ฐ€ DOM์—์„œ disconn ๋  ๋•Œ ํ˜ธ์ถœ
  • adoptedCallback : node๊ฐ€ moved ๋˜์—ˆ์„ ๋•Œ ํ˜ธ์ถœ
  • attributeChangedCallback : custom element์˜ attributes๊ฐ€ added, removed, changed ๋  ๋•Œ ํ˜ธ์ถœ
    • ๋‹จ, static get observedAttributes() ๋ฉ”์„œ๋“œ๋กœ observeํ•  attributes๋ฅผ ์•Œ๋ ค์ค˜์•ผ ํ•จ
ย 
์‹ค์ œ ์˜ˆ๋ฅผ ๋ณด๋ฉฐ ์ดํ•ดํ•ด๋ณด๋„๋ก ํ•˜์ž.
ย 
<custom-square l="100" c="red"></custom-square>
ย 
class CustomSquare extends HTMLElement { constructor() { super(); this.shadow = this.attachShadow({ mode: 'open' }); const div = document.createElement('div'); const style = document.createElement('style'); this.shadow.appendChild(style); this.shadow.appendChild(div); } updateStyle() { this.querySelector('style').textContent = /* css string */; } /** * lifecycle 'connected' * */ connectedCallback() { console.log('Custom square element added to page'); this.updateStyle(); } /** * lifecycle 'disconnected' * */ disconnectedCallback() { console.log('Custom square element removed from page'); } /** * lifecycle 'adopted' * */ adoptedCallback() { console.log('Custom square element moved to new page'); } /** * lifecycle 'attribute changed' * */ attributeChangedCallback(name, oldValue, newValue) { console.log('Custom square element attributes changed'); updateStyle(); } static get observedAttributes() { // should return an array containing names of attributes return ['c', 'l']; // watch 'c' and 'l' attributes } }
ย 
ย 
๋Œ€์ถฉ ๋А๋‚Œ์ด ์˜ค์ง€ ์•Š๋Š”๊ฐ€?
์ฐธ๊ณ ๋กœ, ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด attributeChangedCallback() ์€ observedAttributes() ๋กœ ์–ด๋–ค attribute๋ฅผ watching ํ•  ๊ฒƒ์ธ์ง€ ์ง€์ •ํ•ด์ค˜์•ผ๋งŒ ํ•œ๋‹ค. ๋ฐ˜๋“œ์‹œ.
ย 

Using Shadow DOM


์›น ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฐœ๋…์€ encapsulation ์ด๋‹ค.
ย 
๋ณต์žกํ•˜๊ฒŒ ๊ตฌํ˜„๋œ Component ๋‚ด๋ถ€๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ์ž˜ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋ฉฐ, ๋˜ ๋‹ค๋ฅธ Component์™€๋Š” ์–ด๋–ป๊ฒŒ ์ถฉ๋Œ์„ ํ”ผํ•  ์ˆ˜ ์žˆ์„๊นŒ? ์–ด๋–ป๊ฒŒ Scope๋ฅผ ๊ด€๋ฆฌํ•ด์•ผ๋งŒ ํ• ๊นŒ? ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ๊น”๋”ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•œ ์ฝ”๋“œ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์„๊นŒ?
ย 
Shadow DOM API๋Š” ์ด๋Ÿฌํ•œ ๊ฒƒ์— ์ดˆ์ ์„ ๋งž์ถ”์—ˆ์œผ๋ฉฐ, ์—ฌ๊ธฐ์„œ๋Š” ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ๊ฐ๊ฐ์˜ DOM์„ ์„œ๋กœ ์ถฉ๋Œ ์—†์ด ๋ถ„๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•œ์ง€ ๊ทธ ๋ฐฉ๋ฒ•์„ ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.
(๋ฌผ๋ก  ์•ž์„œ DOM์— ๋Œ€ํ•ด ์•Œ๊ณ ์žˆ์–ด์•ผ๋งŒ ํ•จ)
ย 

High-level view

ย 
๋‹ค์Œ๊ณผ ๊ฐ™์€ HTML ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•  ๋•Œ
ย 
<!DOCTYPE html> <html> <head> <meta charste="utf-8"> <title>Simple DOM</title> </head> <body> <section> <img src="dinosaur.png" alt="T-Rex"> <p>Here we will add a link to the <a href="https://www.mozilla.org/">Mozilla</a></p> </section> </body> </html>
ย 
์ด๋ฅผ DOM์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ tree ํ˜•ํƒœ๋กœ ๋‚˜ํƒ€๋‚ธ๋‹ค.
ย 
notion image
ย 
Shadow DOM ์—ญ์‹œ ์ด์™€ ํฌ๊ฒŒ ๋‹ค๋ฅผ ๊ฒƒ์ด ์—†์œผ๋ฉฐ, ๋‹ค์Œ ๊ทธ๋ฆผ์„ ํ†ตํ•ด ์ดํ•ดํ•ด๋ณด์ž.
ย 
notion image
ย 
๊ทธ๋ฆผ์— ๋‚˜์˜ค๋Š” ๋„ค ๊ฐ€์ง€ ๋‹จ์–ด์˜ ์˜๋ฏธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
ย 
  • Shadow host : ์ผ๋ฐ˜์ ์ธ DOM Node์ฒ˜๋Ÿผ ๋ณด์ด๋Š”, Shadow DOM ์—ฐ๊ฒฐ ์ง€์ 
  • Shadow tree : Shadow DOM ๋‚ด๋ถ€์˜ DOM Tree
  • Shadow boundary : Shadow DOM์˜ ์‹œ์ž‘ node๋ถ€ํ„ฐ Shadow DOM์˜ ๋ node๊นŒ์ง€์˜ ๊ณต๊ฐ„
  • Shadow root : Shadow tree์˜ root node
ย 
ํฌ๊ฒŒ ์–ด๋ ต์ง€ ์•Š๋‹ค.
์ผ๋ฐ˜์ ์ธ DOM Structure์™€ ๋น„๊ตํ•ด๋„ ํฌ๊ฒŒ ๋‹ค๋ฅผ ๊ฒƒ ์—†์œผ๋ฉฐ, ์‚ฌ์šฉ๋ฒ• ๋˜ํ•œ ๋™์ผ. ๊ทธ์ € Shadow DOM ๋‚ด๋ถ€์˜ ๋ชจ๋“  ๊ฒƒ๋“ค์€ Shadow Boundary ์™ธ๋ถ€์— ์˜ํ–ฅ์„ ๋ผ์น  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์ œ์™ธํ•˜๊ณ ๋Š” ๋ง์ด๋‹ค.
ย 

Basic usage

ย 
์•ž์„œ Element.attachShadow() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด Shadow DOM์„ ๊ตฌ์„ฑํ•ด๋ดค๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ ๋‘ ๊ฐ€์ง€ mode ๋ฅผ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
ย 
const shadow = element.attachShadow({ mode: 'open' }); const shadow = element.attachShadow({ mode: 'closed' });
ย 
  • open : Element.shadowRoot property๋ฅผ ์ด์šฉํ•ด Shadow DOM์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Œ
  • closed : ์ฐธ์กฐ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ฒŒ๋” Shadow DOM์„ ๊ตฌ์„ฑ
ย 
๋ฌด์Šจ ๋ง์ด๋ƒ?
ย 
const myShadowDOM = myCustomElement.shadowRoot; // when mode is 'open'
ย 
์—ฌ๊ธฐ์„œ closed mode์ธ ๊ฒฝ์šฐ์—๋Š” Element.shadowRoot ์— null ์ด ๋“ค์–ด๊ฐ€๋ฉฐ, ๋‹น์—ฐํžˆ ์ด ๊ฒฝ์šฐ๋Š” ์–ด๋– ํ•œ ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด์„œ๋„ Shadow DOM์„ ์ฐธ์กฐํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๋ง์ด๋‹ค.
ย 
์ด ๋•Œ๋ฌธ์— ์•ž์„œ constructor ๋‚ด๋ถ€์—์„œ๋Š” ์ฐธ์กฐ๋ฅผ ์œ„ํ•ด { mode: 'open' } ์œผ๋กœ ์„ค์ •ํ•œ ๊ฒƒ.
๋”ฐ๋กœ ์˜ˆ์ œ๋Š” ํ•ด๋ณด์ง€ ์•Š๊ฒ ๋‹ค. ์œ„์—์„œ ์ง„ํ–‰ํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ.
ย 

Using Templates and Slots


ย 
์—ฌ๊ธฐ์„œ๋Š” <template> ๊ณผ <slot> ํƒœ๊ทธ์— ๋Œ€ํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.
ย 

The truth about templates

ย 
์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ components๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์ด ์žˆ๊ฒ ์ง€๋งŒ, <template> ํƒœ๊ทธ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์‰ฝ๊ณ  ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค.
ย 
<template> ๋‚ด์˜ Nodes๋Š” DOM์— ๋ Œ๋”๋ง๋˜์ง€๋Š” ์•Š์œผ๋‚˜, Programmaticํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์ฐธ์กฐ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ.
ย 
<template id="my-paragraph"> <p>My Paragraph</p> <style> p { font-size: 30px; } </style> </template>
ย 
๊ฐ€๋ น ์œ„์™€ ๊ฐ™์€ template์ด ์žˆ๋‹ค๋ฉด...
ย 
const template = document.getElementById('my-paragraph'); const templateContent = template.content; document.body.appendChild(templateContent);
ย 
์ด๋ ‡๊ฒŒ DOM์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
๋‹ค๋งŒ ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ ์œ„์—์„œ ์ง€์ •ํ•œ style ์€ ์ „์—ญ์ ์œผ๋กœ ์„ ์–ธ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์œ ์˜.
ย 

Using templates with Web Components

ย 
์ด๋ฅผ web component์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
ย 
customElements.define( 'my-paragraph', class extends HTMLElement { constructor() { super(); const template = document.getElementById('my-paragraph'); this.attachShadow({ mode: 'open' }); this.shadowRoot.appendChild(template.content.cloneNode(true)); } } );
ย 
์—ฌ๊ธฐ์„œ ๊ตณ์ด cloneNode() ๋ฅผ ํ•˜๋Š” ์ด์œ ๋Š”? ์–ด์จŒ๋“  Template์€ ํ•˜๋‚˜์ด๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ ๊ณณ์—์„œ my-paragraph ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” cloneNode() ๋ฅผ ์ด์šฉํ•ด์•ผ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๊ฒŒ ๋œ๋‹ค.
ย 
์•”ํŠผ ์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 

Adding flexibility with Slots

ย 
์—ฌ๊ธฐ์— <slot> ํƒœ๊ทธ๋ฅผ ์ด์šฉํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.
์ด๋ฅผ ์ด์šฉํ•˜๋ฉด ๋งค์šฐ flexibleํ•œ Web Component์˜ ๊ตฌ์„ฑ์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.
ย 
<template> <p><slot></slot></p> </template>
ย 
์ด๋ ‡๊ฒŒ template์„ ๊ตฌ์„ฑํ–ˆ๋‹ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด <slot> ํƒœ๊ทธ ์ž๋ฆฌ์— Element๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
ย 
<my-paragraph> TEXT </my-paragraph>
ย 
๋ Œ๋”๋ง ์‹œ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
ย 
<p>TEXT</p>
ย 
๋งŒ์•ฝ ์—ฌ๋Ÿฌ ๊ฐœ์˜ <slot> ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด? ๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋ฆ„์„ ์ง€์ •ํ•ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
<template> <p class="title"><slot name="title"></slot></p> <p><slot></slot></p> </template>
ย 
์ด๋ ‡๊ฒŒ ์ •์˜๋œ ํ…œํ”Œ๋ฆฟ์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
<my-paragraph> <span slot="title">TITLE</span> Contents </my-paragraph>
ย 
๋ Œ๋”๋ง ์‹œ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
ย 
<p class="title"><span>TITLE</span></p> <p>Contents</p>
ย 
์ฐธ๊ณ ๋กœ... ์ง€๊ธˆ์€ <slot> ๋‚ด๋ถ€์— ์–ด๋– ํ•œ ๋‚ด์šฉ๋„ ๋“ค์–ด๊ฐ€์žˆ์ง€ ์•Š์œผ๋‚˜ ๋งŒ์•ฝ default node๋ฅผ ์›ํ•œ๋‹ค๋ฉด? ์›ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ <slot> ๋‚ด๋ถ€์— ๋„ฃ์–ด์ฃผ๋„๋ก ํ•˜์ž.
ย 
<template> <p><slot>default text</slot></p> </template>
ย 
ย 

Scoped styles

๋์œผ๋กœ, Styles๋ฅผ Component ๋‚ด์—๋งŒ ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋ณด๊ฒ ๋‹ค.
์‚ฌ์‹ค ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•œ๋ฐ, ๊ทธ๋ƒฅ Shadow DOM ๋‚ด์— <style> ํƒœ๊ทธ๊ฐ€ ์˜ค๊ฒŒ๋” ๋งŒ๋“ค์–ด์ฃผ๋ฉด ๋œ๋‹ค.
ย 
const html = ` <div></div> <style> div { width: 100px; height: 100px; background-color: black; } </style> `; class extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'closed' }) .innerHTML = html; }
ย 
์œ„์—์„œ <template> ํƒœ๊ทธ๋ฅผ ์ด์šฉํ–ˆ์„ ๋•Œ๋Š” <style> ํƒœ๊ทธ์— ์กด์žฌํ•˜๋Š” CSS Styles๊ฐ€ ์ „์—ญ์ ์œผ๋กœ ์„ ์–ธ๋˜์—ˆ๋˜ ๊ฒƒ์— ๋ฐ˜ํ•ด, ์œ„ ์ฝ”๋“œ๋Š” Shadow DOM์„ ์ด์šฉํ•˜๊ธฐ์— <style> ํƒœ๊ทธ์— ์กด์žฌํ•˜๋Š” CSS Styles๋Š” Shadow Boundary ๋ฐ–์œผ๋กœ ์˜ํ–ฅ์„ ๋ผ์น  ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.
ย