shadow DOM과 template으로 HTML 모듈화하기

조 은길·2022년 6월 18일
1

Javascript 정리

목록 보기
47/48
post-thumbnail

단순한 것같지만, 열면 복잡한 HTML 태그가 있는데

<input type="range">

이런거 쓰면 되게 복잡한 레이아웃이 나온다.

구현원리를 생각해보면, <div> 박스를 여러개 중첩해서 만들어놓은 것같지 않나??

실제로도, <div> 박스를 2-3개 겹쳐서 만든 거다.

왜 이런 것들이 가능한 거냐면, shadowDOM이라는 기술 덕분이다.

실제로도 이게 <div>로 구성되있는지 증명하기 위해서, 이거를 개발자 도구에서 확인해보면, 진짜로 <div> 가 숨겨져 있는 것을 확인할 수 있다.

근데, 지금은 안 보일 거다. 숨겨져 있거든요.


shadow DOM을 개발자도구에서 확인하는 법

이걸 진짜 확인하고 싶으면 크롬 개발자도구 - 설정 - show user agent shadow DOM 켜기 이런거 체크해놓으면 됩니다.

<input type="range"> 안에 진짜로 <div> 3개가 숨어있다.

이거를 전문용어로 shadow DOM 이라고 하는데, 이런 것들은 개발자 편하라고 만들어 놓은 거다.

shadow DOM : "일반적으로는 볼 수 없는 숨겨진 HTML"을 의미한다.

여러분들이 직접 <div>를 3개 만들 필요없이, 그냥 <input>만 하나 딸랑쓰면, <div> 가 3개 생성이 되도록 코드가 짜여져있다.

그래서, 평소에는 shadowDOM 몰라도 HTML 코드 잘 짤 수있다.

여러분도 이런거 만들어서 HTML 숨길 수 있다.


shadow DOM 만드는 법

이제 shadow DOM을 직접 만들어보자.

shadow DOM을 이용해서, HTML 태그 안에다가 몰래 HTML 태그를 숨겨놔보도록 하자.

이걸 왜 하냐고?? 간지나서!! 실제로 쓸 데는 없다.

예를 들어서,

<div> 안에다가 shadow DOM을 부착하고 싶다면??

shadowRoot라는 걸 하나 생성해줘야한다. 거기다가 HTML을 숨길 수가 있다.

shadowRoot를 부착하는 방법은 attachShadow();를 써주면 된다.

attachShadow();는 JS의 기본기능이다.

그 다음에 거기 안에다가 "커스텀 HTML 엘리먼트들을 집어넣고 싶다 " 그러면??

우리가 만들어놓은 shadowRoot라는 공간을 우선 선택해야 한다.

그 다음에 . 점을 찍어서 innerHTML을 바꾸든 자유롭게 하면 된다.


  • shadow DOM 예시)
<div class="mordor"></div>
<script>
  document.querySelector('mordor').attachShadow({mode : 'open'});
  document.querySelector('mordor').shadowRoot.innerHTML = '<p>심연에서왔도다</p>'
</script>

이러면 <div> 안에 몰래 <p>를 숨길 수 있다.

  1. 우선 attachShadow() 라는걸 써서 shadowRoot 라는 공간을 하나 만들어준다.

  1. shadowRoot 여기에 원하는 html 넣으면 숨겨진다.

거의 모든 <태그>는 shadowRoot를 오픈할 수 있고

shadowRoot안에 넣은걸 전부 shadow DOM 이라고 칭하는데

이걸 굳이 써야할 이유를 모르겠고 별 장점은 없어보이지만, web component 문법과 합해야 진가를 발휘한다.
=> web component 문법과 조합을 하면, 완벽한 모듈화가 가능하다.


Web Component의 단점 : 스타일 오염

지난 시간에 했던 예제를 소환해보자!!

label, input을 한 단어로 축약해서 쓰고 싶다면 web components라는 문법을 갖다쓰면 된다.

그냥 정해진 class와 함수 형식에 맞춰주고, 그 안에다가 HTML을 자유롭게 작성하면 된다.

이 커스텀 HTML이 커스텀 단어 하나로 축약이 된다고 했다.

그런데, 이렇게 쓰다가 태그들에게 style을 입히고 싶어진거다. 그러면, HTML 안에다가 직접 style을 집어넣을 수도 있다.

이런 식으로 작성하면, label, input, style 태그를 한 단어로 축약했기 때문에, 이 커스텀 태그를 갖다쓸 때마다 label에 빨간색이 씌워져서 나오겠죠??

근데, 이 방법은 약간 위험하다.

왜냐면, label 태그를 다른 데에서 갖다 쓸 데에도 빨간 색이 적용되서 나온다.


=> 다른 label 태그까지 오염시키고 있다.

이러면, 컴포넌트를 만들어서 쓰는 의미가 없다. 또한, 컴포넌트가 많아지면, style이 겹치기도 한다.

이런 현상을 피하고 싶다면, class명을 태그마다 유니크하게 집어넣던지해야 되는데, 그건 또 귀찮다.

그리고 다른 컴포넌트들끼리 class 중복이 생기면 어떻하죠??
=> 이런 많은 어려움들이 존재합니다.

그래서, style 태그를 커스텀 HTML에 같이 넣고 싶다면??

정확히 이렇게 해줘야 편리하게 쓸 수있다.

shadowDOM에다가 이걸 다 집어넣는 거다.

커스텀 태그들을 shadowDOM이라는 공간에 집어넣게 되면,

<style>같은 경우, shadowRoot 안에서만 존재한다. 즉, CSS를 막짜도 된다.

  • 정리하면,
    • CSS로 class를 만들어서 해결한다고 해도, 다른 곳과 class가 겹치면 문제가 생기고 아무튼 귀찮은 문제들이 발생한다.
      그럴 땐 스타일을 shadow DOM 열어서 거기 집어넣으면 된다.
      왜냐면 shadow DOM에 있는 스타일은 밖에 영향을 끼치지 않아서 그렇다.

shadowDOM 안에 web components를 넣어보자


class 클래스 extends HTMLElement {
  connectedCallback() {
    this.attachShadow({mode : 'open'});
    this.shadowRoot.innerHTML = `<label>이름을 입력하쇼</label><input>
      <style> label { color : red } </style>`
  }
}

customElements.define("custom-input", 클래스);
  

<custom-input></custom-input>
<label>오 이제 바깥건 안빨개짐</label>
  

스타일과 태그들을 전부 shadow DOM으로 집어넣어놨더니, 진짜 다른 태그들 스타일을 오염시키지 않는다.

그래서 대부분 Web Component 만들 때 shadow DOM을 활용한다.

이래야 진정한 의미의 html 모듈화 개발이 가능하다.

다른 모듈들이 서로 영향을 끼치는걸 막을 수 있으니까...

그런데, 지금처럼 이렇게 코드를 짜면, HTML 코드가 되게 드럽게 않나요??

그래서, 이 HTML을 다른 곳으로 뺼 수가 있다.

HTML을 이런 태그에다가 임시로 보관했다가 그걸 this.shadowRoot.innerHTML에 붙여넣기하는 식으로 개발이 가능하다.

이런 기능을 실현시켜주는 HTML 태그를 <template>라고 한다.


html 임시보관함 <template> 태그

컴포넌트 만들 때 html이 너무 길어지면, <template>태그에 잠깐 보관해두고 집어넣을 수도 있다.

<template>에 작성한 HTML 태그들은 HTML 페이지에 렌더링되지 않는다.

그래서, shadowDOM이라던지, web components의 커스텀 엘리먼트 안에다가 집어넣고 싶은 긴 HTML 태그들이 있다고 하면, 일단 <template>로 감싼다.

그 다음 <template>에 id를 지정하자!!


<custom-input></custom-input>

  <template id="template1">
    <label>이메일을 입력하쇼</label><input>
    <style>label { color : red }</style>
  </template>

  <script>
    class 클래스 extends HTMLElement {
      connectedCallback() {
        this.attachShadow({mode : 'open'});
        this.shadowRoot.append(template1.content.cloneNode(true));
      }
    }
    customElements.define("custom-input", 클래스);
  </script>
  
  1. <template>은 특수한 태그인데 여기에 적은 html은 렌더링되지 않는다.

  2. 그래서 거기에 html들 잠깐 보관하고

  3. this.shadowRoot.append(template1.content.cloneNode(true)) 이런 식으로 집어넣으면 된다.

그냥 <template>태그 사용법이다.

이러면 html이 길어져도 이쁘게 코드짤 수 있겠다.


근데, 실제로 웹 개발을 이렇게 생으로 하는 것보다 라이브러리 갖다 쓰는게 더 좋다.

나는 더 나아가서 웹 앱을 만들고 싶다??

그러면, React나 Vue를 쓰면 된다.


이벤트 리스너를 부착하고 싶다면

그냥 addEventListener 아무데나 코드짜서 부착하면 된다.

심지어 shadow DOM에도 이벤트리스너 부착 가능하다.

알아서 하자.

  
<custom-input></custom-input>

  <template id="template1">
    <label>이메일을 입력하쇼</label><input>
    <style>label { color : red }</style>
  </template>

  <script>
    class 클래스 extends HTMLElement {
      connectedCallback() {
        this.attachShadow({mode : 'open'});
        this.shadowRoot.append(template1.content.cloneNode(true));
        let el = this.shadowRoot.querySelector('label');
        el.addEventListener('click', function(){
          console.log('클릭함')
        })
      }
    }
    customElements.define("custom-input", 클래스);
  </script>

shadow DOM 안의 label 태그를 누르면 콘솔창에 '클릭함' 출력하는

이벤트리스너를 부착해봤다.


=> 이것도 복잡해보이면?? 함수도 빼면 된다.

이런 식으로 개발하면 자바스크립트도 컴포넌트안에 담아서 보관할 수도 있다.

이제 원하는 곳에서 class 만 export 해서 가져다 쓰면 컴포넌트로 모듈식 개발이 가능하다.

profile
좋은 길로만 가는 "조은길"입니다😁

0개의 댓글