Web Component
2021.12.25
1. 웹 컴포넌트에 대해
컴포넌트(Component)의 사전적 단어 뜻은 '요소, 부품' 이라는 뜻을 담고 있다.
프로그래밍에서는 컴포넌트는 재사용이 가능한 최소한의 독립된 모듈을 뜻한다.
코드를 짜다 보면 어느 순간에 반복되는 코드들에 대해 실증이 나게 된다.
프론트엔드 부분에서는 같은 HTML
,CSS
를 공유하지만 다른 데이터를 사용하는 경우가 있다.
이 이미지는
Bootstrap
을 이용해 카드를 생성한 것이다.
카드를 보면 내용만 같고HTML
,CSS
구조는 같은 것을 볼 수 있다.
이러한 부분을 해결하고 싶었고 대표적인 방법은 React.js
, Vue.js
등 여러 프론트엔드 프레임 워크를 사용하는 방법이 있다.
프론트엔드 프레임워크들은 컴포넌트를 통해서 중복되는 코드를 재사용하게 하고 상태 관리를 통해 효율적으로 관리를 하게 도와준다.
처음에는 이러한 코드 재사용에 관한 문제를 프레임워크를 통해서 찾으려 했었지만 이러한 프레임워크에 대해 의존성이 높아지지 않고 언어만으로 컴포넌트의 개념을 사용하고 싶었고 역시나 이미 해결법은 있었다.
내가 찾은 방법은 vanilla JS
를 통해 코드를 재사용 하는 방법으로 Web Components
(웹 컴포넌트)이다.
웹 컴포넌트는 재사용 가능한 Custom Element를 생성하고 웹 앱에서 활용이 가능하게 해준다.
아래와 같이 카드를 두가지를 만든다고 할때 위에 파란색제목 카드는 일반적인 코드로 구성한 경우고 아래쪽 빨간색 제목 카드는 웹 컴포넌트를 사용해 코드를 구성한 경우 이다.
-
일반적으로 코드를 구성할 경우 (파란색)
<!-- style --> <style> h1 { color: blue; } .card { border: 1px solid black; border-radius: 5px; width: 250px; height: 120px; background-color: beige; padding: 20px; margin: 5px; float: left; } </style> <!-- body --> <div class="card"> <h1>Hello World</h1> <p>Test Card</p> </div> <div class="card"> <h1>Hello World</h1> <p>Test Card</p> </div>
-
웹 컴포넌트를 사용할 경우 (빨간색)
<!-- 커스텀 템플릿 --> <custom-card></custom-card> <custom-card></custom-card> <!-- 템플릿 --> <template id="temp1"> <div class="card"> <h1>Hello World</h1> <p>Test Card Components</p> </div> <style> h1 { color: red; } .card { border: 1px solid black; border-radius: 5px; width: 250px; height: 120px; background-color: antiquewhite; padding: 20px; margin: 5px; float: left; } </style> </template> <script> class CustomCard extends HTMLElement { connectedCallback() { this.attachShadow({ mode: "open" }); this.shadowRoot.append(temp1.content.cloneNode(true)); } } customElements.define("custom-card", CustomCard); </script>
단순히 코드 자체를 보면 위에 코드처럼 그냥 직접 html
에 짜는게 낳을 수 있어 보이지만 만약 이 card
라는 컴포넌트가 여러개일 경우는 아래와 같이 짜는게 효율 적이다.
쇼핑몰 사이트를 구성하다고 할때 상품 리스트를 상품하나당 card
에 넣어서 한페이지에 50개의 card
를 브라우저에 보여진다고 했을 경우 코드의 길이가 몇배로 차이가 나게 된다.
그리고 만약 card
의 구조가 바뀌게 된다고 했을때 "웹 컴포넌트" 의 경우 단 한번만 바꾸어주어 모든 card
를 바꿀 수 있지만 위의 코드는 card
의 개수 만큼 바꾸어 주게 된다.
또한 위의 코드에서는 나오지 않았지만 JavaScript
로 반복문으로 백엔드에서 값을 받아오는데로 화면에 넣어 줄 수 있어서 React.js
, Vue.js
와 같이 상태 관리도 할 수 있다.
2. 웹 컴포넌트 만들기
웹 컴포넌트는 아래 코드와 같이 클래스로 생성한다.
<만든-태그명></만든-태그명>
<script>
class 클래스명 extends HTMLElement{
connectedCallback(){
this.innerHTML = ``
}
}
customElements.define("만든-태그명", 클래스명);
</script>
HTMLElement
을 자식으로 갇는 Class를 만들어 준 다음 사용자 정의
요소가 문서의 DOM에 처음 연결될 때 호출되는 connectedCallback()
안에
HTML를 삽입하는 코드를 만들면 된다.
그리고 그 클래스를 customElements.define("만든-태그명", 클래스명)
로
내가 원하는 태그로 정의해 줄 수 있다.
이제 <만든-태그명></만든-태그명>
으로 태그를 정의해서 사용을 할 수 있다.
단, 여기서 커스텀 태그명에는 규칙이 하나 있는데 - (하이픈)
으로 이름을 연결하여 사용해야 한다.
-
예제 코드
<custom-text></custom-text> <script> class customClass extends HTMLElement { connectedCallback() { this.innerHTML = ``; } } customElements.define("custom-text", customClass); </script>
-
createElement를 사용한 예제 코드 (결과 동일)
위의 코드와실행 결과는 같지만
createElement
를 사용하여 태그를 일일이 생성하고 각 태그에 내용을 넣어 주는 방식을 사용할 수 있다. 이 방식을 사용 하면HTML
의 생성 속도가 조금 빨라진다고 한다. 하지만 이 방식을 사용하기에는 너무 번거럽다.
<custom-text></custom-text> <script> class customClass extends HTMLElement { connectedCallback() { let custom_h1 = document.createElement("h1"); custom_h1.innerHTML = "커스텀 태그 제목 입니다."; this.appendChild(custom_h1); let custom_p = document.createElement("p"); custom_h1.innerHTML = "커스텀 태그 내용 입니다."; this.appendChild(custom_p); } } customElements.define("custom-text", customClass); </script>
파라미터 사용하기
위에서 정의한 커스텀 엘리먼트들에 속성값으로 파라미터를 전달해서 각 컴포넌트마다 다르게 값을 출력 할 수 있다.
<custom-text data-contents="HTML"></custom-text>
<custom-text data-contents="CSS"></custom-text>
<custom-text data-contents="JavaScript"></custom-text>
<script>
class customClass extends HTMLElement {
connectedCallback() {
let content = this.dataset.contents;
this.innerHTML = ``;
}
}
customElements.define("custom-text", customClass);
</script>
위 코드는 dataset를 파라미터로 전달하여 dataset에 저장되어 있는 값으로 화면에 출력 하도록 하였다.
이 방식을 이용해서 dataset뿐만 아니라 다른 속성값으로도 값을 전달 할 수 있다.
-
태그 밖에있는 값을 넣는 법
<custom-text data-params="0"></custom-text> <custom-text data-params="1"></custom-text> <custom-text data-params="2"></custom-text> <script> const array = ["HTML", "CSS", "JavaScript"]; class customClass extends HTMLElement { connectedCallback() { let params = parseInt(this.dataset.params); this.innerHTML = ``; } } customElements.define("custom-text", customClass); </script>
결과 코드는 위와 동일하다.
따로 외부의 값을 위와 같이 전달할 수 있다.
3. 쉐도우 돔 (ShadowDOM)
웹 컴포넌트를 만들어서 사용하다보면 css
가 겹치는 것을 알 수 있다.
컴포넌트 밖이 아닌 컴포넌트안의 코드에다가 스타일을 지정해 주어도 컴포넌트 밖의 코드에도 영향을 주게 된다.
그렇기 때문에 웹 컴포넌트를 캡슐화 하는 것이 필요하다.
ShadowDOM은 이러한 캡슐화의 방법이며, 숨겨지고 분리된 DOM을 엘리먼트에 달아서 해결 할 수 있다.
우선 이 ShadowDOM을 크롬 개발자모드에서 확인하기 위해서는 크롬에서 Show User agent shadow DOM 설정을 해주어야 한다.
이렇게 설정하고 나서 ./src/03-example/03-example1.html
를 실행 하고 개발자 모드를 보면 <div>
태그 안에 다음과 같이 ShadowDOM안에 <p>
태그가 숨겨져 있는 것을 볼 수 있다.
결과 화면 또한 Hello
로 잘 나오게 된다.
3.1. ShadowDOM 사용하기
ShadowDOM의 사용법은 간단하다.
attachShadow
로 open, closed 옵션중 선택shadowRoot
에 내가 넣으려는 HTML을 삽입
attachShadow
옵션open
을 사용하면 JavaScript로ShadowDOM
에 대해 액세스 할 수 있게 되고 closed를 사용하면 액세스 할 수 없게 된다. (closed
모드에서shadowRoot
를 값을 호출시null
을 반환한다.)
이것에 대한 자세한 설명은 여기에서 확인 할 수 있다.
-
예제 코드
<div id="app"></div> <script> document.querySelector("#app").attachShadow({ mode: "open" }); document.querySelector("#app").shadowRoot.innerHTML = ``; </script>
이 코드를 실행 하면 위에서 본 것과 같이 개발자모드에서 shadowDOM을 확인 할 수 있다.
3.2. ShadowDOM에 HTML, CSS넣기
<p>일반 태그 내용 입니다.</p>
<custom-text></custom-text>
<script>
class customClass extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = ``;
}
}
customElements.define("custom-text", customClass);
</script>
웹 컴포넌트를 만드는 법에 ShadowDOM
생성 방법을 넣어서 위의 코드와 같이 HTML,CSS를 넣어 주면 다음과 같이 나오게 됩니다.
<p>
태그에 CSS
를 주었지만 ShadowDOM
에서 넣은 값은 ShadowDOM
에만 적용되고 외부의 <p>
태그에는 적용되지 않는 모습을 볼 수 있습니다.
3.3. 이벤트 리스너 사용하기
<p>일반 태그 내용 입니다.</p>
<custom-text></custom-text>
<script>
class customClass extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = ``;
this.addEventListener("click", () => alert("알림창 입니다!"));
}
}
customElements.define("custom-text", customClass);
</script>
JS의 이벤트 리스너를 작동 하고 싶을 때는 HTML
를 작성할 때와 같이 this.addEventListener
으로 작성을 하면된다.
3.4. 템플릿 사용하기
innerHTML
을 통해서 값을 넣게 되면 코드의 가독성이 떨어지게 됩니다.
template
을 만들어서 이러한 문제를 해결 할 수 있습니다.
<template>
태그에 컴포넌트화할 코드를 작성하고 id값을 지정한 다음에 this.shadowRoot.append(템플릿id이름.content.clonNode(true))
의 형식으로 innerHTML
처럼 template
에 있는 코드를 커스텀 엘리먼트로 화면에 출력 할 수 있습니다.
-
템플릿 코드
<custom-element></custom-element> <template id="templateId"> <p>템플릿 사용 하기</p> <style> p { color: red; } </style> </template> <script> class customClass extends HTMLElement { connectedCallback() { this.attachShadow({ mode: "open" }); this.shadowRoot.append(templateId.content.cloneNode(true)); } } customElements.define("custom-element", customClass); </script>