LitElement
การ import LitElement
หัวข้อที่มีชื่อว่า “การ import LitElement”Lit เผยแพร่เป็น npm package เดียว ในโปรเจกต์ที่มี build tool คุณติดตั้งครั้งเดียว:
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 คืออะไร
หัวข้อที่มีชื่อว่า “LitElement คืออะไร”LitElement คือ base class ที่ extend จาก HTMLElement มาตรฐาน ซึ่งให้สามสิ่งหลัก:
- Reactive properties — ประกาศใน
static propertiesและ Lit จะ watch การเปลี่ยนแปลงเพื่อ schedule re-render - Declarative render — เมธอด
render()คืนTemplateResultจากhtmltagged template literal - Scoped styles — ประกาศใน
static stylesด้วยcsstagged template literal และ Lit จะนำเข้าสู่ Shadow DOM
Reactive Properties
หัวข้อที่มีชื่อว่า “Reactive Properties”การประกาศ property ใน static properties ทำให้ Lit รู้ว่าต้อง watch property นั้น:
static properties = { count: { type: Number }, label: { type: String }, active: { type: Boolean },};เมื่อ this.count เปลี่ยนค่า Lit จะ schedule microtask update และเรียก render() โดยอัตโนมัติ
ทางเลือกแบบ decorator
หัวข้อที่มีชื่อว่า “ทางเลือกแบบ decorator”ถ้าโปรเจกต์ของคุณมี 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()
หัวข้อที่มีชื่อว่า “เมธอด render()”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 ที่มีอยู่ และอัปเดตเฉพาะส่วนที่เปลี่ยนแปลง
Scoped Styles
หัวข้อที่มีชื่อว่า “Scoped Styles”ใช้ 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 ไม่รั่วออกไปหรือถูกรบกวนจากภายนอก
Reactive update เมื่อ property เปลี่ยนค่า
หัวข้อที่มีชื่อว่า “Reactive update เมื่อ property เปลี่ยนค่า”เมื่อคุณเขียน this.count++ setter ที่ Lit สร้างให้จะเรียก requestUpdate() ภายในให้เอง การอัปเดตจะถูก batch ที่จังหวะ microtask ทำให้การเปลี่ยนแปลง property หลายครั้งใน tick เดียวกันสร้าง render pass เพียงครั้งเดียว
ตัวอย่าง: LitCard
หัวข้อที่มีชื่อว่า “ตัวอย่าง: LitCard”ดู component ที่รวม reactive properties, render และ scoped styles เข้าด้วยกัน:
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
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)