프론트엔드 개발자의 기록 공간

[FE_Roadmap] Web Components 본문

개발지식

[FE_Roadmap] Web Components

[리우] 2024. 6. 2. 18:47

Web Components

  • 웹 컴포넌트는 그 기능을 나머지 코드로부터 캡슐화하여 재사용 가능한 커스텀 엘리먼트를 생성하고 웹 앱에서 활용할 수 있도록 해주는 다양한 기술들의 모음입니다.
  • 웹 표준 기술로 모든 브라우저에서 동작하고 플랫폼 간 호환성을 높여줍니다. (미지원 브라우저 제외 및 구버전 브라우저에서는 polyfill을 사용하여 지원 가능)
  • 다양한 프레임워크나 라이브러리에서 Web Components를 사용할 수 있기 때문에, 기술 스택에 구애받지 않고 사용할 수 있습니다.

특징

  • Custom elements
    • html tag 자체를 커스텀하게 만들고 browser 사용할 수 있도록 만들어 줍니다.
    • customElements.define 메소드를 이용
  • Shadow DOM
    • 다른 DOM과의 분리를 통해 DOM을 캡슐화합니다.
    • shadow dom 외부의 js는 접근이 안된다.
    • shadow boundary의 style은 외부로 영향을 미치지 않는다.
    • 브라우저 미 지원시 polyfill 사용
    • shadowRoot.querySelector() 메소드를 이용

Shadow Dom

  • HTML templates
    • <template><slot> 엘리먼트는 렌더링된 페이지에 나타나지 않는 마크업 템플릿을 작성할 수 있게 해줍니다. 그 후, 커스텀 엘리먼트의 구조를 기반으로 여러번 재사용할 수 있습니다.
    • content.cloneNode(true) 메소드를 이용
  • Declarative Shadow DOM

 

lifecycle

class MyCustomElement extends HTMLElement {
  conntectedCallback() {
    console.log("연결 완료")
  }
  disconnectedCallback() {
    console.log("연결 해제 완료")
  }
  adoptedCallback() {
    console.log("Element가 다른 page로 이동 하였습니다.")
  }
  attributeChangedCallback(name, oldValue, newValue) {
    console.log("name 이 변경 되었습니다.")
  }

  static get observedAttributes() {
    // 변경을 관찰하고자 하는 attribute를 나열한다.
    // 아래 반환값들이 변경되면 `attributeChangedCallback` callback이 호출된다.
    return ["autofocus", "disabled", "form", "value"]
  }
}

connectedCallback

  • 요소가 DOM에 추가되면 메서드가 트리거됩니다.
  • 리소스를 가져오고, 설정 코드를 실행하거나 템플릿을 렌더링할 수 있습니다.
  • react useEffect와 비슷한듯? 대신 렌더링 시점과 DOM 추가 시점이 다른 듯..
class MyCustomElement extends HTMLElement {
  connectedCallback() {
    console.log("connected")
  }
}

customElements.define("my-custom-element", MyCustomElement)

const myCustomElement = new MyCustomElement()

document.body.appendChild(myCustomElement)
document.body.appendChild(myCustomElement)

// result:
// 'connected'
// 'connected'

disconnectedCallback

  • 요소가 DOM에서 제거될 때 트리거됩니다.
  • DOM 이벤트 구독 취소
  • 간격 타이머 중지
  • 글로벌 또는 애플리케이션 서비스에 등록된 모든 콜백 등록 취소
  • react useEffect의 return과 비슷한듯?
class MyCustomElement extends HTMLElement {
  disconnectedCallback() {
    console.log("disconnected from the DOM")
  }
}

customElements.define("my-custom-element", MyCustomElement)

document.querySelector("my-custom-element").remove() // 'disconnected from the DOM'

attributeChangedCallback(attrName, oldVal, newVal)

  • name: 속성의 이름을 나타냅니다. oldValue: 속성의 이전 값을 나타냅니다. newValue: 속성의 새 값을 나타냅니다.
  • attributeChangedCallback속성이 추가, 제거, 업데이트 또는 교체되거나 구성 요소 인스턴스가 업그레이드될 때 트리거됩니다.
  • 추적할 속성은 static get observedAttributes속성 이름 배열을 반환하는 메서드에 지정됩니다.
  • react 의존성 배열 deps와 비슷한듯?
<my-custom-element
  prop1="foo"
  prop2="bar"
  prop3="baz">
</my-custom-element>

...
class MyCustomElement extends HTMLElement {
  static get observedAttributes() {
    return ["prop1", "prop2", "prop3"]
  }

  attributeChangedCallback(name, oldValue, newValue) {
    console.log(
      `${name}'s value has been changed from ${oldValue} to ${newValue}`,
    )
  }
}

adoptedCallback()

  • <iframe/> iframe과 같은 외부 요소에서 사용됩니다.
  • 이러한 일이 발생하면 adoptedCallback수명 주기 후크가 트리거됩니다. 이를 사용하여 소유자 문서, 기본 문서 또는 기타 요소와 상호 작용할 수 있습니다.

실제 사용 예시

  • 카운터 구성 요소에는 다음이 포함됩니다.
  • 현재 값을 증가시키는 증가 버튼
  • 현재 값을 감소시키는 감소 버튼
  • 현재 값을 표시하는 레이블
  • "value" 속성을 통해 기본값을 설정할 수 있어야 합니다.
const template = document.createElement("template")
template.innerHTML = `
  <button id="increaseBtn">+</button>
  <span id="label"></span>
  <button id="decreaseBtn">-</button>
`

export class CounterComponent extends HTMLElement {
  // define the observedAttributes array
  static get observedAttributes() {
    return ["value"]
  }

  // define getters and setters for attributes
  get value() {
    return this.getAttribute("value")
  }

  set value(val) {
    if (val) {
      this.setAttribute("value", val)
    } else {
      this.removeAttribute("value")
    }
  }

  // DOM 요소 참조 변수
  $increaseButton
  $decreaseButton
  $label

  constructor() {
    super()

    // Shadow DOM 사용
    this.attachShadow({ mode: "open" })
    this.shadowRoot.appendChild(template.content.cloneNode(true))

    // DOM 요소에 대한 참조를 설정합니다.
    this.$increaseButton = this.shadowRoot.querySelector("#increaseBtn")
    this.$decreaseButton = this.shadowRoot.querySelector("#decreaseBtn")
    this.$label = this.shadowRoot.querySelector("#label")
  }
  connectedCallback() {
    // 양쪽 버튼에 이벤트 리스너 추가
    // 리스너의 콜백에 "this"를 바인딩하여 컴포넌트의 스코프를 연결합니다.
    this.$increaseButton.addEventListener("click", this._increase.bind(this))
    this.$decreaseButton.addEventListener("click", this._decrease.bind(this))
  }
  disconnectedCallback() {
    // 양쪽 버튼에서 이벤트 리스너 제거
    this.$increaseButton.removeEventListener("click", this._increase.bind(this))
    this.$decreaseButton.removeEventListener("click", this._decrease.bind(this))
  }

  attributeChangedCallback(name, oldValue, newValue) {
    this.$label.innerHTML = newValue
  }

  adoptedCallback() {
    console.log("I am adopted!")
  }

  _increase() {
    const value = +this.value
    this.value = String(value)
  }

  _decrease() {
    const value = +this.value
    this.value = String(value)
  }
}

 

https://ultimatecourses.com/blog/lifecycle-hooks-in-web-components

 

브라우저 호환성

  • 웹 컴포넌트는 기본적으로 Firefox (버전 63), Chrome, 및 Opera 에서 지원됩니다.
  • Safari 는 많은 웹 컴포넌트 기능을 지원하지만 위 브라우저들만큼은 아닙니다.
  • Edge 는 구현 작업중입니다.

728x90
Comments