Zeno ZENO

[Part 5] Web Components — Lifecycle Callbacks 완전 이해

connectedCallback, disconnectedCallback, attributeChangedCallback, adoptedCallback 4가지 라이프사이클의 실행 시점과 올바른 사용 패턴을 정리합니다.

[Part 5] Web Components — Lifecycle Callbacks 완전 이해

Part 4에서는 Template과 Slot을 사용해서 컴포넌트 구조를 유연하게 만드는 방법을 알아봤다.

이번 글에서는 Web Components의 Lifecycle Callbacks를 알아본다.

Lifecycle은 생명주기라는 뜻이다.

컴포넌트도 화면에 생성되고, 추가되고, 변경되고, 제거되는 흐름을 가진다.

이 흐름 중 특정 시점에 자동으로 실행되는 메서드가 Lifecycle Callback이다.

1. 생명주기란?

컴포넌트의 생명주기는 컴포넌트가 만들어지고 사라질 때까지의 과정이다.

간단히 보면 다음 흐름이다.

요소 생성
↓
DOM에 추가
↓
속성 변경
↓
DOM에서 제거

DOM은 HTML 문서를 JavaScript가 다룰 수 있게 만든 구조다.

즉 DOM에 추가된다는 것은 화면의 문서 구조 안에 요소가 들어간다는 뜻이다.

Web Components는 이 과정에서 특정 메서드를 자동으로 실행해준다.

2. Lifecycle Callback 종류

Custom Element에서 사용할 수 있는 대표적인 Lifecycle Callback은 다음과 같다.

  • connectedCallback()
  • disconnectedCallback()
  • attributeChangedCallback()
  • adoptedCallback()

이 중에서 가장 자주 사용하는 것은 connectedCallback(), disconnectedCallback(), attributeChangedCallback()이다.

3. connectedCallback()

connectedCallback()은 커스텀 요소가 DOM에 추가될 때 실행된다.

class MyMessage extends HTMLElement {
  connectedCallback() {
    console.log('DOM에 추가됨');
  }
}

customElements.define('my-message', MyMessage);

HTML에서 다음처럼 사용하면

<my-message></my-message>

브라우저가 <my-message>를 문서에 추가하면서 connectedCallback()을 실행한다.

보통 이 메서드 안에서 초기 화면을 렌더링한다.

4. connectedCallback()에서 화면 만들기

class MyButton extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <button>확인</button>
    `;
  }
}

customElements.define('my-button', MyButton);

this.innerHTML은 현재 커스텀 요소 내부에 HTML을 넣는다.

<my-button> 안에 실제 <button>을 넣는 것이다.

5. disconnectedCallback()

disconnectedCallback()은 커스텀 요소가 DOM에서 제거될 때 실행된다.

class MyMessage extends HTMLElement {
  disconnectedCallback() {
    console.log('DOM에서 제거됨');
  }
}

customElements.define('my-message', MyMessage);

예를 들어 다음 코드로 요소를 제거하면

const message = document.querySelector('my-message');

message.remove();

disconnectedCallback()이 실행된다.

6. disconnectedCallback()이 필요한 이유

컴포넌트가 사라질 때 정리해야 할 작업들이 있다.

  • 이벤트 제거
  • 타이머 제거
  • Observer 해제
  • 외부 구독 해제

이런 정리 작업을 하지 않으면 메모리 누수가 발생할 수 있다.

메모리 누수는 더 이상 필요 없는 데이터나 이벤트가 계속 남아있어서 브라우저 자원을 낭비하는 문제다.

7. 이벤트 정리 예제

class MyComponent extends HTMLElement {
  handleResize = () => {
    console.log('화면 크기 변경');
  }

  connectedCallback() {
    window.addEventListener('resize', this.handleResize);
  }

  disconnectedCallback() {
    window.removeEventListener('resize', this.handleResize);
  }
}

customElements.define('my-component', MyComponent);

코드 리뷰

window.addEventListener('resize', ...)는 브라우저 창 크기가 바뀔 때 실행할 이벤트를 등록한다.

removeEventListener()는 등록된 이벤트를 제거한다.

컴포넌트가 사라질 때 이벤트를 제거하지 않으면, 화면에 없는 컴포넌트의 코드가 계속 실행될 수 있다.

8. attributeChangedCallback()

AD

제휴 광고 · 일부 링크는 수수료를 받을 수 있습니다

애드픽 쇼핑메이트 회원가입

쇼핑정보로 재테크하는 꿀팁! 애드픽 쇼핑메이트

attributeChangedCallback()은 커스텀 요소의 attribute 값이 변경될 때 실행된다.

하지만 모든 attribute를 자동으로 감시하지는 않는다.

감시할 attribute를 먼저 지정해야 한다.

9. observedAttributes

observedAttributes는 변경을 감시할 attribute 목록을 반환한다.

class UserCard extends HTMLElement {
  static get observedAttributes() {
    return ['name'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    console.log(name, oldValue, newValue);
  }
}

customElements.define('user-card', UserCard);

return ['name']은 name attribute를 감시하겠다는 뜻이다.

name 값이 바뀌면 attributeChangedCallback()이 실행된다.

10. attributeChangedCallback()의 인자

attributeChangedCallback(name, oldValue, newValue) {

}

name은 변경된 attribute 이름이다.

oldValue는 변경 전 값이다.

newValue는 변경 후 값이다.

예를 들어 name이 “사용자 A”에서 “사용자 B”로 바뀌면 oldValue는 “사용자 A”, newValue는 “사용자 B”가 된다.

11. attribute 변경 시 화면 다시 그리기

class UserCard extends HTMLElement {
  static get observedAttributes() {
    return ['name', 'role'];
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback() {
    this.render();
  }

  render() {
    const name = this.getAttribute('name') || '이름 없음';
    const role = this.getAttribute('role') || '역할 없음';

    this.innerHTML = `
      <article>
        <h3>${name}</h3>
        <p>${role}</p>
      </article>
    `;
  }
}

customElements.define('user-card', UserCard);

코드 리뷰

connectedCallback()에서 처음 화면을 그린다.

attributeChangedCallback()에서 attribute가 바뀔 때 다시 화면을 그린다.

render()는 화면 출력 코드를 따로 모아둔 메서드다.

|| '이름 없음'은 값이 없을 때 기본값을 사용하기 위한 코드다.

12. adoptedCallback()

adoptedCallback()은 커스텀 요소가 다른 문서로 이동할 때 실행된다.

예를 들어 iframe이나 다른 document로 이동하는 특수한 상황에서 사용될 수 있다.

adoptedCallback() {
  console.log('다른 문서로 이동됨');
}

일반적인 컴포넌트 개발에서는 거의 사용하지 않는다.

존재만 알고 있어도 충분하다.

13. constructor와 connectedCallback의 차이

Custom Element class에서도 constructor를 사용할 수 있다.

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

    console.log('constructor 실행');
  }

  connectedCallback() {
    console.log('connectedCallback 실행');
  }
}

constructor는 객체가 생성될 때 실행된다.

connectedCallback은 요소가 DOM에 추가될 때 실행된다.

초기값을 준비하는 정도는 constructor에서 할 수 있다.

하지만 DOM을 조작하거나 화면을 렌더링하는 작업은 보통 connectedCallback에서 처리하는 것이 안전하다.

14. 실무에서 자주 쓰는 구조

class MyComponent extends HTMLElement {
  static get observedAttributes() {
    return ['value'];
  }

  connectedCallback() {
    this.render();
  }

  disconnectedCallback() {
    // 이벤트 정리
  }

  attributeChangedCallback() {
    this.render();
  }

  render() {
    // 화면 렌더링
  }
}

customElements.define('my-component', MyComponent);

이 구조는 많은 Web Components에서 기본 패턴처럼 사용된다.

15. 정리

Lifecycle Callbacks는 Web Components의 생명주기를 관리하는 메서드다.

  • connectedCallback()은 DOM에 추가될 때 실행된다.
  • disconnectedCallback()은 DOM에서 제거될 때 실행된다.
  • attributeChangedCallback()은 감시 중인 attribute가 변경될 때 실행된다.
  • observedAttributes는 감시할 attribute 목록을 지정한다.
  • adoptedCallback()은 다른 문서로 이동할 때 실행된다.
  • 실무에서는 render() 메서드를 따로 만들어 화면 갱신을 관리하는 경우가 많다.

다음 Part에서는 Custom Events를 다룬다.

Custom Events는 컴포넌트 내부에서 발생한 일을 외부로 알려주는 중요한 통신 방법이다.

AD

제휴 광고

일부 링크는 제휴 링크이며, 구매 또는 가입 시 일정 수수료를 받을 수 있습니다.

AD

'Web components' 카테고리의 다른 글

전체보기