Custom Elements: 네이티브 UI 구축의 핵심, 재사용 가능한 컴포넌트 만들기
JavaScript만으로 네이티브 HTML 요소처럼 동작하는 재사용 가능한 UI 컴포넌트를 구축하는 Web Components 표준 기술, Custom Elements에 대해 알아봅니다. 기본 개념부터 생명주기 콜백, 활용법, 장단점까지 상세히 설명합니다.
"이 버튼은 왜 이렇게 동작하지?"
프론트엔드 개발을 하다 보면 공통적으로 사용되는 UI 요소들이 있습니다. 예를 들어, 모달 창, 탭 컴포넌트, 사용자 정의 버튼 등이 그렇습니다. 이런 요소들을 매번 똑같이 코딩하는 것은 비효율적이며, 유지보수에도 어려움을 야기합니다. "이 버튼은 왜 이렇게 동작하지?" 와 같은 질문은 종종 스타일이나 동작 방식이 통일되지 않아 발생하곤 합니다.
이러한 문제를 해결하기 위한 강력한 도구 중 하나가 바로 Custom Elements입니다. Web Components 표준의 일부인 Custom Elements를 사용하면 HTML에 새로운 태그를 직접 정의하고, 그 동작 방식까지 제어할 수 있습니다. 이는 마치 네이티브 HTML 요소처럼 동작하는 재사용 가능한 UI 컴포넌트를 구축할 수 있게 해줍니다.
Custom Elements, 무엇이 특별할까?
Custom Elements는 기본적으로 JavaScript 클래스를 사용하여 정의됩니다. 이 클래스는 새로운 HTML 요소를 나타내며, 해당 요소의 생명주기(lifecycle) 동안 특정 로직을 실행할 수 있도록 여러 콜백 메소드를 제공합니다.
가장 기본적인 콜백은 connectedCallback() 입니다. 이 메소드는 Custom Element가 DOM에 연결될 때마다 호출됩니다. 예를 들어, 간단한 커스텀 버튼을 만들어 봅시다.
1class MyCustomButton extends HTMLElement { 2 constructor() { 3 super(); // 반드시 호출해야 합니다. 4 this.attachShadow({ mode: 'open' }); // Shadow DOM을 사용하여 스타일 캡슐화를 합니다. 5 } 6 7 connectedCallback() { 8 this.shadowRoot.innerHTML = ` 9 <style> 10 button { 11 background-color: #4CAF50; /* Green */ 12 border: none; 13 color: white; 14 padding: 15px 32px; 15 text-align: center; 16 text-decoration: none; 17 display: inline-block; 18 font-size: 16px; 19 margin: 4px 2px; 20 cursor: pointer; 21 border-radius: 8px; 22 } 23 </style> 24 <button><slot>Default Text</slot></button> 25 `; 26 27 // 이벤트 리스너 추가 28 this.querySelector('button').addEventListener('click', () => { 29 alert('Custom button clicked!'); 30 }); 31 } 32} 33 34customElements.define('my-custom-button', MyCustomButton);
위 코드에서 MyCustomButton 클래스는 HTMLElement를 상속받습니다. constructor에서는 super()를 호출하고 Shadow DOM을 설정합니다. connectedCallback에서는 버튼의 HTML 구조와 스타일을 Shadow DOM 내부에 렌더링하고, 클릭 이벤트 리스너를 추가합니다. customElements.define() 함수를 통해 'my-custom-button'이라는 새로운 HTML 태그를 브라우저에 등록합니다.
이제 HTML에서는 이 커스텀 태그를 네이티브 태그처럼 사용할 수 있습니다:
1<my-custom-button>Click Me</my-custom-button> 2<my-custom-button>Another Button</my-custom-button>
생명주기 콜백 활용하기
Custom Elements는 connectedCallback 외에도 다음과 같은 생명주기 콜백을 제공합니다:
disconnectedCallback(): Custom Element가 DOM에서 제거될 때 호출됩니다. 이벤트 리스너 제거 등 정리 작업에 유용합니다.adoptedCallback(): Custom Element가 새로운 문서로 이동될 때 호출됩니다. (잘 사용되지 않음)attributeChangedCallback(name, oldValue, newValue): Custom Element의 속성이 변경될 때 호출됩니다. 이 콜백을 사용하려면observedAttributesgetter를 통해 관찰할 속성 목록을 명시해야 합니다.
attributeChangedCallback을 사용하여 속성에 따라 컴포넌트의 동작이나 외형을 동적으로 변경하는 예제를 살펴보겠습니다.
1class HighlightableButton extends HTMLElement { 2 static get observedAttributes() { 3 return ['highlighted']; 4 } 5 6 constructor() { 7 super(); 8 this.attachShadow({ mode: 'open' }); 9 } 10 11 connectedCallback() { 12 this.render(); 13 this.addEventListener('click', this.handleClick); 14 } 15 16 disconnectedCallback() { 17 this.removeEventListener('click', this.handleClick); 18 } 19 20 attributeChangedCallback(name, oldValue, newValue) { 21 if (name === 'highlighted') { 22 this.render(); 23 } 24 } 25 26 render() { 27 const isHighlighted = this.hasAttribute('highlighted'); 28 this.shadowRoot.innerHTML = ` 29 <style> 30 button { 31 padding: 10px 20px; 32 border: 1px solid #ccc; 33 cursor: pointer; 34 background-color: ${isHighlighted ? 'yellow' : 'white'}; 35 transition: background-color 0.3s ease; 36 } 37 </style> 38 <button>Highlightable</button> 39 `; 40 } 41 42 handleClick() { 43 alert('Clicked!'); 44 } 45} 46 47customElements.define('highlightable-button', HighlightableButton);
이 컴포넌트는 highlighted 속성이 추가되거나 제거됨에 따라 버튼의 배경색이 변합니다. observedAttributes에 'highlighted'를 명시했기 때문에, HTML에서 highlighted 속성을 토글하면 attributeChangedCallback이 호출되어 render 메소드가 다시 실행됩니다.
1<highlightable-button highlighted>Always Yellow</highlightable-button> 2<highlightable-button>Normal</highlightable-button> 3 4<script> 5 const btn = document.querySelector('highlightable-button:not([highlighted])'); 6 setTimeout(() => { 7 btn.setAttribute('highlighted', ''); // 3초 후 highlighted 속성 추가 8 }, 3000); 9</script>
왜 Custom Elements를 사용해야 할까?
- 재사용성 및 유지보수성 향상: 공통 UI 패턴을 컴포넌트화하여 코드 중복을 줄이고, 수정이 필요할 때 해당 컴포넌트만 업데이트하면 되므로 유지보수성이 크게 향상됩니다.
- 프레임워크 독립성: Custom Elements는 웹 표준이므로 React, Vue, Angular 등 어떤 프레임워크에서도 사용할 수 있습니다. 혹은 프레임워크 없이 순수 JavaScript 환경에서도 활용 가능합니다. 이는 장기적으로 기술 스택 변화에 유연하게 대처할 수 있게 합니다.
- 캡슐화: Shadow DOM과 함께 사용하면 컴포넌트의 스타일과 동작이 외부와 격리되어 예상치 못한 스타일 충돌이나 동작 방해를 방지할 수 있습니다.
- 네이티브처럼 사용:
<my-custom-button>과 같이 의미론적이고 명확한 태그를 사용할 수 있어 HTML의 가독성이 높아집니다.
주의할 점
- 브라우저 지원: 모든 최신 브라우저에서 지원하지만, Internet Explorer와 같은 구형 브라우저에서는 Polyfill이 필요할 수 있습니다.
- 복잡성: 매우 복잡한 컴포넌트의 경우, Custom Elements만으로 구현하기보다는 상태 관리 라이브러리나 프레임워크의 도움을 받는 것이 더 효율적일 수 있습니다.
- SEO: Custom Elements의 내부 콘텐츠가 JavaScript에 의해 동적으로 렌더링되는 경우, 검색 엔진 최적화(SEO)를 고려해야 합니다. 서버 사이드 렌더링(SSR)이나 정적 사이트 생성(SSG) 시 Custom Elements 렌더링 방안을 고려해야 합니다.
결론
Custom Elements는 네이티브 HTML 요소처럼 동작하는 재사용 가능한 UI 컴포넌트를 JavaScript만으로 구축할 수 있게 하는 강력한 웹 표준 기술입니다. 이를 통해 개발 생산성을 높이고, 유지보수하기 쉬운 코드를 작성하며, 프레임워크에 구애받지 않는 유연한 개발을 할 수 있습니다.
참고 자료: