ข้ามไปยังเนื้อหา

LitElement

Lit เผยแพร่เป็น npm package เดียว ในโปรเจกต์ที่มี build tool คุณติดตั้งครั้งเดียว:

Terminal window
npm install lit

จากนั้น import ส่วนที่ต้องการ:

import { LitElement, html, css } from 'lit';

ใน playground runner ของเว็บไซต์นี้ เราใช้ CDN dynamic import (แพทเทิร์น async IIFE ที่คุณเคยเห็นในบทก่อนหน้า):

const { LitElement, html, css } = await import('https://esm.sh/lit');

LitElement คือ base class ที่ extend จาก HTMLElement มาตรฐาน ซึ่งให้สามสิ่งหลัก:

  1. Reactive properties — ประกาศใน static properties และ Lit จะ watch การเปลี่ยนแปลงเพื่อ schedule re-render
  2. Declarative render — เมธอด render() คืน TemplateResult จาก html tagged template literal
  3. Scoped styles — ประกาศใน static styles ด้วย css tagged template literal และ Lit จะนำเข้าสู่ Shadow DOM

การประกาศ property ใน static properties ทำให้ Lit รู้ว่าต้อง watch property นั้น:

static properties = {
count: { type: Number },
label: { type: String },
active: { type: Boolean },
};

เมื่อ this.count เปลี่ยนค่า Lit จะ schedule microtask update และเรียก render() โดยอัตโนมัติ

ถ้าโปรเจกต์ของคุณมี build step (TypeScript หรือ Babel) Lit ยังมี API แบบ decorator ที่เทียบเท่ากับ static properties และลงทะเบียน tag ในขั้นตอนเดียวด้วย @customElement:

import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('my-counter')
export class MyCounter extends LitElement {
@property({ type: Number })
count = 0;
render() {
return html`<p>Count: ${this.count}</p>`;
}
}

TypeScript ต้องตั้งค่า "experimentalDecorators": true ใน tsconfig.json (ปัจจุบันเป็นค่าที่แนะนำสำหรับ production) ส่วน Babel ต้องใช้ @babel/plugin-proposal-decorators เนื่องจาก playground ในหน้านี้ทำงานโดยไม่มี build step ตัวอย่างในหน้านี้จึงใช้ syntax แบบ static properties field แทน — ทั้งสองแบบ compile ออกมาให้พฤติกรรม reactive property เหมือนกันทุกประการ

render() ต้องคืน TemplateResult ที่สร้างด้วย html tagged template literal:

render() {
return html`
<p>${this.label}: ${this.count}</p>
<button @click=${() => this.count++}>Increment</button>
`;
}

Lit จะ diff TemplateResult นี้กับ DOM ที่มีอยู่ และอัปเดตเฉพาะส่วนที่เปลี่ยนแปลง

ใช้ css tagged template literal ใน static styles เพื่อกำหนด styles:

static styles = css`
:host { display: block; padding: 1rem; font-family: sans-serif; }
button { background: #6200ee; color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; }
p { margin: 0 0 8px; }
`;

Lit จะนำ stylesheet นี้เข้าสู่ Shadow DOM ของ element ทำให้ styles ไม่รั่วออกไปหรือถูกรบกวนจากภายนอก

เมื่อคุณเขียน this.count++ setter ที่ Lit สร้างให้จะเรียก requestUpdate() ภายในให้เอง การอัปเดตจะถูก batch ที่จังหวะ microtask ทำให้การเปลี่ยนแปลง property หลายครั้งใน tick เดียวกันสร้าง render pass เพียงครั้งเดียว

ดู component ที่รวม reactive properties, render และ scoped styles เข้าด้วยกัน:

ตัวเลือกBenefitCost
Reactive properties ของ Lit (static properties)Lit จัดการ dirty-checking, batching และ re-render ให้อัตโนมัติ ลด boilerplate ไปมากเมื่อเทียบกับการเขียนเองเพิ่ม runtime ประมาณ +5-6KB และมี behavior บางส่วน (เช่น microtask scheduling) ที่ต้องเรียนรู้เพิ่ม
จัดการ state และ re-render เองใน HTMLElementควบคุมทุก timing ได้เต็มที่ ไม่มี runtime เพิ่มต้องเขียน attribute parsing, dirty-checking และ DOM diffing เองทั้งหมดด้วยมือ
  • ลืมประกาศ property ใน static properties — ถ้าไม่ประกาศ Lit จะไม่ track ค่า ทำให้ตั้งค่าแล้ว UI ไม่ re-render ตาม
  • Mutate array/object ใน property โดยตรง เช่น this.items.push(x) แทนที่จะสร้าง reference ใหม่ — Lit เทียบค่าด้วย !== เป็นค่าเริ่มต้น จึงตรวจไม่พบว่าเปลี่ยนแปลง และไม่ re-render
  • ใส่ logic หนักไว้ใน render() โดยตรง แทนที่จะคำนวณ/cache ไว้ล่วงหน้า — เพราะ render() อาจถูกเรียกบ่อยกว่าที่คิด ทำให้ performance แย่ลงโดยไม่จำเป็น

💡 ตัวอย่างจากของจริง

Home Assistant — frontend ทั้งหมดของแพลตฟอร์ม home automation นี้สร้างจาก custom elements ที่ extend LitElement โดยตรง (home-assistant/frontend)

Google Material Web — component ของ Material Design ใช้ reactive properties และ scoped styles ของ LitElement เป็นแกนหลักในการ implement ทุกตัว (material-components/material-web)

การประกาศ property ใน static properties ทำอะไร?
html tagged template literal คืนค่าอะไร?
Lit กำหนดขอบเขต CSS ให้กับ shadow tree ของ component ได้อย่างไร?