나의 개발 일지

생 javascript 기반 웹컴포넌트 만들어보기 테스트

designer DK 2024. 8. 10. 18:10
728x90

디자이너로써 디자인을 하면서도 컴포넌트는 굉장히 유용하게 쓰는 편인데 개발 공부를하면서도 컴포넌트 개념을 꼭 활용하고 싶었고 그러기 위해 리액트도 맛뵈기로 조금 공부를 해봤었다.

리액트에서의 컴포넌트는 정말 사용성이 편리했었는데 다만 다른 부분에서 아직 개발 초보가 접근하기 힘든 개념이 많았었다. (스테이츠 등등...) 그래서 좀 더 기본기를 다지면서 컴포넌트를 활용할 수 없을까 고민해봤었다.

즉 생 자바스크립트 기반으로 개발하면서 컴포넌트를 자유자재로 사용하고 싶었고 그것에 대한 좋은 솔루션이 바로 '웹 컴포넌트' 개념이었다.

요즘 최애하는 개발 유튜브 채널인 코딩애플 영상 중
- 깃헙 개발자들이 React 안쓰는 이유 : Web Component
https://www.youtube.com/watch?v=RtvSgptpfnY&t=210s

이 콘텐츠에서는 깃허브와 같은 방대한 사이즈의 사이트도 리액트와 같은 프레임워크 기반이 아닌 생 자바스크립트로 이루어져있고 그 관리 비법으로 '웹컴포넌트'를 알려주었다.

그래서 이번에 들어갈 서울시 유치원 현황 사이트 프로젝트를 시작으로 웹 컴포넌트를 사용해보려고 계획했고 그 계획을 이루기 위해 간단한 '웹컴포넌트 테스트 공부'를 진행해보았다.

컴포넌트의 큰 장점이라면 두가지가 아닐까 싶다.
1번 재사용성, 2번 속성 제어. 이 두가지가 된다면 바랄 것 없었다.

그래서 이번 테스트는 너무 고급기능까지 파고들기보다는 이 두가지 장점을 활용할 수 있을지에 대한 테스트였고 그 두 가지만 잘 된다면 바로 본 프로젝트로 들어가기로 결정했다.

테스트에 가장 많이 참고한 자료
- [JavaScript] - Web Component (with 문벅스 애플리케이션)
https://velog.io/@jinyoung234/JavaScript-Web-Component-with-%EB%AC%B8%EB%B2%85%EC%8A%A4-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98

그리고 코딩애플 유튜브 채널의
- 자바스크립트 숨겨진 기능 Shadow DOM 활용법
https://www.youtube.com/watch?v=o0spBNs0zRk

먼저 개별 js파일로 컴포넌트를 만들어보았다. html 덩어리를 컴포넌트화 할 수 있느냐에 대한 확인이 핵심이었음. 아주 간단하게 구성해보았다.

class CustomTitle extends HTMLElement {
    constructor() {
        super();
    }
    connectedCallback() {
        this.innerHTML = `<h1>Welcome, Web Component!</h1>`;
    }
}

customElements.define('custom-title', CustomTitle)


이런식으로 하면 html을 쉽게 컴포넌트화 할 수 있었다.

index.html에서 <script src="/components/CustomTitle.js"></script> 이런식으로 간단하게 컴포넌트를 불러올 수 있었고 body 안에 <custom-title></custom-title>와 같이 간단한 태그 입력으로 컴포넌트 사용을 할 수 있었다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
   
</head>
<body>
    <custom-title></custom-title>
   
    <script src="/components/CustomTitle.js"></script>
</body>
</html>

 

공부하다보니 웹컴포넌트의 독특한 특징이 컴포넌트 독자적인 캡슐화를 위해 '쉐도우돔(shadow DOM) 기능'이 존재하는 부분이었다. 이건 다른 컴포넌트나 파일로부터 클래스 같은 것들이 섞이지 않도록 막아주는 역할이다.

방금과 같이 이런 식의 구성도 좋긴한데

    connectedCallback() {
        this.innerHTML = `<h1>Welcome, Web Component!</h1>`;
    }
 


매번 ` `(어퍼스트로피) 안에 html을 써야해서 오타위험도 있고 자동완성도 안되어서 불편할 것 같긴 했다.

이런 부분을 보완할 수 있는 template이란 기능도 존재하는데 사용해보니 생각보다 좋았다. template이라는 태그는 html에서 자체적으로는 절대 렌더링 되지 않는 독특한 태그이다

template 태그 안에 html과 스타일을 작성 후

<template id="input-template">
        <style>
            .aaa {
                border: 2px solid red;
            }
            .input-container {
                display: flex;
                gap: 20px;
            }
        </style>
        <slot name="label-slot"></slot>
        <div class="input-container">
            <slot name="img-slot"></slot>
            <input class="aaa" type="text">
        </div>
    </template>


이런식으로 템플릿으로 가져오면 자바스크립트가 훨씬 깔끔해지면서도 html에서의 태그 작성도 훨씬 편했다.

class CustomInput extends HTMLElement {
    constructor() {
        super();

        this.attachShadow({ mode: 'open' });
       
        const template = document.getElementById('input-template')
        const content = template.content.cloneNode(true);
        this.shadowRoot.appendChild(content);
    }
}

customElements.define('custom-input', CustomInput)


그리고 탬플릿 안에 있는 스타일들은 이런식으로 '쉐도우돔'을 통해서 나오기 때문에 밖에있는 다른 태그들의 스타일에 전혀 영향을 주거나 받지 않는다.

마지막으로 내가 아까도 두번째로 짚었던 컴포넌트의 중요한 특징 중 하나인 '사용자가 정의하는 속성을 웹컴포넌트에서 쉽게 밖으로 꺼내서 쓸 수 있을지'에 대한 부분이 궁금했는데 이것도 찾아봤을 때 여러가지 복잡한 방법으로 리액트처럼 사용할 수 있게 하는 방법들도 있었지만...초보가 사용할 때 가장 쉽고 직관적인 기능으로 '슬롯(slot)'이라는 기능을 찾았고 이게 '컴포넌트별 자유로운 사용자 속성 제어'를 얼추 커버할 수 있겠다고 판단되었다.

<body>
    <custom-title></custom-title>
    <custom-input>
        <div class="bbb" slot="label-slot">라벨1</div>
        <img class="ccc" slot="img-slot" src="data/favicon.png" alt="">
    </custom-input>
    <custom-input>
        <div class="bbb" slot="label-slot">라벨2</div>
    </custom-input>
 
 
    <template id="input-template">
        <style>
            .aaa {
                border: 2px solid red;
            }
            ::slotted(.bbb) {
                color:blue;
            }
            ::slotted(.ccc) {
                width: 24px;
            }
            .input-container {
                display: flex;
                gap: 20px;
            }
        </style>
        <slot name="label-slot"></slot>
        <div class="input-container">
            <slot name="img-slot"></slot>
            <input class="aaa" type="text">
        </div>
    </template>
   
    <script src="/components/CustomTitle.js"></script>
    <script src="/components/CustomInput.js"></script>
</body>
</html>



그리고 혹시나 싶어서 다른 js파일에서 컴포넌트를 가져다와서 사용 가능한지도 추가적으로 테스트 해봤는데 잘 되는 것 같았다.

document.querySelector('#btn').addEventListener("click", function(){
    console.log("hi");
    document.querySelector('.insert').innerHTML = '<custom-input></custom-input>'
});
<body>
    <custom-title></custom-title>
    <div class="insert"></div>
    <custom-input>
        <div class="bbb" slot="label-slot">라벨1</div>
        <img class="ccc" slot="img-slot" src="data/favicon.png" alt="">
    </custom-input>
    <custom-input>
        <div class="bbb" slot="label-slot">라벨2</div>
    </custom-input>
    <button id="btn">버튼</button>
   


    <template id="input-template">
        <style>
            .aaa {
                border: 2px solid red;
            }
            ::slotted(.bbb) {
                color:blue;
            }
            ::slotted(.ccc) {
                width: 24px;
            }
            .input-container {
                display: flex;
                gap: 20px;
            }
        </style>
        <slot name="label-slot"></slot>
        <div class="input-container">
            <slot name="img-slot"></slot>
            <input class="aaa" type="text">
        </div>
    </template>
   
    <script src="/components/CustomTitle.js"></script>
    <script src="/components/CustomInput.js"></script>
    <script src="script.js"></script>
</body>
</html>



이번 웹 컴포넌트 테스트 프로젝트는 본 프로젝트 전에 생 자바스크립트 구현할 수 있는 웹컴포넌트를 만들고자 공부해 본 부분인데 생각보다 재미있고 유용한 기능이 많아서 활용도가 높을 것으로 판단되었다.
 

테스트 결과물



이제 본격적으로 본 프로젝트 기획 해보겠다!