웹 애플리케이션의 복잡성이 증가하면서 코드의 재사용성과 유지보수성을 향상시키기 위한 기술들이 각광받고 있습니다. 이번 글에서는 Custom Elements와 customElements.define() API를 활용해 HTML 컴포넌트를 만드는 방법과 모듈화된 구조를 설계하는 과정을 다룹니다.
1. Custom Elements란?
CustomElements는 사용자 정의 HTML 태그를 만들 수 있도록 제공되는 웹 컴포넌트(Web Components)의 핵심 기술 중 하나입니다. 이를 통해 표준 HTML 태그처럼 동작하는 커스텀 태그를 정의하고, 복잡한 UI 로직을 캡슐화하여 재사용할 수 있습니다.
예를 들어 <user-profile>와 같은 사용자 정의 태그를 만들어 사용자 정보를 표시하는 기능을 손쉽게 재사용할 수 있습니다.
2. Custom Elements의 기본 구조
- 클래스 정의: JavaScript 클래스를 사용하여 컴포넌트의 동작을 정의합니다.
- customElements.define(): 정의된 클래스를 특정 태그 이름과 연결합니다.
예제: 간단한 Custom Element 만들기
class HelloWorld extends HTMLElement {
constructor() {
super();
this.innerHTML = `<p>Hello, World!</p>`;
}
}
// 태그 이름 정의
customElements.define('hello-world', HelloWorld);
위 코드를 통해 <hello-world></hello-world> 태그를 어디서든 사용할 수 있습니다.
3. Shadow DOM을 활용한 캡슐화
CustomElements는 Shadow DOM과 결합하면 더욱 강력해집니다. Shadow DOM은 컴포넌트의 스타일과 구조를 캡슐화하여 외부로부터의 영향을 차단합니다.
Shadow DOM 적용 예제
class UserCard extends HTMLElement {
constructor() {
super();
// Shadow Root 생성
const shadow = this.attachShadow({ mode: 'open' });
// Shadow DOM 내부 내용 정의
shadow.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
text-align: center;
font-family: Arial, sans-serif;
}
</style>
<div class="card">
<h2>User Name</h2>
<p>This is a custom element with Shadow DOM.</p>
</div>
`;
}
}
customElements.define('user-card', UserCard);
위 코드를 사용하면 <user-card></user-card> 태그를 삽입했을 때 외부 CSS 스타일과 충돌하지 않는 캡슐화된 컴포넌트를 생성할 수 있습니다.
4. Custom Elements에 데이터 전달
CustomElements에 데이터를 전달하기 위해 속성(attributes)와 메서드(methods)를 활용할 수 있습니다.
속성을 통한 데이터 전달
class GreetingCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `<p>Hello, ${this.getAttribute('name')}!</p>`;
}
}
customElements.define('greeting-card', GreetingCard);
<greeting-card name="Alice"></greeting-card>
결과: Hello, Alice!
속성 변경 감지
속성 값이 변경되면 자동으로 업데이트되도록 attributeChangedCallback()을 활용할 수 있습니다.
class DynamicGreeting extends HTMLElement {
static get observedAttributes() {
return ['name'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<p>Hello!</p>`;
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'name') {
this.shadowRoot.innerHTML = `<p>Hello, ${newValue}!</p>`;
}
}
}
customElements.define('dynamic-greeting', DynamicGreeting);
5. Custom Elements로 모듈화된 구조 구현하기
CustomElements를 활용하면 프로젝트의 각 UI 컴포넌트를 독립적으로 개발하고 재사용할 수 있습니다. 이를 통해 복잡한 애플리케이션도 간단하고 관리 가능한 구조로 구현할 수 있습니다.
예제: 여러 컴포넌트 조합
- Header 컴포넌트
class AppHeader extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<header><h1>App Header</h1></header>`;
}
}
customElements.define('app-header', AppHeader);
- Footer 컴포넌트
class AppFooter extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<footer><p>© 2024 My App</p></footer>`;
}
}
customElements.define('app-footer', AppFooter);
- Main 컴포넌트
class AppMain extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<main>
<user-card></user-card>
</main>
`;
}
}
customElements.define('app-main', AppMain);
최종 HTML
<app-header></app-header> <app-main></app-main> <app-footer></app-footer>
이처럼 CustomElements를 활용하면 프로젝트의 컴포넌트를 독립적으로 관리하고 쉽게 결합할 수 있습니다.
6. Custom Elements의 장단점
장점
- 캡슐화된 구조: Shadow DOM과 결합하여 독립적인 스타일과 동작 구현 가능
- 재사용성: 코드 중복을 줄이고 유지보수성을 향상
- 표준 기술: 추가 라이브러리 없이 브라우저에서 바로 지원
단점
- 초기 학습 곡선: 익숙해지기까지 약간의 학습이 필요
- 구형 브라우저 호환성: 일부 브라우저에서 추가 폴리필 필요
CustomElements는 모듈화된 구조와 코드 재사용성을 제공하는 강력한 웹 개발 도구입니다. customElements.define() API와 Shadow DOM을 활용하면 독립적이고 유지보수하기 쉬운 UI 컴포넌트를 만들 수 있습니다. 프로젝트에 CustomElements를 도입해보세요. 한층 더 효율적인 개발이 가능해질 것입니다!









