iT邦幫忙

2025 iThome 鐵人賽

DAY 29
2
Modern Web

原生元件養成計畫:Web Component系列 第 29

Day 29: Web Component 的框架 Lit - Life cycle & Event

  • 分享至 

  • xImage
  •  

在前面兩篇文章,介紹了 Lit 的元件初始化屬性
完成靜態元件後,接下來要介紹 Lit 的生命週期(Life cycle)與事件(Event),讓元件可以動起來!

生命週期 Life Cycle

你是否還記得在 Day 6: Web Component 的生命週期 Life cycle 中有介紹過原生 Web Component 的生命週期呢?
原生的生命週期包含以下:

  • constructor() 當元件實例化時。
  • connectedCallback() : 當元件首次被加入 DOM 時。
  • disconnectedCallback() : 當元件從 DOM 移除時。
  • attributeChangedCallback(name, oldValue, newValue) : 當元件的屬性有改變時。
  • adoptedCallback() : 當元件被移到新的文件時。

Lit 除了繼承原生的自訂元素生命週期方法之外,還引入了 reactive update cycle(響應式更新周期),當響應式屬性改變時,它會渲染 DOM 的變化。

注意:在 Lit 中使用 connectedCallback 時,記得加入 super.connectedCallback()

Lit 中常用的響應式更新生命週期

  • update(changedProperties):當屬性或狀態變化即將導致畫面更新時觸發,通常用來初始化屬性或綁定方法(這時候還沒有建立起 Shadow DOM)。
  • render():Lit 會負責輸出 HTML Template,回傳 html 樣板(這階段 Shadow DOM 已建立完成)。
  • updated(changedProperties):畫面更新完畢後觸發(每次屬性變化都會觸發)
  • firstUpdated(changedProperties):僅在第一次渲染完成後觸發一次,通常用來抓 DOM 的節點。

生命週期的執行順序:

constructor() {
    super();
    this.count = 10;
    this.disabled = false;
    this.title = 'WC-Lit Counter'

    console.log('constructor')
  }

connectedCallback() {
  super.connectedCallback();
  console.log('connectedCallback');
}

update(changedProps: Map<PropertyKey, any>) {
  console.log('update');
  super.update(changedProps);
}

render() {
  console.log('render');
  return html`<button>Count: ${this.count}</button>`;
}

updated(changedProps: Map<PropertyKey, any>) {
  console.log('updated');
}

firstUpdated(changedProps: Map<string, any>) {
  console.log('firstUpdated');
}

這是初次掛載時 consloe 會看見的:

constructor -> connectedCallback -> update -> render -> firstUpdated -> updated

那接下來我們來看看後續屬性更新時會觸發的幾個生命週期,我們先在 firstUpdated 的週期中綁定按鈕事件:

firstUpdated(changedProps: Map<string, any>) {
  console.log('firstUpdated');
  
  // 模板第一次完成 render,此時已經可以操作 DOM 元素。
  this.shadowRoot!.querySelector('button')!
    .addEventListener('click', () => {
      this.count ++
    });
}

當我們按下畫面上的按鈕,屬性變化時,會觸發以下生命週期:

update -> render -> updated

以上大致是 Lit 生命週期的粗粗粗淺介紹,因為內容真的太多了,有興趣的讀者可以去看看 Lit-Lifecycle

另外補充:
Lit 其實還提供了 requestUpdate(name?, oldValue?),可以讓使用者手動觸發更新,而不是等屬性改變才更新。
適合使用在當屬性沒有透過 @property 控制,需要手動觸發更新時使用。

事件 Event

接下來我們來說說 Event 吧!

在上一篇文章中我們有提到了兩個 Lit 的模板表達式 expressions,分別是 ${}, ?
那這一篇我們就來說說 @event

在剛剛我們在 firstUpdated 的響應式更新生命週期中加入了事件綁定,而現在要使用 @event,直接在模板中綁定事件,像是 @click

<button @click=${this.handleClick}>Click me</button>

注意:其中 ${this.handleClick} 不用加 (),否則會立即執行。

完善 Counter-btn 元件

import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('counter-btn')
export class CounterBtn extends LitElement {
  @property({ type: Number, reflect: true }) count!: number;
  @property({ type: Boolean, reflect: true, }) disabled!: boolean;
  @property({ type: String,  reflect: true, }) title!: string;

  // 建構函數:初始化組件屬性
  constructor() {
    super();
    this.count = 10;
    this.disabled = false;
    this.title = 'WC-Lit Counter'
  }

  // 定義樣式
  static styles = css`
    // ...略
  `

  // 增加按鈕
  increment() {
    this.count++;
    this._dispatchCountChanged();
  }

  // 減少按鈕
  decrement() {
    this.count--;
    this._dispatchCountChanged();
  }

  // 重置
  reset() {
    this.count = 10;
    this._dispatchCountChanged();
  }

  // 發送自訂事件給外部
  private _dispatchCountChanged() {
    this.dispatchEvent(
      new CustomEvent('count-changed', {
        detail: { count: this.count },
        bubbles: true,
        composed: true,
      })
    );
  }

  // render
  render() {
    return html`
      <div class="counter-wrapper">
        <h3>${this.title}</h3>
        <div class="counter">
          <button class="decrement" @click=${this.decrement}>-</button>
          <span>Count: ${this.count}</span>
          <button class="increment" @click=${this.increment}>+</button>
        </div>
        <button class="reset" ?disabled=${this.disabled} @click=${this.reset}>
          Reset
        </button>
      </div>
    `;
  }
}

https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExemsyazNhMzV4dHRoYXdrb3ZtYTJlcGFpcG1hNGo3djhweWQza3JjbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/g54Tps8vwrOPRI4HGh/giphy.gif

透過這三天的文章,我們使用 Lit 完成了基本的 counter-btn。
其實關於 Lit 這個框架,還是有許多要學習的地方,也再請大家一起深入研究囉!


上一篇
Day 28: Web Component 的框架 Lit - @property
系列文
原生元件養成計畫:Web Component29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言