본 글에서는 Custom Element를 적용해보는 위주로 다룹니다. 상세한 내용을 확인하려면 아래의 링크를 참고해주세요.
- WHATWG Spec: https://html.spec.whatwg.org/multipage/custom-elements.html
- MDN - Using custom elemtns: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements
- More capable form controls: https://web.dev/more-capable-form-controls/
- Custom Elements best practice: https://web.dev/custom-elements-best-practices/
<cr-button id="addShortcut" tabindex="0" noink="" title="바로가기 추가" aria-label="바로가기 추가" role="button" aria-disabled="false">
<!-- ... -->
</cr-button>
브라우저에서 이와 같은 엘리먼트를 보신 적 있으신가요? 분명 HTML엔 없는 버튼인데, 뭔가 사이트 측에서 커스텀한 느낌입니다.
위의 코드는 사실 크롬에서 새 탭(chrome://new-tab-page
)을 열었을 때 나타나는 "바로가기 추가" 에 해당되는 엘리먼트입니다.
이와 같이 사용자가 정의한 엘리먼트를 Custom Element 라고 합니다.
Custom Element
<body>
<my-alert></my-alert>
</body>
<script>
customElements.define('my-alert', class MyAlert extends HTMLElement {
constructor() {
super();
this.innerHTML = `
<style>
div {
padding: 12px;
background-color: yellow;
color: red;
}
</style>
<div>경고가 발생하였습니다</div>
`;
}
});
</script>
조금 길지만, Custom Element의 innerHTML
을 제외하면 그렇게 많진 않습니다. 보시면 아시겠지만, innerHTML
은 Custom Element가 가지는 내부 HTML 코드입니다.
코드를 실행했을 때, 이렇게 나타납니다.
이걸 활용하면 반복되는 번거로운 태그들(div
, img
등)을 하나로 묶어 관리할 수 있습니다. 이 외에도 다른 기능들을 알아봅시다.
connectedCallback
/ disconnectedCallback
Custom Element가 document
에 추가되었을 때 호출됩니다. 기본적으로 document
에 추가된 직후 한 번만 호출되지만, 만약 document
에서 지웠다가 추가하는 것을 반복할 경우 여러 번 호출될 수 있습니다.
customElements.define('my-input', class MyInput extends HTMLElement {
constructor() {
super();
this.innerHTML = `
<style>
input {
padding: 10px;
border: 1px solid grey;
}
</style>
<input>
<p>당신의 입력은?: <span></span></p>
`;
}
onInput(event) {
this.querySelector('span').innerText = event.target.value;
}
connectedCallback() {
console.log('connected');
this.querySelector('input').addEventListener('change', this.onInput.bind(this));
}
disconnectedCallback() {
console.log('disconnected');
this.querySelector('input').removeEventListener('change', this.onInput);
}
});
setTimeout
, setInterval
, addEventListener
/ clearTimeout
, clearInterval
, removeEventListener
등, DOM의 생성과 소멸 시 수행해야 할 동작이 있을 때 유용합니다.
observedAttributes
/ attributesChangedCallback
observedAttributes
는 관찰할 속성(attribute)들 입니다. attributesChangedCallback
은 observedAttributes
로 관찰한 속성에 변경이 생겼을 때 호출됩니다.
customElements.define('my-alert', class MyAlert extends HTMLElement {
constructor() {
super();
this.innerHTML = `
<style>
div { padding: 12px; }
[data-severity="warn"] { background: yellow; }
[data-severity="error"] { background: red; }
[data-severity="info"] { background: grey; }
</style>
<div data-severity="warn">문제가 발생하였습니다.</div>
`;
}
static get observedAttributes() {
return ['severity'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'severity') {
this.querySelector('div').dataset.severity = newValue;
}
}
});
Custom Element를 사용하는 이유?
잘 비유된 그림이 있어 가져와 봤습니다. 보편적으로는, Javascript가 HTML, CSS를 직접 조작하여 UI 로직을 수행합니다. Javascript가 조작하는 HTML, CSS는 굉장히 중첩되어 있는 div
일 수도 있고, 아주아주~긴 CSS 클래스 이름일 수도 있습니다.
<body>
<div class="app__container">
<div class="app__header">
<div class="app__title">
<div class="app__title-main-text"></div>
<div class="app__title-sub-text"></div>
</div>
<div class="app__navigation-container">
<div class="app__navigation-main-items">
<div>
<body>
<app-header><!-- ... --></app-header>
<app-navigation>
<app-navigation-item></app-navigation-item>
</app-navigation>
<!-- ... -->
하지만 Custom Element는 컴포넌트 단위로 묶어 작업을 할 수 있게 해주기 때문에, UI 개발을 훨씬 쉽게 해 줍니다.
마무리하며...
이미 이러한 문제를 해결하기 위한 솔루션이 많습니다. 컴포넌트 단위로 작업을 가능케 해주는 React, Vue.js, Svelte 같은 프레임워크를 보통 많이 사용합니다.
따라서 라이브러리를 사용하지 못하는 환경이 아닌 이상 더 많은 도구를 제공해주고, 더 편리하고, 레퍼런스가 많은 React 같은 프론트엔드 프레임워크를 선택할 것입니다.
그렇지만 Vanilla JS에도 기존의 문제를 해결하기 위한, 이러한 방법이 존재한다는 사실을 기억해주면 좋을 것 같습니다!