web components

김_리트리버·2022년 3월 18일
0

Web

목록 보기
5/5

정의

사용자가 정의할 수 있고, 재사용가능하고, 캡슐화 가능한 HTML 태그를 생성할 수 있는 웹 플랫폼 API

사용하는 이유

  • module 화 하여 코드를 적게쓸수 있고 , 재사용이 가능하여 유지보수하기 좋다.
  • 웹 표준 기술이기 때문에 react,vue,angular 등 라이브러리에 상관없이 사용할 수 있다.
  • 크롬, 엣지, 사파리, 파이어폭스, 삼성인터넷 등 메이저 브라우저에서 지원한다.
    • MS 도 버린 익스플로러는 하던지 말던지 상관안한다.

사용방법

Custom elements

일단 React 처럼 Component 를 만들어야 사용할 수 있으므로 Component 를 정의하고 window 에 등록한다.

등록하고 나면 일반적인 html tag 처럼 자연스럽게 사용할 수 있다.

class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);

// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', AppDrawer);

CustomElement JavaScript API 정의

  • HTMLElement 를 상속받았기 때문에 HTMLElement 의 API 는 전부 사용할 수 있다.
  • 이외의 추가적인 기능들을 public 으로 생성하면 된다.
  • setAttribute 으로 설정한 속성은 html attribute 로 설정된다.
  • this 는 AppDrawer 의 Dom element instance 인 <app-drawer> 이다.
class AppDrawer extends HTMLElement {

  // A getter/setter for an open property.
  get open() {
    return this.hasAttribute('open');
  }

  set open(val) {
    // Reflect the value of the open property as an HTML attribute.
    if (val) {
      this.setAttribute('open', '');
    } else {
      this.removeAttribute('open');
    }
    this.toggleDrawer();
  }

  // A getter/setter for a disabled property.
  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    // Reflect the value of the disabled property as an HTML attribute.
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Can define constructor arguments if you wish.
  constructor() {
    // If you define a constructor, always call super() first!
    // This is specific to CE and required by the spec.
    super();

    // Setup a click listener on <app-drawer> itself.
    this.addEventListener('click', e => {
      // Don't toggle the drawer if it's disabled.
      if (this.disabled) {
        return;
      }
      this.toggleDrawer();
    });
  }

  toggleDrawer() {
    ...
  }
}

customElements.define('app-drawer', AppDrawer);

customElement 생성 규칙

  • 케밥케이스로 html tag 생성해야 한다.
    'app-drawer'
  • 당연하게도 동일한 이름으로 여러 컴포넌트를 등록할 수 없다.
    • 바로 DOMException 뜬다.
  • jsx 처럼 <app-drawer/> 과 같이 사용할 수 없다.
    • <app-drawer></app-drawer> ****식으로 써야한다.

custom element reaction ( 생명주기 )

  1. constructor
    • instance 가 생성될 때 호출
    • 사용예시
      • 상태를 초기화할 때
      • 해당 customElement 를 shadow dom 으로 설정할 때
  2. connectedCallback
    • 요소가 DOM에 삽입될 때마다 호출
    • 리액트의 componentDidMount 와 유사함
    • 사용예시
      • fetch 등으로 외부 resource 가져오기
  3. disconnectedCallback
    • DOM에서 요소가 제거될 때마다 호출
    • 사용예시
      • connectedCallback 에서 설정한 타이머나 subscribe 등을 해제할 때
  4. attributeChangedCallback
    • 인자로 넘긴 속성이 변경되었을 때 호출
    • 사용예시
      • app-drawer 의 open 속성을 예로들면 기본값을 false 로 설정한 다음 open 속성이 true 로 변경되었을 때 css 를 변경시킨다던지 하면 좋을 것 같음
  5. adoptedCallback
    • customElement 가 새 요소로 이동되었을 때

생명주기에 넣은 함수들은 동기적으로 작동한다.

예를들어  el.setAttribute() 요소를 호출하면 브라우저는 즉시 attributeChangedCallback()을 호출된다.

disconnectedCallback()도 마찬가지로, el.remove() 을 사용해 요소가 DOM에서 제거된 직후 호출된다.

class AppDrawer extends HTMLElement {
  constructor() {
    super(); // always call super() first in the constructor.
    ...
  }
  connectedCallback() {
    ...
  }
  disconnectedCallback() {
    ...
  }
  attributeChangedCallback(attrName, oldVal, newVal) {
    ...
  }
}

속성 변경 관찰

customElement 는attributeChangedCallback 을 사용해 속성이 변경되었을 때 반응을 정의할 수 있다.

브라우저는 observedAttributes 에서 반환한 배열 에 나열된 속성이 변경될 때마다 attributeChangedCallback를 호출합니다 .

class AppDrawer extends HTMLElement {
  ...

  static get observedAttributes() {
    return ['disabled', 'open'];
  }

  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Only called for the disabled and open attributes due to observedAttributes
  attributeChangedCallback(name, oldValue, newValue) {
    // When the drawer is disabled, update keyboard/screen reader behavior.
    if (this.disabled) {
      this.setAttribute('tabindex', '-1');
      this.setAttribute('aria-disabled', 'true');
    } else {
      this.setAttribute('tabindex', '0');
      this.setAttribute('aria-disabled', 'false');
    }
    // TODO: also react to the open attribute changing.
  }
}

Creating an element that uses Shadow DOM

shadow dom 은 전체 dom tree 안에서 특정 부분을 분리시킬 수 있다.

customElement 에서 shadowDom 을 적용하려면 constructor 에서 설정하면 된다.

let tmpl = document.createElement('template');
tmpl.innerHTML = `
  <style>:host { ... }</style> <!-- look ma, scoped styles -->
  <b>I'm in shadow dom!</b>
  <slot></slot>
`;

customElements.define('x-foo-shadowdom', class extends HTMLElement {
  constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to the element.
    let shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.appendChild(tmpl.content.cloneNode(true));
  }
  ...
});

html 에 이런식으로 나온다.

<x-foo-shadowdom>
  <p><b>User's</b> custom text</p>
</x-foo-shadowdom>

<!-- renders as -->
<x-foo-shadowdom>
  #shadow-root
    <b>I'm in shadow dom!</b>
    <slot></slot> <!-- slotted content appears here -->
</x-foo-shadowdom>

Creating elements from a <template>

template 요소를 사용하면 parse 되었지만 페이지 로드 시 비활성화되며 런타임시 활성화될 수 있는 DOM 조각을 선언할 수 있다.

vue 처럼 미리 html 구조를 선언해 놓고 JS 에서 window 에 등록해 사용

html tag 와 JS API 를 분리해서 관리할 수 있는 장점이 있다.

<template id="x-foo-from-template">
  <style>
    p { color: green; }
  </style>
  <p>I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
</template>

<script>
  let tmpl = document.querySelector('#x-foo-from-template');
  // If your code is inside of an HTML Import you'll need to change the above line to:
  // let tmpl = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');

  customElements.define('x-foo-from-template', class extends HTMLElement {
    constructor() {
      super(); // always call super() first in the constructor.
      let shadowRoot = this.attachShadow({mode: 'open'});
      shadowRoot.appendChild(tmpl.content.cloneNode(true));
    }
    ...
  });
</script>

Styling a custom element

  • customElement 를 shadow dom 을 사용해 css scope 로 부터 분리하더라도 사용자가 원하면 style 을 수정할 수 있다.
  • css 우선순위는 shadow dom 내에서 정의된 스타일 보다 사용자가 정의한 style 이 우선한다.
<!-- user-defined styling -->
<style>
  app-drawer {
    display: flex;
  }
  panel-item {
    transition: opacity 400ms ease-in-out;
    opacity: 0.3;
    flex: 1;
    text-align: center;
    border-radius: 50%;
  }
  panel-item:hover {
    opacity: 1.0;
    background: rgb(255, 0, 255);
    color: white;
  }
  app-panel > panel-item {
    padding: 5px;
    list-style: none;
    margin: 0 7px;
  }
</style>

<app-drawer>
  <panel-item>Do</panel-item>
  <panel-item>Re</panel-item>
  <panel-item>Mi</panel-item>
</app-drawer>

Pre-styling unregistered elements

template 로 선언하고 아직 정의되지 않은 customElement 의 스타일을 지정할 수 있다.

사용자가 키보드등으로 정의되지 않은 customElement 에 focus 하는 것을 방지하는데 사용할 수 있다.

app-drawer:not(:defined) {
  /* Pre-style, give layout, replicate app-drawer's eventual styles, etc. */
  display: inline-block;
  height: 100vh;
  opacity: 0;
  transition: opacity 0.3s ease-in-out;
}

참고

How we use Web Components at GitHub | The GitHub Blog

https://github.com/github/github-elements

React vs. Web Components. Combining them is the genuine secret to… | by Marius Bongarts | JavaScript in Plain English

The Complete Web Component Guide: Custom Elements | by Marius Bongarts | JavaScript in Plain English

Web Component: Why You Should Stick to React | by Nathan Sebhastian | Bits and Pieces (bitsrc.io)

웹 컴포넌트 (naver.com)

https://www.webcomponents.org/introduction

Web Component 에 대하여 간략하게 알아봅시다. :: GoodBye World (tistory.com)

Shadow DOM v1: Self-Contained Web Components  |  Web Fundamentals  |  Google Developers

profile
web-developer

0개의 댓글