Lit untuk Developer React

Pengertian Lit

Lit merupakan kumpulan library open source dari Google yang dapat membantu developer membuat komponen yang cepat dan ringan serta berfungsi di semua framework. Dengan bantuan Lit, Anda dapat membuat komponen yang bisa dibagikan, membuat aplikasi, sistem desain, dan masih banyak lagi.

Yang akan Anda pelajari

Cara menerjemahkan beberapa konsep React ke Lit:

  • JSX & Pembuatan Template
  • Komponen & Properti
  • Status & Siklus Proses
  • Hook
  • Turunan
  • Referensi
  • Status Mediasi

Yang akan Anda buat

Di akhir codelab ini, Anda dapat mengonversi konsep komponen React ke analog Lit.

Yang dibutuhkan

  • Chrome, Safari, Firefox, atau Edge versi terbaru.
  • Pengetahuan tentang HTML, CSS, JavaScript, dan Chrome DevTools.
  • Pengetahuan tentang React
  • (Lanjutan) Jika Anda menginginkan pengalaman pengembangan terbaik, download VS Code. Anda juga akan memerlukan lit-plugin untuk VS Code dan NPM.

Konsep dan kemampuan inti Lit hampir serupa dengan React dalam berbagai hal. Namun, Lit juga memiliki beberapa perbedaan dan pembeda utama:

Kecil

Ukuran Lit sangat kecil yaitu sekitar 5 kb yang diperkecil dan di-gzip dibandingkan dengan ukuran 40+ kb dari React + ReactDOM.

Diagram batang ukuran paket diperkecil dan dikompresi dalam kb. Kolom lit sebesar 5 kb dan React + React DOM sebesar 42,2 kb

Cepat

Pada tolok ukur publik yang membandingkan sistem pembuatan template Lit, lit-html, hingga VDOM React, lit-html muncul 8-10% lebih cepat daripada React dalam kasus terburuk dan 50%+ lebih cepat dalam kasus penggunaan yang lebih umum.

LitElement (class dasar komponen Lit) menambah overhead minimal untuk lit-html, tetapi mengalahkan performa React sebesar 16-30% saat membandingkan fitur komponen seperti penggunaan memori, interaksi, serta waktu startup.

diagram batang dari performa yang dikelompokkan membandingkan lit dan React dalam milidetik (lebih rendah)

Tidak memerlukan build

Dengan fitur browser baru seperti modul ES dan literal template yang diberi tag, Lit tidak memerlukan kompilasi untuk berjalan. Hal ini berarti lingkungan developer dapat disiapkan dengan tag skrip + browser + server, dan Anda siap untuk memulai.

Dengan modul ES dan CDN modern seperti Skypack atau UNPKG, Anda mungkin tidak memerlukan NPM untuk memulai.

Namun, jika mau, Anda tetap dapat membuat dan mengoptimalkan kode Lit. Konsolidasi developer terbaru seputar modul ES asli telah sesuai untuk Lit – Lit hanyalah JavaScript biasa dan tidak perlu untuk CLI khusus framework atau penanganan build.

Agnostik framework

Komponen Lit dibuat dari serangkaian standar web yang disebut Komponen Web. Hal ini berarti bahwa membuat komponen di Lit akan berfungsi di framework saat ini ataupun nanti. Jika komponen ini mendukung elemen HTML, komponen ini juga akan mendukung komponen Web.

Satu-satunya masalah interop framework adalah saat framework memiliki dukungan terbatas untuk DOM. React adalah salah satu dari framework ini, tetapi memungkinkan jalan keluar melalui Ref, dan Ref di React bukanlah pengalaman developer yang baik.

Tim Lit telah mengerjakan project eksperimental bernama @lit-labs/react yang akan mengurai komponen Lit Anda secara otomatis dan membuat wrapper React agar Anda tidak perlu menggunakan ref.

Sebagai tambahan, Custom Elements Everywhere akan menunjukkan framework dan library yang berfungsi dengan baik dengan elemen kustom.

Dukungan TypeScript kelas satu

Meskipun mungkin untuk menulis semua kode Lit di JavaScript, Lit akan ditulis dalam TypeScript dan tim Lit menyarankan agar developer juga menggunakan TypeScript.

Tim Lit telah bekerja sama dengan komunitas Lit untuk membantu mengelola project yang menghadirkan pemeriksaan jenis TypeScript dan intellisense ke template Lit pada saat pengembangan dan waktu build dengan lit-analyzer dan lit-plugin.

Screenshot IDE akan menampilkan pemeriksaan jenis yang tidak tepat untuk menyetel logika yang diuraikan ke sebuah angka

Screenshot IDE akan menampilkan saran intellisense

Fitur pengembangan tertanam dalam browser

Komponen lit hanyalah elemen HTML di DOM. Hal ini berarti untuk memeriksakan komponen, Anda tidak perlu menginstal alat atau ekstensi apa pun untuk browser.

Anda dapat membuka alat developer, memilih elemen, dan menjelajahi properti ataupun statusnya.

, $0.value menampilkan halo dunia, $0.outlined menampilkan nilai syarat benar, dan {$0} menunjukkan perluasan properti" class="l10n-relative-url-src" l10n-attrs-original-order="alt,src,class" src="https://codelabs.developers.google.com/codelabs/lit-2-for-react-devs/./img/browser-tools.png" />

Layanan ini dibuat dengan mempertimbangkan rendering sisi server (SSR)

Lit 2 dibuat dengan mempertimbangkan dukungan SSR. Pada saat menulis codelab ini, tim Lit belum merilis alat SSR dalam bentuk yang stabil, tetapi tim Lit telah men-deploy komponen yang dirender sisi server di seluruh produk Google. Tim Lit berharap dapat merilis fitur ini secara eksternal di GitHub dengan segera.

Sementara itu, Anda dapat mengikuti kemajuan tim Lit di sini.

Persetujuan rendah

Lit tidak memerlukan komitmen yang signifikan untuk digunakan. Anda dapat membuat komponen di Lit dan menambahkannya ke project yang ada. Jika tidak menyukainya, Anda tidak perlu mengonversi seluruh aplikasi sekaligus karena komponen web dapat berfungsi dalam framework lain.

Sudahkah Anda membuat seluruh aplikasi di Lit dan apakah juga ingin mengubah ke bentuk lain? Selanjutnya Anda dapat menempatkan aplikasi Lit saat ini di dalam framework baru dan memigrasikan apa pun yang Anda inginkan ke komponen framework baru.

Sebagai tambahan, banyak framework modern mendukung output di komponen web, sehingga kerangka tersebut biasanya dapat masuk ke dalam elemen Lit itu sendiri.

Ada dua cara untuk melakukan codelab ini:

  • Anda dapat melakukannya sepenuhnya secara online di browser
  • (Lanjutan) Anda dapat melakukannya di komputer lokal menggunakan VS Code.

Mengakses kode

Pada seluruh codelab, terdapat link ke playground Lit seperti ini:

Code Checkpoint

Playground adalah sandbox kode yang berjalan sepenuhnya di browser Anda. Sandbox kode ini dapat mengompilasi dan menjalankan file TypeScript dan JavaScript, dan juga dapat menyelesaikan impor ke modul node secara otomatis, misalnya:

// before
import './my-file.js';
import 'lit';

// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';

Anda dapat melakukan seluruh tutorial di playground Lit dengan menggunakan checkpoint ini sebagai titik awal. Jika menggunakan VS Code, Anda dapat menggunakan checkpoint ini untuk mendownload kode awal pada langkah apa pun, serta menggunakannya untuk memeriksa pekerjaan Anda.

Menjelajahi UI playground lit

Kolom tab pemilih file diberi label Bagian 1, bagian pengeditan kode sebagai Bagian 2, pratinjau output sebagai Bagian 3, serta tombol pratinjau muat ulang sebagai Bagian 4

Screenshot UI playground menyorot bagian yang akan Anda gunakan dalam codelab ini.

  1. Pemilih file. Perhatikan tombol plus...
  2. Editor file.
  3. Pratinjau kode.
  4. Tombol muat ulang.
  5. Tombol download.

Penyiapan VS Code (Lanjutan)

Berikut manfaat menggunakan penyiapan VS Code:

  • Pemeriksaan jenis template
  • Intellisense template & pelengkapan otomatis

Jika Anda memiliki NPM, VS Code (dengan plugin lit-plugin) yang sudah terinstal dan mengetahui cara menggunakannya, Anda dapat mendownload dan memulai project ini dengan melakukan hal berikut:

  • Tekan tombol download
  • Ekstrak konten file tar ke dalam direktori
  • (Jika TS) menyiapkan tsconfig cepat yang menghasilkan modul es dan es2015+
  • Instal server dev yang dapat menyelesaikan penentu modul kosong (tim Lit menyarankan @web/dev-server)
  • Jalankan server dev dan buka browser (jika menggunakan @web/dev-server, Anda dapat menggunakan web-dev-server --node-resolve --watch --open)

Di bagian ini, Anda akan mempelajari dasar-dasar template di Lit.

JSX & Template Lit

JSX adalah ekstensi sintaksis untuk JavaScript yang memungkinkan pengguna React menulis templatenya dengan mudah di kode JavaScript. Template Lit memiliki tujuan yang serupa: menunjukkan UI komponen sebagai fungsi dari statusnya.

Sintaksis Dasar

Code Checkpoint

Di React, Anda akan merender JSX halo dunia seperti ini:

import 'react';
import ReactDOM from 'react-dom';

const name = 'Josh Perez';
const element = (
  <>
    <h1>Hello, {name}</h1>
    <div>How are you?</div>
  </>
);

ReactDOM.render(
  element,
  mountNode
);

Pada contoh di atas, ada dua elemen dan variabel "name" yang disertakan. Di Lit, Anda akan melakukan hal berikut:

import {html, render} from 'lit';

const name = 'Josh Perez';
const element = html`
  <h1>Hello, ${name}</h1>
  <div>How are you?</div>`;

render(
  element,
  mountNode
);

Perlu diperhatikan bahwa template Lit tidak memerlukan React Fragment untuk mengelompokkan beberapa elemen dalam template.

Di Lit, template digabungkan dengan html template yang diberi tag LIT eral yang merupakan tempat Lit mendapatkan namanya.

Nilai Template

Template Lit dapat menerima template Lit lainnya, yang disebut TemplateResult. Misalnya, gabungkan name dalam tag miring (<i>) lalu gabungkan kode tersebut dengan literal template yang diberi tag NB. Pastikan untuk menggunakan karakter backtick (`) bukan karakter kutipan tunggal (').

import {html, render} from 'lit';

const name = html`<i>Josh Perez</i>`;
const element = html`
  <h1>Hello, ${name}</h1>
  <div>How are you?</div>`;

render(
  element,
  mountNode
);

Lit TemplateResult dapat menerima array, string, TemplateResult lainnya, serta perintah.

Code Checkpoint

Untuk latihan, coba konversi kode React berikut menjadi Lit:

const itemsToBuy = [
  <li>Bananas</li>,
  <li>oranges</li>,
  <li>apples</li>,
  <li>grapes</li>
];
const element = (
  <>
    <h1>Things to buy:</h1>
    <ol>
      {itemsToBuy}
    </ol>
  </>);

ReactDOM.render(
  element,
  mountNode
);

Jawaban:

import {html, render} from 'lit';

const itemsToBuy = [
  html`<li>Bananas</li>`,
  html`<li>oranges</li>`,
  html`<li>apples</li>`,
  html`<li>grapes</li>`
];
const element = html`
  <h1>Things to buy:</h1>
  <ol>
    ${itemsToBuy}
  </ol>`;

render(
  element,
  mountNode
);

Meneruskan dan menyetel properti

Code checkpoint

Salah satu perbedaan terbesar antara sintaksis JSX dan Lit adalah sintaksis data binding. Misalnya, ambil input React ini dengan binding:

const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
  <input
      disabled={disabled}
      className={`static-class ${myClass}`}
      defaultValue={value}/>;

ReactDOM.render(
  element,
  mountNode
);

Pada contoh di atas, input ditentukan dengan melakukan hal berikut:

  • Menetapkan penonaktifan untuk variabel yang ditentukan (salah dalam kasus ini)
  • Menetapkan class ke static-class plus variabel (dalam kasus ini "static-class my-class")
  • Menetapkan nilai default

Di Lit, Anda akan melakukan hal berikut:

import {html, render} from 'lit';

const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
  <input
      ?disabled=${disabled}
      class="static-class ${myClass}"
      .value=${value}>`;

render(
  element,
  mountNode
);

Pada contoh Lit, binding boolean ditambahkan untuk mengalihkan atribut disabled.

Berikutnya, terdapat binding langsung ke atribut class daripada className. Beberapa binding dapat ditambahkan ke atribut class, kecuali Anda menggunakan perintah classMap yang merupakan bantuan deklaratif untuk mengalihkan class.

Terakhir, properti value disetel di input. Tidak seperti React, Lit tidak akan menyetel elemen input menjadi hanya-baca karena mengikuti implementasi asli dan perilaku input.

Sintaksis binding properti Lit

html`<my-element ?attribute-name=${booleanVar}>`;
  • Awalan ? adalah sintaksis binding untuk mengalihkan atribut pada elemen
  • Setara dengan inputRef.toggleAttribute('attribute-name', booleanVar)
  • Berguna untuk elemen yang menggunakan disabled sebagai disabled="false" masih dibaca sebagai true oleh DOM karena inputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
  • Awalan . adalah sintaksis binding untuk menyetel properti elemen
  • Setara dengan inputRef.propertyName = anyVar
  • Cocok untuk meneruskan data kompleks seperti objek, array, atau class
html`<my-element attribute-name=${stringVar}>`;
  • Menggabungkan ke atribut elemen
  • Setara dengan inputRef.setAttribute('attribute-name', stringVar)
  • Cocok untuk nilai dasar, pemilih aturan gaya, dan querySelectors

Meneruskan pengendali

Code checkpoint

const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
  <input
      onClick={() => console.log('click')}
      onChange={e => console.log(e.target.value)} />;

ReactDOM.render(
  element,
  mountNode
);

Pada contoh di atas, input ditentukan dengan melakukan hal berikut:

  • Log kata "klik" saat input diklik
  • Log nilai input saat pengguna mengetik karakter

Di Lit, Anda akan melakukan hal berikut:

import {html, render} from 'lit';

const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
  <input
      @click=${() => console.log('click')}
      @input=${e => console.log(e.target.value)}>`;

render(
  element,
  mountNode
);

Pada contoh Lit, terdapat pemroses yang ditambahkan ke peristiwa click dengan @click.

Berikutnya, daripada menggunakan onChange, binding ke peristiwa input native dari <input> tersedia karena peristiwa change asli hanya dilepaskan di blur (abstrak React pada peristiwa ini),

Sintaksis pengendali peristiwa Lit

html`<my-element @event-name=${() => {...}}></my-element>`;
  • Awalan @ adalah sintaksis binding untuk pemrosesan peristiwa
  • Setara dengan inputRef.addEventListener('event-name', ...)
  • Menggunakan nama peristiwa DOM asli

Di bagian ini, Anda akan mempelajari tentang komponen dan fungsi class Lit. Status dan Hook dibahas secara lebih mendetail di bagian selanjutnya.

Komponen Class & LitElement

Code checkpoint (TS)Code checkpoint (JS)

Lit yang setara dengan komponen class React adalah LitElement, dan konsep Lit tentang "properti reaktif" adalah kombinasi dari properti dan status React. Contoh:

import React from 'react';
import ReactDOM from 'react-dom';

class Welcome extends React.Component {
  constructor(props) {
    super(props);
    this.state = {name: ''};
  }

  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

const element = <Welcome name="Elliott"/>
ReactDOM.render(
  element,
  mountNode
);

Pada contoh di atas, terdapat komponen React yang:

  • Merender name
  • Menetapkan nilai default name ke string kosong ("")
  • Menetapkan ulang name menjadi "Elliott"

Ini merupakan cara yang akan Anda lakukan di LitEement

Di TypeScript:

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

@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
  @property({type: String})
  name = '';

  render() {
    return html`<h1>Hello, ${this.name}</h1>`
  }
}

Di JavaScript:

import {LitElement, html} from 'lit';

class WelcomeBanner extends LitElement {
  static get properties() {
    return {
      name: {type: String}
    }
  }

  constructor() {
    super();
    this.name = '';
  }

  render() {
    return html`<h1>Hello, ${this.name}</h1>`
  }
}

customElements.define('welcome-banner', WelcomeBanner);

Dan di file HTML:

<!-- index.html -->
<head>
  <script type="module" src="./index.js"></script>
</head>
<body>
  <welcome-banner name="Elliott"></welcome-banner>
</body>

Tinjauan tentang apa yang terjadi dalam contoh di atas:

@property({type: String})
name = '';
  • Menentukan properti reaktif publik – bagian dari API publik komponen
  • Mengekspos atribut (secara default) serta properti pada komponen
  • Menentukan cara menerjemahkan atribut komponen (yang berupa string) ke dalam nilai
static get properties() {
  return {
    name: {type: String}
  }
}
  • Fungsi di bawah ini sama seperti dekorator TS @property, tetapi berjalan secara asli di JavaScript
render() {
  return html`<h1>Hello, ${this.name}</h1>`
}
  • Fungsi ini disebut setiap kali properti reaktif diubah
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
  ...
}
  • Fungsi ini akan mengaitkan nama tag Elemen HTML dengan definisi class
  • Karena standar Elemen Kustom, nama tag harus menyertakan tanda hubung (-)
  • this dalam LitElement merujuk pada instance elemen kustom (dalam kasus ini <welcome-banner>)
customElements.define('welcome-banner', WelcomeBanner);
  • Fungsi di bawah ini merupakan JavaScript yang setara dengan dekorasi TS @customElement
<head>
  <script type="module" src="./index.js"></script>
</head>
  • Mengimpor definisi elemen kustom
<body>
  <welcome-banner name="Elliott"></welcome-banner>
</body>
  • Menambahkan elemen kustom ke halaman
  • Menetapkan properti name ke 'Elliott'

Komponen Fungsi

Code checkpoint

Lit tidak memiliki penafsiran 1:1 komponen fungsi karena tidak menggunakan JSX atau praprosesor. Meskipun begitu, cukup mudah untuk menuliskan fungsi yang membutuhkan properti dan merender DOM berdasarkan properti tersebut. Contoh:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Elliott"/>
ReactDOM.render(
  element,
  mountNode
);

Dalam Lit, fungsi ini akan menjadi:

import {html, render} from 'lit';

function Welcome(props) {
  return html`<h1>Hello, ${props.name}</h1>`;
}

render(
  Welcome({name: 'Elliott'}),
  document.body.querySelector('#root')
);

Di bagian ini, Anda akan mempelajari status dan siklus proses Lit.

Status

Konsep Lit tentang "Properti Reaktif" adalah campuran status dan properti React. Jika Properti Reaktif diubah dapat memicu siklus proses komponen. Properti reaktif tersedia dalam dua varian:

Properti reaktif publik

// React
import React from 'react';

class MyEl extends React.Component {
  constructor(props) {
    super(props)
    this.state = {name: 'there'}
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.name !== nextProps.name) {
      this.setState({name: nextProps.name})
    }
  }
}

// Lit (TS)
import {LitElement} from 'lit';
import {property} from 'lit/decorators.js';

class MyEl extends LitElement {
  @property() name = 'there';
}
  • Ditentukan oleh @property
  • Mirip dengan properti dan status React, tetapi dapat diubah
  • API Publik yang diakses dan ditetapkan oleh konsumen komponen

Status reaktif internal

// React
import React from 'react';

class MyEl extends React.Component {
  constructor(props) {
    super(props)
    this.state = {name: 'there'}
  }
}

// Lit (TS)
import {LitElement} from 'lit';
import {state} from 'lit/decorators.js';

class MyEl extends LitElement {
  @state() name = 'there';
}
  • Ditentukan oleh @state
  • Serupa dengan status React, tetapi dapat diubah
  • Status internal pribadi yang biasanya diakses dari dalam komponen atau subclass

Siklus Proses

Siklus proses Lit cukup mirip dengan React, tetapi ada beberapa perbedaan yang signifikan.

constructor

// React (js)
import React from 'react';

class MyEl extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this._privateProp = 'private';
  }
}

// Lit (ts)
class MyEl extends LitElement {
  @property({type: Number}) counter = 0;
  private _privateProp = 'private';
}

// Lit (js)
class MyEl extends LitElement {
  static get properties() {
    return { counter: {type: Number} }
  }
  constructor() {
    this.counter = 0;
    this._privateProp = 'private';
  }
}
  • Lit setara juga dengan constructor
  • Tidak perlu meneruskan apa pun ke panggilan super
  • Dijalankan oleh (tidak sepenuhnya inklusif):
    • document.createElement
    • document.innerHTML
    • new ComponentClass()
    • Jika nama tag yang belum diupgrade berada di halaman, definisi dimuat dan didaftarkan dengan @customElement atau customElements.define
  • Serupa dengan fungsi untuk constructor React

render

// React
render() {
  return <div>Hello World</div>
}

// Lit
render() {
  return html`<div>Hello World</div>`;
}
  • Lit setara juga dengan render
  • Dapat menampilkan hasil yang dapat dirender, misalnya TemplateResult atau string dll.
  • Agar serupa dengan React, render() harus berupa fungsi murni
  • Akan merender ke node mana pun yang ditampilkan createRenderRoot() (ShadowRoot secara default)

componentDidMount

componentDidMount mirip dengan kombinasi dari callback siklus proses firstUpdated dan connectedCallback Lit.

firstUpdated

import Chart from 'chart.js';

// React
componentDidMount() {
  this._chart = new Chart(this.chartElRef.current, {...});
}

// Lit
firstUpdated() {
  this._chart = new Chart(this.chartEl, {...});
}
  • Dipanggil saat template komponen pertama kali dirender ke root komponen
  • Hanya akan dipanggil jika elemen terhubung misalnya tidak dipanggil melalui document.createElement('my-component') sampai node tersebut ditambahkan ke hierarki DOM
  • Fungsi ini merupakan tempat yang baik untuk melakukan penyiapan komponen yang memerlukan DOM yang dirender oleh komponen
  • Tidak seperti React, componentDidMount yang berubah ke properti reaktif di firstUpdated akan menyebabkan perenderan ulang, meskipun browser biasanya akan mengelompokkan perubahan ke dalam frame yang sama. Jika perubahan tersebut tidak memerlukan akses ke DOM root, biasanya perubahan itu akan berada di willUpdate

connectedCallback

// React
componentDidMount() {
  this.window.addEventListener('resize', this.boundOnResize);
}

// Lit
connectedCallback() {
  super.connectedCallback();
  this.window.addEventListener('resize', this.boundOnResize);
}
  • Dipanggil setiap kali elemen kustom disisipkan ke dalam hierarki DOM
  • Tidak seperti komponen React, saat elemen kustom dilepaskan dari DOM, elemen tersebut tidak akan dihancurkan sehingga dapat "dihubungkan" beberapa kali
  • Berguna untuk menginisialisasi ulang DOM atau melampirkan ulang pemroses peristiwa yang telah dihapus saat diputuskan
  • Catatan: connectedCallback mungkin dipanggil sebelum firstUpdated sehingga pada panggilan pertama, DOM mungkin tidak akan tersedia

componentDidUpdate

// React
componentDidUpdate(prevProps) {
  if (this.props.title !== prevProps.title) {
    this._chart.setTitle(this.props.title);
  }
}

// Lit (ts)
updated(prevProps: PropertyValues<this>) {
  if (prevProps.has('title')) {
    this._chart.setTitle(this.title);
  }
}
  • Persamaan Lit adalah updated (menggunakan bentuk waktu lampau bahasa Inggris dari "update")
  • Tidak seperti React, updated juga dipanggil pada render awal
  • Serupa dengan fungsi untuk componentDidUpdate React

componentWillUnmount

// React
componentWillUnmount() {
  this.window.removeEventListener('resize', this.boundOnResize);
}

// Lit
disconnectedCallback() {
  super.disconnectedCallback();
  this.window.removeEventListener('resize', this.boundOnResize);
}
  • Persamaan Lit setara dengan disconnectedCallback
  • Tidak seperti komponen React, saat elemen kustom dilepaskan dari DOM, komponen tidak akan dimusnahkan
  • Tidak seperti componentWillUnmount, disconnectedCallback dipanggil setelah elemen dihapus dari hierarki
  • DOM di dalam root masih terpasang ke subhierarki root
  • Berguna untuk membersihkan pemroses peristiwa dan referensi yang bocor sehingga browser dapat membersihkan sampah memori komponen

Latihan

Code Checkpoint (TS)Code Checkpoint (JS)

import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Pada contoh di atas, terdapat jam sederhana yang melakukan hal berikut:

  • Jam tersebut merender "Halo Dunia! Waktunya" lalu menampilkan waktu
  • Setiap detik fungsi tersebut akan memperbarui jam
  • Ketika dilepaskan, tindakan tersebut akan menghapus interval yang memanggil tick

Pertama-tama, mulai dengan deklarasi class komponen:

// Lit (TS)
// some imports here are imported in advance
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';

@customElement('lit-clock')
class LitClock extends LitElement {
}

// Lit (JS)
// `html` is imported in advance
import {LitElement, html} from 'lit';

class LitClock extends LitElement {
}

customElements.define('lit-clock', LitClock);

Berikutnya, lakukan inisialisasi date dan deklarasikan properti reaktif internal dengan @state karena pengguna komponen tidak akan menetapkan date secara langsung.

// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';

@customElement('lit-clock')
class LitClock extends LitElement {
  @state() // declares internal reactive prop
  private date = new Date(); // initialization
}

// Lit (JS)
import {LitElement, html} from 'lit';

class LitClock extends LitElement {
  static get properties() {
    return {
      // declares internal reactive prop
      date: {state: true}
    }
  }

  constructor() {
    super();
    // initialization
    this.date = new Date();
  }
}

customElements.define('lit-clock', LitClock);

Berikutnya, merender template.

// Lit (JS & TS)
render() {
  return html`
    <div>
      <h1>Hello, World!</h1>
      <h2>It is ${this.date.toLocaleTimeString()}.</h2>
    </div>
  `;
}

Sekarang, terapkan metode tick.

tick() {
  this.date = new Date();
}

Selanjutnya adalah penerapan componentDidMount. Sekali lagi, analog Lit adalah campuran dari firstUpdated dan connectedCallback. Dalam kasus komponen ini, memanggil tick dengan setInterval tidak memerlukan akses ke DOM di dalam root. Selain itu, interval akan dikosongkan ketika elemen dihapus dari hierarki dokumen sehingga jika dipasang kembali, interval harus memulai lagi. Jadi, connectedCallback adalah pilihan yang lebih baik di sini.

// Lit (TS)
@customElement('lit-clock')
class LitClock extends LitElement {
  @state()
  private date = new Date();
  private timerId = -1; // initialize timerId for TS

  connectedCallback() {
    super.connectedCallback();
    this.timerId = setInterval(
      () => this.tick(),
      1000
    );
  }

  ...
}

// Lit (JS)
constructor() {
  super();
  // initialization
  this.date = new Date();
  this.timerId = -1; // initialize timerId for JS
}

connectedCallback() {
  super.connectedCallback();
  this.timerId = setInterval(
    () => this.tick(),
    1000
  );
}

Terakhir, bersihkan interval agar tidak menjalankan tick setelah elemen terputus dari hierarki dokumen.

// Lit (TS & JS)
disconnectedCallback() {
  super.disconnectedCallback();
  clearInterval(this.timerId);
}

Jika menggabungkan semuanya, elemen tersebut akan terlihat seperti ini:

// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';

@customElement('lit-clock')
class LitClock extends LitElement {
  @state()
  private date = new Date();
  private timerId = -1;

  connectedCallback() {
    super.connectedCallback();
    this.timerId = setInterval(
      () => this.tick(),
      1000
    );
  }

  tick() {
    this.date = new Date();
  }

  render() {
    return html`
      <div>
        <h1>Hello, World!</h1>
        <h2>It is ${this.date.toLocaleTimeString()}.</h2>
      </div>
    `;
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    clearInterval(this.timerId);
  }
}

// Lit (JS)
import {LitElement, html} from 'lit';

class LitClock extends LitElement {
  static get properties() {
    return {
      date: {state: true}
    }
  }

  constructor() {
    super();
    this.date = new Date();
  }

  connectedCallback() {
    super.connectedCallback();
    this.timerId = setInterval(
      () => this.tick(),
      1000
    );
  }

  tick() {
    this.date = new Date();
  }

  render() {
    return html`
      <div>
        <h1>Hello, World!</h1>
        <h2>It is ${this.date.toLocaleTimeString()}.</h2>
      </div>
    `;
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    clearInterval(this.timerId);
  }
}

customElements.define('lit-clock', LitClock);

Di bagian ini, Anda akan mempelajari cara menerjemahkan konsep React Hook ke Lit.

Konsep hook React

Hook React memberikan cara untuk membuat komponen fungsi "hook" ke dalam status. Ada beberapa manfaat dari hook ini.

  • Hook menyederhanakan penggunaan kembali dari logika stateful
  • Membantu membagi komponen ke dalam beberapa fungsi yang lebih kecil

Selain itu, fokus pada komponen berbasis fungsi mengatasi masalah tertentu terkait sintaksis berbasis class React seperti:

  • Harus meneruskan props dari constructor ke super
  • Melakukan inisialisasi properti yang tidak rapi di constructor
    • Hook ini merupakan alasan yang dinyatakan oleh tim React pada saat itu tetapi diselesaikan oleh ES2019
  • Masalah yang disebabkan oleh this tidak lagi mengacu pada komponen

Konsep hook React di Lit

Seperti yang disebutkan di bagian Komponen & Properti, Lit tidak menawarkan cara untuk membuat elemen kustom dari fungsi, tetapi LitElement dapat menangani sebagian besar masalah utama pada komponen class React. Contoh:

// React (at the time of making hooks)
import React from 'react';
import ReactDOM from 'react-dom';

class MyEl extends React.Component {
  constructor(props) {
    super(props); // Leaky implementation
    this.state = {count: 0};
    this._chart = null; // Deemed messy
  }

  render() {
    return (
      <>
        <div>Num times clicked {count}</div>
        <button onClick={this.clickCallback}>click me</button>
      </>
    );
  }

  clickCallback() {
    // Errors because `this` no longer refers to the component
    this.setState({count: this.count + 1});
  }
}

// Lit (ts)
class MyEl extends LitElement {
  @property({type: Number}) count = 0; // No need for constructor to set state
  private _chart = null; // Public class fields introduced to JS in 2019

  render() {
    return html`
        <div>Num times clicked ${count}</div>
        <button @click=${this.clickCallback}>click me</button>`;
  }

  private clickCallback() {
    // No error because `this` refers to component
    this.count++;
  }
}

Bagaimana cara Lit mengatasi masalah ini?

  • constructor tidak memerlukan argumen
  • Semua binding @event otomatis dikelompokkan ke this
  • this dalam kebanyakan kasus mengacu pada referensi elemen kustom
  • Properti class sekarang dapat dibuat instance sebagai anggota class. Tindakan ini akan menghapus penerapan berbasis konstruktor

Pengontrol Reaktif

Code Checkpoint (TS)Code Checkpoint (JS)

Konsep utama di balik Hook yang ada di Lit sebagai pengontrol reaktif. Pola pengontrol reaktif memungkinkan untuk berbagi logika stateful, membagi komponen menjadi lebih kecil, lebih banyak bit modular, serta menghubungkan ke siklus proses update dari suatu elemen.

Pengontrol reaktif adalah antarmuka objek yang dapat terhubung ke siklus proses update dari host pengontrol seperti LitElement.

Siklus proses dari ReactiveController dan reactiveControllerHost adalah:

interface ReactiveController {
  hostConnected(): void;
  hostUpdate(): void;
  hostUpdated(): void;
  hostDisconnected(): void;
}
interface ReactiveControllerHost {
  addController(controller: ReactiveController): void;
  removeController(controller: ReactiveController): void;
  requestUpdate(): void;
  readonly updateComplete: Promise<boolean>;
}

Dengan membuat pengontrol reaktif dan melampirkannya ke host dengan addController, siklus proses pengontrol akan dipanggil bersama host tersebut. Misalnya, memanggil kembali contoh jam dari bagian Status & Siklus Proses:

import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Pada contoh di atas, terdapat jam sederhana yang melakukan hal berikut:

  • Jam tersebut merender "Halo Dunia! Waktunya" lalu menampilkan waktu
  • Setiap detik fungsi tersebut akan memperbarui jam
  • Ketika dilepaskan, tindakan tersebut akan menghapus interval yang memanggil tick

Membuat perancah komponen

Mulailah dengan deklarasi class komponen dan tambahkan fungsi render.

// Lit (TS) - index.ts
import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';

@customElement('my-element')
class MyElement extends LitElement {
  render() {
    return html`
      <div>
        <h1>Hello, world!</h1>
        <h2>It is ${'time to get Lit'}.</h2>
      </div>
    `;
  }
}

// Lit (JS) - index.js
import {LitElement, html} from 'lit';

class MyElement extends LitElement {
  render() {
    return html`
      <div>
        <h1>Hello, world!</h1>
        <h2>It is ${'time to get Lit'}.</h2>
      </div>
    `;
  }
}

customElements.define('my-element', MyElement);

Membuat pengontrol

Sekarang beralih ke clock.ts dan buat class untuk ClockController lalu siapkan constructor:

// Lit (TS) - clock.ts
import {ReactiveController, ReactiveControllerHost} from 'lit';

export class ClockController implements ReactiveController {
  private readonly host: ReactiveControllerHost;

  constructor(host: ReactiveControllerHost) {
    this.host = host;
    host.addController(this);
  }

  hostConnected() {
  }

  private tick() {
  }

  hostDisconnected() {
  }

  // Will not be used but needed for TS compilation
  hostUpdate() {};
  hostUpdated() {};
}

// Lit (JS) - clock.js
export class ClockController {
  constructor(host) {
    this.host = host;
    host.addController(this);
  }

  hostConnected() {
  }

  tick() {
  }

  hostDisconnected() {
  }
}

Pengontrol reaktif dapat dibuat dengan cara apa pun asalkan dapat membagikan antarmuka ReactiveController. Namun, menggunakan class dengan constructor yang dapat menggunakan antarmuka ReactiveControllerHost serta properti lain yang diperlukan untuk menginisialisasi pengontrol adalah pola yang lebih dipilih oleh tim Lit untuk digunakan di sebagian besar kasus dasar.

Sekarang Anda perlu menerjemahkan callback siklus proses React menjadi callback pengontrol. Singkatnya:

  • componentDidMount
    • Ke connectedCallback LitElement
    • Ke hostConnected pengontrol
  • ComponentWillUnmount
    • Ke disconnectedCallback LitElement
    • Ke hostDisconnected pengontrol

Untuk mengetahui informasi selengkapnya tentang cara menerjemahkan siklus proses React ke siklus proses Lit, lihat bagian Status & Siklus Proses.

Berikutnya, terapkan metode callback hostConnected dan tick, serta bersihkan interval di hostDisconnected seperti yang dilakukan pada contoh di bagian Status & Siklus Proses.

// Lit (TS) - clock.ts
export class ClockController implements ReactiveController {
  private readonly host: ReactiveControllerHost;
  private interval = 0;
  date = new Date();

  constructor(host: ReactiveControllerHost) {
    this.host = host;
    host.addController(this);
  }

  hostConnected() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  private tick() {
    this.date = new Date();
  }

  hostDisconnected() {
    clearInterval(this.interval);
  }

  hostUpdate() {};
  hostUpdated() {};
}

// Lit (JS) - clock.js
export class ClockController {
  interval = 0;
  host;
  date = new Date();

  constructor(host) {
    this.host = host;
    host.addController(this);
  }

  hostConnected() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  tick() {
    this.date = new Date();
  }

  hostDisconnected() {
    clearInterval(this.interval);
  }
}

Menggunakan pengontrol

Untuk menggunakan pengontrol jam, impor pengontrol, lalu perbarui komponen di index.ts atau index.js.

// Lit (TS) - index.ts
import {LitElement, html, ReactiveController, ReactiveControllerHost} from 'lit';
import {customElement} from 'lit/decorators.js';
import {ClockController} from './clock.js';

@customElement('my-element')
class MyElement extends LitElement {
  private readonly clock = new ClockController(this); // Instantiate

  render() {
    // Use controller
    return html`
      <div>
        <h1>Hello, world!</h1>
        <h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
      </div>
    `;
  }
}

// Lit (JS) - index.js
import {LitElement, html} from 'lit';
import {ClockController} from './clock.js';

class MyElement extends LitElement {
  clock = new ClockController(this); // Instantiate

  render() {
    // Use controller
    return html`
      <div>
        <h1>Hello, world!</h1>
        <h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
      </div>
    `;
  }
}

customElements.define('my-element', MyElement);

Untuk menggunakan pengontrol, Anda perlu membuat instance pengontrol dengan meneruskan referensi ke host pengontrol (yang merupakan komponen <my-element>), lalu menggunakan pengontrol di metode render.

Memicu rendering ulang di pengontrol

Perlu diperhatikan bahwa ini akan menampilkan waktu, tetapi waktu tidak diperbarui. Hal ini karena pengontrol menetapkan tanggal setiap detik, tetapi host tidak memperbarui. Dan juga karena date berubah pada class ClockController dan bukan merupakan komponen lagi. Hal ini berarti bahwa setelah date disetel pada pengontrol, host perlu diberi tahu untuk menjalankan siklus proses pembaruannya dengan host.requestUpdate().

// Lit (TS & JS) - clock.ts / clock.js
private tick() {
  this.date = new Date();
  this.host.requestUpdate();
}

Sekarang jamnya sudah berdenting!

Untuk perbandingan yang lebih mendalam tentang kasus penggunaan umum dengan hook, lihat bagian Topik Lanjutan - Hook.

Di bagian ini, Anda akan mempelajari cara menggunakan slot untuk mengelola turunan di Lit.

Slot & Turunan

Code Checkpoint

Slot mengaktifkan komposisi dengan memungkinkan Anda untuk menumpuk komponen.

Dalam React, turunan diwariskan melalui properti. Slot default-nya adalah props.children dan fungsi render menentukan posisi slot default. Contoh:

const MyArticle = (props) => {
 return <article>{props.children}</article>;
};

Perlu diingat bahwa props.children adalah Komponen React dan bukan merupakan elemen HTML.

Di Lit, turunan disusun dalam fungsi render dengan elemen slot. Perlu diperhatikan bahwa turunan tidak diwariskan dengan cara yang sama seperti React. Di Lit, turunan merupakan HTMLElements yang dilampirkan ke slot. Lampiran ini disebut Proyeksi.

@customElement("my-article")
export class MyArticle extends LitElement {
  render() {
    return html`
      <article>
        <slot></slot>
      </article>
   `;
  }
}

Beberapa Slot

Code Checkpoint

Di React, menambahkan beberapa slot pada dasarnya sama dengan mewarisi lebih banyak properti.

const MyArticle = (props) => {
  return (
    <article>
      <header>
        {props.headerChildren}
      </header>
      <section>
        {props.sectionChildren}
      </section>
    </article>
  );
};

Demikian pula, menambahkan lebih banyak elemen <slot> akan membuat lebih banyak slot di Lit. Beberapa slot ditentukan dengan atribut name: <slot name="slot-name">. Hal ini memungkinkan turunan untuk menyatakan slot mana yang akan ditetapkan.

@customElement("my-article")
export class MyArticle extends LitElement {
  render() {
    return html`
      <article>
        <header>
          <slot name="headerChildren"></slot>
        </header>
        <section>
          <slot name="sectionChildren"></slot>
        </section>
      </article>
   `;
  }
}

Konten Slot Default

Slot akan menampilkan subhierarki jika tidak ada node yang diproyeksikan ke slot tersebut. Jika node diproyeksikan ke slot, slot tersebut tidak akan menampilkan subhierarki dan akan menampilkan node yang diproyeksikan.

@customElement("my-element")
export class MyElement extends LitElement {
  render() {
    return html`
      <section>
        <div>
          <slot name="slotWithDefault">
            <p>
             This message will not be rendered when children are attached to this slot!
            <p>
          </slot>
        </div>
      </section>
   `;
  }
}

Menetapkan turunan ke slot

Code Checkpoint

Di React, turunan ditetapkan ke slot melalui properti Komponen. Pada contoh di bawah ini, elemen React diteruskan ke properti headerChildren dan sectionChildren.

const MyNewsArticle = () => {
 return (
   <MyArticle
     headerChildren={<h3>Extry, Extry! Read all about it!</h3>}
     sectionChildren={<p>Children are props in React!</p>}
   />
 );
};

Di Lit, turunan ditetapkan ke slot menggunakan atribut slot.

@customElement("my-news-article")
export class MyNewsArticle extends LitElement {
  render() {
    return html`
      <my-article>
        <h3 slot="headerChildren">
          Extry, Extry! Read all about it!
        </h3>
        <p slot="sectionChildren">
          Children are composed with slots in Lit!
        </p>
      </my-article>
   `;
  }
}

Jika tidak ada slot default (misalnya <slot>) dan tidak ada slot yang memiliki atribut name (misalnya <slot name="foo">) yang cocok dengan atribut slot dari turunan elemen kustom (misalnya <div slot="foo">), node tersebut tidak akan diproyeksikan dan tidak akan ditampilkan.

Terkadang, developer mungkin perlu mengakses API HTMLElement.

Pada bagian ini, Anda akan mempelajari cara memperoleh referensi elemen di Lit.

Referensi React

Code Checkpoint (TS)Code Checkpoint (JS)

Komponen React ditranspilasikan menjadi serangkaian panggilan fungsi yang membuat DOM virtual saat dipanggil. DOM virtual ini ditafsirkan oleh ReactDOM dan merender HTMLElements.

Di React, referensi adalah ruang di memori berisi HTMLEding yang dihasilkan.

const RefsExample = (props) => {
 const inputRef = React.useRef(null);
 const onButtonClick = React.useCallback(() => {
   inputRef.current?.focus();
 }, [inputRef]);

 return (
   <div>
     <input type={"text"} ref={inputRef} />
     <br />
     <button onClick={onButtonClick}>
       Click to focus on the input above!
     </button>
   </div>
 );
};

Pada contoh di atas, komponen React akan melakukan hal berikut:

  • Merender input teks kosong dan tombol dengan teks
  • Memfokuskan input saat tombol diklik

Setelah render awal, React akan menetapkan inputRef.current ke HTMLInputElement yang dihasilkan melalui atribut ref.

Lit "Referensi" dengan @query

Lit berada dekat dengan browser dan membuat abstraksi yang sangat tipis pada fitur browser asli.

React yang setara dengan refs di Lit merupakan HTMLElement yang ditampilkan oleh dekorator @query dan @queryAll.

@customElement("my-element")
export class MyElement extends LitElement {
  @query('input') // Define the query
  inputEl!: HTMLInputElement; // Declare the prop

  // Declare the click event listener
  onButtonClick() {
    // Use the query to focus
    this.inputEl.focus();
  }

  render() {
    return html`
      <input type="text"></input>
      <br />
      <!-- Bind the click listener -->
      <button @click=${this.onButtonClick}>
        Click to focus on the input above!
      </button>
   `;
  }
}

Pada contoh di atas, komponen Lit melakukan:

  • Menentukan properti di MyElement menggunakan dekorator @query (membuat pengambil untuk HTMLInputElement).
  • Mendeklarasikan dan memasang callback peristiwa klik yang disebut onButtonClick.
  • Memfokuskan input pada klik tombol

Dalam JavaScript, dekorasi @query dan @queryAll menjalankan querySelector dan querySelectorAll secara berturut-turut. JavaScript ini setara dengan @query('input') inputEl!: HTMLInputElement;

get inputEl() {
  return this.renderRoot.querySelector('input');
}

Setelah komponen Lit melakukan template metode render ke root my-element, dekorator @query sekarang akan mengizinkan inputEl untuk menampilkan elemen input pertama yang ditemukan di root render. null akan ditampilkan jika @query tidak dapat menemukan elemen yang ditentukan.

Jika ada beberapa elemen input di root render, @queryAll akan menampilkan daftar node.

Di bagian ini, Anda akan mempelajari cara memediasi status antarkomponen di Lit.

Komponen yang Dapat Digunakan Kembali

Code Checkpoint

React meniru pipeline rendering fungsional dengan aliran data turun. Induk akan memberikan status ke turunan melalui prop. Turunan berkomunikasi dengan induk melalui callback yang ditemukan di prop.

const CounterButton = (props) => {
  const label = props.step < 0
    ? `- ${-1 * props.step}`
    : `+ ${props.step}`;

  return (
    <button
      onClick={() =>
        props.addToCounter(props.step)}>{label}</button>
  );
};

Pada contoh di atas, komponen React melakukan beberapa hal berikut:

  • Membuat label berdasarkan nilai props.step.
  • Merender tombol dengan +langkah atau -langkah sebagai labelnya
  • Memperbarui komponen induk melalui pemanggilan props.addToCounter dengan props.step sebagai argumen saat diklik

Meskipun dapat meneruskan callback di Lit, pola konvensinya akan berbeda. Komponen React pada contoh di atas dapat ditulis sebagai Komponen Lit dalam contoh di bawah:

@customElement('counter-button')
export class CounterButton extends LitElement {
  @property({type: Number}) step: number = 0;

  onClick() {
    const event = new CustomEvent('update-counter', {
      bubbles: true,
      detail: {
        step: this.step,
      }
    });

    this.dispatchEvent(event);
  }

  render() {
    const label = this.step < 0
      ? `- ${-1 * this.step}`  // "- 1"
      : `+ ${this.step}`;      // "+ 1"

    return html`
      <button @click=${this.onClick}>${label}</button>
    `;
  }
}

Pada contoh di atas, Komponen Lit akan melakukan beberapa hal berikut:

  • Membuat properti reaktif step
  • Mengirim peristiwa kustom bernama update-counter yang membawa nilai step elemen saat diklik

Peristiwa browser muncul dari elemen turunan hingga elemen induk. Peristiwa memungkinkan turunan menyiarkan peristiwa interaksi dan perubahan status. React pada dasarnya meneruskan status ke arah yang berbeda sehingga melihat pengiriman Komponen React dan memproses peristiwa dengan cara yang sama seperti Menyalakan Komponen adalah hal yang tidak biasa.

Komponen Stateful

Code Checkpoint

Dalam React, penggunaan hook untuk mengelola status adalah hal yang umum. Komponen MyCounter dapat dibuat dengan menggunakan kembali Komponen CounterButton. Perhatikan cara addToCounter diteruskan ke kedua instance CounterButton.

const MyCounter = (props) => {
 const [counterSum, setCounterSum] = React.useState(0);
 const addToCounter = useCallback(
   (step) => {
     setCounterSum(counterSum + step);
   },
   [counterSum, setCounterSum]
 );

 return (
   <div>
     <h3>&Sigma;: {counterSum}</h3>
     <CounterButton
       step={-1}
       addToCounter={addToCounter} />
     <CounterButton
       step={1}
       addToCounter={addToCounter} />
   </div>
 );
};

Contoh di atas melakukan beberapa hal berikut:

  • Membuat status count.
  • Membuat callback yang menambahkan nomor ke status count.
  • CounterButton menggunakan addToCounter untuk memperbarui count berdasarkan step pada setiap klik.

Penerapan MyCounter yang serupa dapat dilakukan di Lit. Perhatikan cara addToCounter tidak diteruskan ke counter-button. Sebaliknya, callback di-binding sebagai pemroses peristiwa ke peristiwa @update-counter pada elemen induk.

@customElement("my-counter")
export class MyCounter extends LitElement {
  @property({type: Number}) count = 0;

  addToCounter(e: CustomEvent<{step: number}>) {
    // Get step from detail of event or via @query
    this.count += e.detail.step;
  }

  render() {
    return html`
      <div @update-counter="${this.addToCounter}">
        <h3>&Sigma; ${this.count}</h3>
        <counter-button step="-1"></counter-button>
        <counter-button step="1"></counter-button>
      </div>
    `;
  }
}

Contoh di atas melakukan beberapa hal berikut:

  • Membuat properti reaktif bernama count yang akan mengupdate komponen saat nilai diubah
  • Mem-binding callback addToCounter ke pemroses peristiwa @update-counter
  • Mengupdate count dengan menambahkan nilai yang ditemukan di detail.step peristiwa update-counter
  • Menetapkan nilai step counter-button melalui atribut step

Lebih konvensional menggunakan properti reaktif di Lit untuk menyiarkan perubahan dari induk ke turunan. Demikian pula, sebaiknya gunakan sistem peristiwa browser untuk menampilkan detail dari bawah ke atas.

Pendekatan ini mengikuti praktik terbaik dan mematuhi sasaran Lit dalam memberikan dukungan lintas platform untuk komponen web.

Di bagian ini, Anda akan mempelajari penataan gaya di Lit.

Penggayaan

Lit menawarkan beberapa cara untuk menata gaya elemen serta solusi bawaan.

Gaya Inline

Code checkpoint

Lit mendukung gaya inline serta mem-binding-nya.

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

@customElement('my-element')
class MyElement extends LitElement {
  render() {
    return html`
      <div>
        <h1 style="color:orange;">This text is orange</h1>
        <h1 style="color:rebeccapurple;">This text is rebeccapurple</h1>
      </div>
    `;
  }
}

Pada contoh di atas, ada 2 judul masing-masing dengan gaya inline.

Sekarang coba mem-binding border: 1px solid black ke teks berwarna oranye:

<h1 style="color:orange;${'border: 1px solid black;'}">This text is orange</h1>

Mengharuskan penghitungan string gaya setiap waktu mungkin agak mengganggu sehingga Lit menawarkan perintah untuk mempermudah hal ini.

styleMap

Perintah styleMap mempermudah penggunaan JavaScript untuk menetapkan gaya inline. Contoh:

Code Checkpoint

import {LitElement, html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {styleMap} from 'lit/directives/style-map';

@customElement('my-element')
class MyElement extends LitElement {
  @property({type: String})
  color = '#000'

  render() {
    // Define the styleMap
    const headerStyle = styleMap({
      'border-color': this.color,
    });

    return html`
      <div>
        <h1
          style="border-style:solid;
          <!-- Use the styleMap -->
          border-width:2px;${headerStyle}">
          This div has a border color of ${this.color}
        </h1>
        <input
          type="color"
          @input=${e => (this.color = e.target.value)}
          value="#000">
      </div>
    `;
  }
}

Contoh di atas melakukan beberapa hal berikut:

  • Menampilkan h1 dengan batas dan pemilih warna
  • Mengubah border-color menjadi nilai dari pemilih warna

Selain itu, ada styleMap yang digunakan untuk mengatur gaya h1. styleMap mengikuti sintaksis yang mirip dengan sintaksis binding atribut style React.

CSSResult

Code Checkpoint

Cara yang disarankan untuk menentukan gaya komponen ialah menggunakan literal template yang diberi tag css.

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

const ORANGE = css`orange`;

@customElement('my-element')
class MyElement extends LitElement {
  static styles = [
    css`
      #orange {
        color: ${ORANGE};
      }

      #purple {
        color: rebeccapurple;
      }
    `
  ];

  render() {
    return html`
      <div>
    <h1 id="orange">This text is orange</h1>
        <h1 id="purple">This text is rebeccapurple</h1>
      </div>
    `;
  }
}

Contoh di atas melakukan beberapa hal berikut:

  • Mendeklarasikan literal template tag CSS dengan binding
  • Menetapkan warna dari dua h1 dengan ID

Manfaat menggunakan tag template css:

  • Diurai sekali per class vs per instance
  • Diterapkan dengan mempertimbangkan penggunaan kembali modul
  • Dapat dengan mudah memisahkan gaya ke dalam filenya sendiri
  • Kompatibel dengan polyfill Properti Khusus CSS

Selain itu, perhatikan tag <style> di index.html:

<!-- index.html -->
<style>
  h1 {
    color: red !important;
  }
</style>

Lit akan mengatur gaya komponen Anda ke root-nya. Ini berarti gaya tidak akan bocor. Untuk meneruskan gaya ke komponen, tim Lit menyarankan untuk menggunakan Properti Khusus CSS karena dapat memasuki cakupan gaya Lit.

Tag Gaya

Anda juga dapat membuat tag <style> inline di template Anda. Browser akan menghapus duplikat tag gaya ini. Namun, dengan menempatkannya dalam template Anda, tag akan diurai per instance komponen dan bukan per class seperti pada kasus template yang diberi tag css. Selain itu, penghapusan duplikat browser CSSResult jauh lebih cepat.

Penggunaan <link rel="stylesheet"> di template Anda juga kemungkinan adanya gaya, tetapi ini juga tidak direkomendasikan karena dapat menyebabkan flash awal pada konten yang tidak bergaya (FOUC).

JSX & Pembuatan Template

Lit & DOM Virtual

Lit-html tidak menyertakan DOM Virtual biasa yang membedakan setiap node individual. Sebagai gantinya, aplikasi ini menggunakan fitur performa yang bersifat intrinsik pada spesifikasi yang diberi tag literal template dari ES2015. Literal template yang diberi tag merupakan string literal template dengan fungsi tag yang disertakan.

Berikut adalah contoh literal template:

const str = 'string';
console.log(`This is a template literal ${str}`);

Berikut adalah contoh literal template yang diberi tag:

const tag = (strings, ...values) => ({strings, values});
const f = (x) => tag`hello ${x} how are you`;
console.log(f('world')); // {strings: ["hello ", " how are you"], values: ["world"]}
console.log(f('world').strings === f(1 + 2).strings); // true

Pada contoh di atas, tagnya adalah fungsi tag dan fungsi f menampilkan pemanggilan literal template yang diberi tag.

Banyak keajaiban performa di Lit berasal dari fakta bahwa array string yang diteruskan ke fungsi tag memiliki penunjuk yang sama (seperti yang ditunjukkan pada console.log kedua). Browser tidak membuat ulang array strings baru di setiap pemanggilan fungsi tag karena menggunakan literal template yang sama (yaitu di lokasi yang sama di AST). Jadi, mem-binding, mengurai, dan men-cache template Lit dapat memanfaatkan fitur ini tanpa memerlukan banyak biaya overhead diffing runtime.

Perilaku browser bawaan dari literal template yang diberi tag ini memberikan keunggulan performa bagi Lit. Sebagian besar DOM Virtual konvensional melakukan mayoritas pekerjaan di JavaScript. Namun, literal template yang diberi tag dapat melakukan sebagian besar diffing di C++ browser.

Jika Anda ingin mulai menggunakan literal template yang diberi tag HTML dengan React atau Preact, tim Lit akan merekomendasikan htm library.

Namun, seperti halnya situs Google Codelabs dan beberapa editor kode online, Anda akan melihat bahwa penyorotan sintaksis untuk literal template yang diberi tag bukan hal yang umum. Beberapa IDE dan editor teks mendukungnya secara default, seperti Atom dan penyorot kode GitHub. Tim Lit juga bekerja sangat erat dengan komunitas untuk mengelola project seperti lit-plugin yang merupakan plugin VS Code yang akan menambahkan penyorotan sintaksis, pemeriksaan jenis, dan intellisense ke project Lit Anda.

Lit & JSX + React DOM

JSX tidak berjalan di browser dan sebagai gantinya gunakan praprosesor untuk mengonversi panggilan JSX ke panggilan fungsi JavaScript (biasanya melalui Babel).

Misalnya, Babel akan mengubah ini:

const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);

menjadi ini:

const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);

React DOM kemudian mengambil output React dan menerjemahkannya ke DOM yang sebenarnya – properti, atribut, pemroses peristiwa, dan semua.

Lit-html menggunakan literal template bertag yang dapat berjalan di browser tanpa transpilasi atau praprosesor. Ini artinya untuk mulai menggunakan Lit, Anda hanya memerlukan file HTML, skrip modul ES, dan server. Berikut ini skrip yang sepenuhnya dapat dijalankan dengan browser:

<!DOCTYPE html>
<html>
  <head>
    <script type="module">
      import {html, render} from 'https://cdn.skypack.dev/lit';

      render(
        html`<div>Hello World!</div>`,
        document.querySelector('.root')
      )
    </script>
  </head>
  <body>
    <div class="root"></div>
  </body>
</html>

Selain itu, karena sistem template Lit adalah lit-html dan tidak menggunakan DOM Virtual kovensional, melainkan menggunakan API DOM secara langsung, ukuran Lit 2 di bawah 5 kb yang telah diperkecil dan di-gzip dibandingkan dengan React (2,8 kb) + reaksi-dom (39,4 kb) 40kb yang telah diperkecil dan di-gizip juga.

Peristiwa

React menggunakan sistem peristiwa sintetis. Ini artinya react-dom harus mendefinisikan setiap peristiwa yang akan digunakan di setiap komponen dan menyediakan pemroses peristiwa camelCase yang setara untuk setiap jenis node. Oleh karena itu, JSX tidak memiliki metode untuk menentukan pemroses peristiwa untuk peristiwa kustom dan developer harus menggunakan ref, lalu menerapkan pemroses secara imperatif. Hal ini akan menghasilkan pengalaman developer sub-par saat mengintegrasikan library yang tidak memiliki React sehingga harus menulis wrapper khusus React.

Lit-html mengakses DOM secara langsung dan menggunakan peristiwa native sehingga penambahan pemroses peristiwa menjadi semudah @event-name=${eventNameListener}. Ini berarti penguraian runtime yang lebih sedikit dilakukan untuk menambahkan pemroses peristiwa serta mengaktifkan peristiwa.

Komponen & Properti

Komponen React & elemen khusus

Di balik layar, LitEement menggunakan elemen kustom untuk mengemas komponennya. Elemen kustom memasukkan beberapa keseimbangan antara komponen React saat berhubungan dengan komponentasi (status dan siklus proses dibahas lebih lanjut di bagian Status & Siklus Proses).

Beberapa keunggulan yang dimiliki Elemen Kustom sebagai sistem komponen:

  • Native untuk browser dan tidak memerlukan alat apa pun
  • Cocok dengan semua API browser dari innerHTML dan document.createElement hingga querySelector
  • Biasanya dapat digunakan di seluruh framework
  • Dapat dengan santai didaftarkan dengan customElements.define dan DOM "hydrate"

Beberapa kerugian yang dimiliki Elemen Kustom dibandingkan dengan komponen React:

  • Tidak dapat membuat elemen khusus tanpa menetapkan class (tidak ada komponen fungsional seperti JSX)
  • Harus berisi tag penutup
    • Catatan: meskipun memiliki kemudahan developer, vendor browser cenderung tidak menyukai spesifikasi tag yang menutup sendiri, itulah sebabnya spesifikasi yang lebih baru cenderung tidak menyertakan tag yang menutup sendiri
  • Mendaftarkan node tambahan ke hierarki DOM yang dapat menyebabkan masalah tata letak
  • Harus terdaftar melalui JavaScript

Lit telah berjalan dengan elemen kustom di atas sistem elemen yang dipesan terlebih dahulu karena elemen kustom ditanamkan ke dalam browser, dan tim Lit percaya bahwa manfaat lintas-framework lebih besar daripada manfaat yang diberikan oleh lapisan abstraksi komponen. Bahkan, upaya tim Lit di ruang lit-ssr telah mengatasi masalah utama pada pendaftaran JavaScript. Selain itu, beberapa perusahaan seperti GitHub memanfaatkan pendaftaran lazy element kustom untuk secara progresif meningkatkan halaman dengan hiasan opsional.

Meneruskan data ke elemen kustom

Kesalahpahaman yang umum terjadi pada elemen kustom adalah data hanya dapat diteruskan sebagai string. Kesalahpahaman ini mungkin berasal dari fakta bahwa atribut elemen hanya dapat ditulis sebagai string. Meskipun benar bahwa Lit akan mentransmisikan atribut string ke jenis yang ditentukan, elemen kustom juga dapat menerima data yang kompleks sebagai properti.

Misalnya – dengan definisi LitElement berikut:

kode

// data-test.ts
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('data-test')
class DataTest extends LitElement {
  @property({type: Number})
  num = 0;

  @property({attribute: false})
  data = {a: 0, b: null, c: [html`<div>hello</div>`, html`<div>world</div>`]}

  render() {
    return html`
      <div>num + 1 = ${this.num + 1}</div>
      <div>data.a = ${this.data.a}</div>
      <div>data.b = ${this.data.b}</div>
      <div>data.c = ${this.data.c}</div>`;
  }
}

num properti reaktif primitif ditentukan dan akan mengonversi nilai string atribut menjadi number, kemudian struktur data kompleks dimasukkan dengan attribute:false yang menonaktifkan penanganan atribut Lit.

Berikut adalah cara meneruskan data ke elemen khusus ini:

<head>
  <script type="module">
    import './data-test.js'; // loads element definition
    import {html} from './data-test.js';

    const el = document.querySelector('data-test');
    el.data = {
      a: 5,
      b: null,
      c: [html`<div>foo</div>`,html`<div>bar</div>`]
    };
  </script>
</head>
<body>
  <data-test num="5"></data-test>
</body>

Status & Siklus Proses

Callback Siklus Proses React Lainnya

static getDerivedStateFromProps

Tidak ada padanan yang setara di Lit karena properti dan status merupakan properti class yang sama

shouldComponentUpdate

  • Lit setara adalah shouldUpdate
  • Dipanggil pada render pertama tidak seperti React
  • Serupa dengan fungsi untuk shouldComponentUpdate React

getSnapshotBeforeUpdate

Di Lit, getSnapshotBeforeUpdate serupa dengan update dan willUpdate

willUpdate

  • Dipanggil sebelum update
  • Tidak seperti getSnapshotBeforeUpdate, willUpdate dipanggil sebelum render
  • Perubahan pada properti reaktif di willUpdate tidak memicu kembali siklus update
  • Tempat yang baik untuk menghitung nilai properti yang bergantung pada properti lainnya dan digunakan pada proses update lainnya
  • Metode ini dipanggil di server dalam SSR sehingga Anda tidak disarankan mengakses DOM di sini

update

  • Dipanggil setelah willUpdate
  • Tidak seperti getSnapshotBeforeUpdate, update dipanggil sebelum render
  • Perubahan pada properti reaktif di update tidak akan memicu kembali siklus update jika diubah sebelum memanggil super.update
  • Tempat yang baik untuk memperoleh informasi dari DOM yang mengelilingi komponen sebelum output yang dirender diserahkan ke DOM
  • Metode ini tidak dipanggil di server dalam SSR

Callback Siklus Proses Lit Lainnya

Ada beberapa callback siklus proses yang tidak disebutkan di bagian sebelumnya karena tidak ada yang sama dengannya di dalam React. Yaitu:

attributeChangedCallback

Callback ini dipanggil jika salah satu observedAttributes elemen berubah. Baik observedAttributes maupun attributeChangedCallback merupakan bagian dari spesifikasi elemen khusus dan diimplementasikan oleh Lit di bawah hood guna menyediakan API atribut untuk elemen Lit.

adoptedCallback

Dipanggil ketika komponen dipindahkan ke dokumen baru, misalnya dari documentFragment HTMLTemplateElement ke document utama. Callback ini juga merupakan bagian dari spesifikasi elemen kustom dan hanya dapat digunakan untuk kasus penggunaan lanjutan saat komponen mengubah dokumen.

Metode dan properti siklus proses lainnya

Metode dan properti ini merupakan anggota class yang dapat Anda panggil, ganti, atau tunggu untuk membantu memanipulasi proses siklus proses.

updateComplete

Ini adalah Promise yang me-resolve saat elemen selesai diupdate karena update dan siklus proses render bersifat asinkron. Misalnya:

async nextButtonClicked() {
  this.step++;
  // Wait for the next "step" state to render
  await this.updateComplete;
  this.dispatchEvent(new Event('step-rendered'));
}

getUpdateComplete

Ini adalah metode yang harus diganti untuk menyesuaikan saat updateComplete di-resolve. Hal ini umum terjadi ketika komponen merender komponen turunan dan siklus render-nya harus sinkron. Misalnya

class MyElement extends LitElement {
  ...
  async getUpdateComplete() {
    await super.getUpdateComplete();
    await this.myChild.updateComplete;
  }
}

performUpdate

Metode ini adalah apa yang memanggil callback siklus proses update. Hal ini umumnya tidak diperlukan kecuali untuk kasus langka saat update harus dilakukan secara sinkron atau untuk penjadwalan khusus.

hasUpdated

Properti ini adalah true jika komponen telah diupdate setidaknya satu kali.

isConnected

Bagian dari spesifikasi elemen kustom, properti ini akan menjadi true jika elemen saat ini disematkan ke hierarki dokumen utama.

Visualisasi Siklus Proses Update Lit

Ada 3 bagian dari siklus proses update:

  • Pra-update
  • Update
  • Pasca-update

Pra-Update

Grafik node asiklik terarah dengan nama callback. konstruktor ke requestUpdate. @property ke Penyetel Properti. attributeChangedCallback ke Penyetel Properti. Penyetel Properti ke hasChanged. hasChanged ke requestUpdate. requestUpdate mengarah ke grafik siklus proses berikutnya yang diupdate.

Setelah requestUpdate, update terjadwal akan menunggu.

Update

Grafik node asiklik terarah dengan nama callback. Panah dari gambar siklus proses pra-update sebelumnya mengarah ke performUpdate. performUpdate ke shouldUpdate. shouldUpdate mengarah ke "update lengkap jika salah' dan willUpdate. willUpdate ke update. update ke render dan ke grafik siklus proses pasca-update berikutnya. render juga mengarah ke grafik siklus proses pasca-update berikutnya.

Pasca-Update

Grafik node asiklik terarah dengan nama callback. Panah dari gambar siklus proses update sebelumnya mengarah ke firstUpdated. firstUpdated ke diupdate. di-update ke updateComplete.

Hook

Mengapa hook

Hook dimasukkan ke dalam React untuk kasus penggunaan komponen fungsi sederhana yang memerlukan status. Dalam banyak kasus, komponen fungsi dengan hook cenderung lebih sederhana dan lebih mudah dibaca dibandingkan dengan komponen class. Meskipun, saat memperkenalkan update status asinkron serta meneruskan data antar-hook atau antar-efek, pola hook biasanya tidak akan cukup, dan solusi berbasis class seperti pengontrol reaktif cenderung terlihat.

Pengontrol & hook permintaan API

Menulis hook yang meminta data dari API merupakan hal umum. Misalnya, ambil komponen fungsi React ini yang melakukan beberapa hal berikut:

  • index.tsx
    • Merender teks
    • Merender respons useAPI
      • ID Pengguna + Nama pengguna
      • Pesan error
        • 404 saat menjangkau pengguna 11 (berdasarkan desain)
        • Batalkan error jika pengambilan API dibatalkan
      • Memuat Pesan
    • Merender tombol tindakan
      • Pengguna berikutnya: yang mengambil API untuk pengguna berikutnya
      • Batalkan: yang membatalkan pengambilan API dan menampilkan error
  • useApi.tsx
    • Menentukan hook kustom useApi
    • Akan mengambil objek pengguna dari api secara asinkron
    • Memancarkan:
      • Nama pengguna
      • Baik pengambilan sedang dimuat maupun tidak
      • Pesan error apa pun
      • Callback untuk membatalkan pengambilan
    • Pembatalan pengambilan sedang berlangsung jika dibatalkan

Berikut adalah implementasi Lit + Pengontrol Reaktif.

Hasil:

  • Pengontrol Reaktif paling mirip dengan hook kustom
  • Meneruskan data yang tidak dapat dirender antara callback dan efek
    • React menggunakan useRef untuk meneruskan data antara useEffect dan useCallback
    • Lit menggunakan properti class pribadi
    • React pada dasarnya meniru perilaku properti class pribadi

Turunan

Slot Default

Jika elemen HTML tidak diberi atribut slot, elemen tersebut akan ditetapkan ke slot tanpa nama default. Pada contoh di bawah, MyApp akan memasukkan satu paragraf ke dalam slot bernama. Paragraf lainnya akan ditetapkan secara default ke slot tanpa nama".

Playground

@customElement("my-element")
export class MyElement extends LitElement {
  render() {
    return html`
      <section>
        <div>
          <slot></slot>
        </div>
        <div>
          <slot name="custom-slot"></slot>
        </div>
      </section>
   `;
  }
}

@customElement("my-app")
export class MyApp extends LitElement {
  render() {
    return html`
      <my-element>
        <p slot="custom-slot">
          This paragraph will be placed in the custom-slot!
        </p>
        <p>
          This paragraph will be placed in the unnamed default slot!
        </p>
      </my-element>
   `;
  }
}

Update Slot

Saat struktur turunan slot berubah, peristiwa slotchange akan diaktifkan. Komponen Lit dapat mem-binding pemroses peristiwa ke peristiwa slotchange. Pada contoh di bawah, slot pertama yang ditemukan di shadowRoot akan menetapkan assignedNodes yang dicatat ke konsol di slotchange.

@customElement("my-element")
export class MyElement extends LitElement {
  onSlotChange(e: Event) {
    const slot = this.shadowRoot.querySelector('slot');
    console.log(slot.assignedNodes({flatten: true}));
  }

  render() {
    return html`
      <section>
        <div>
          <slot @slotchange="{this.onSlotChange}"></slot>
        </div>
      </section>
   `;
  }
}

Referensi

Pembuatan referensi

Lit dan React akan mengekspos referensi ke HTMLElement setelah fungsi render dipanggil. Namun perlu ditinjau cara React dan Lit menyusun DOM yang nantinya dikembalikan melalui dekorator @query Lit atau Referensi React.

React adalah pipeline fungsional yang membuat Komponen React, bukan HTMLElement. Ruang di memori dialokasikan karena Ref dideklarasikan sebelum HTMLElement dirender. Inilah alasan Anda melihat null sebagai nilai awal referensi karena elemen DOM yang sebenarnya belum dibuat (atau dirender), yaitu useRef(null).

Setelah ReactDOM mengubah Komponen React menjadi HTMLElement, atribut tersebut akan mencari atribut yang dipanggil ref di ReactComponent. Jika tersedia, ReactDOM menempatkan referensi HTMLElement ke ref.current.

LitEement menggunakan fungsi tag template html dari lit-html untuk membuat Elemen Template di balik layar. LitElement menempatkan konten template ke DOM bayangan elemen kustom setelah dirender. DOM bayangan adalah hierarki DOM tercakup yang dienkapsulasi oleh root bayangan. Dekorator @query kemudian membuat pengambil untuk properti yang pada dasarnya menjalankan this.shadowRoot.querySelector pada root tercakup.

Elemen Beberapa Kueri

Pada contoh di bawah ini, dekorator @queryAll akan menampilkan dua paragraf dalam root bayangan sebagai NodeList.

@customElement("my-element")
export class MyElement extends LitElement {
  @queryAll('p')
  paragraphs!: NodeList;

  render() {
    return html`
      <p>Hello, world!</p>
      <p>How are you?</p>
   `;
  }
}

Pada dasarnya, @queryAll membuat pengambil untuk paragraphs yang menampilkan hasil this.shadowRoot.querySelectorAll(). Di JavaScript, pengambil dapat dideklarasikan untuk melakukan tujuan yang sama:

get paragraphs() {
  return this.renderRoot.querySelectorAll('p');
}

Elemen Perubahan Kueri

Dekorator @queryAsync lebih cocok untuk menangani node yang dapat berubah berdasarkan status properti elemen lain.

Pada contoh di bawah, @queryAsync akan menemukan elemen paragraf pertama. Namun, elemen paragraf hanya akan dirender jika renderParagraph menghasilkan angka ganjil secara acak. Perintah @queryAsync akan menampilkan janji yang akan diselesaikan saat paragraf pertama tersedia.

@customElement("my-dissappearing-paragraph")
export class MyDisapppearingParagraph extends LitElement {
  @queryAsync('p')
  paragraph!: Promise<HTMLElement>;

  renderParagraph() {
    const randomNumber = Math.floor(Math.random() * 10)
    if (randomNumber % 2 === 0) {
      return "";
    }

    return html`<p>This checkbox is checked!`
  }

  render() {
    return html`
      ${this.renderParagraph()}
   `;
  }
}

Status Mediasi

Dalam React, pengertian konvensi adalah menggunakan callback karena status dimediasi oleh React itu sendiri. React melakukan yang terbaik untuk tidak bergantung pada status yang disediakan oleh elemen. DOM hanyalah efek dari proses rendering.

Status Eksternal

Anda dapat menggunakan Redux, MobX, atau library pengelolaan status lainnya bersama Lit.

Komponen Lit dibuat dalam cakupan browser. Jadi, library yang juga ada dalam cakupan browser tersedia untuk Lit. Banyak library mengagumkan yang di-build agar memanfaatkan sistem pengelolaan status yang ada di Lit.

Berikut adalah seri oleh Vaadin yang menjelaskan cara memanfaatkan Redux dalam komponen Lit.

Lihat lit-mobx dari Adobe untuk melihat cara situs berskala besar memanfaatkan MobX di Lit.

Sebagai tambahan, lihat Elemen Apollo untuk mengetahui cara developer memasukkan GraphQL dalam komponen web mereka.

Lit berfungsi dengan fitur browser asli dan sebagian besar solusi pengelolaan status dalam cakupan browser dapat digunakan dalam komponen Lit.

Penggayaan

DOM Bayangan

Untuk merangkum gaya dan DOM secara native dalam Elemen Khusus, Lit menggunakan DOM Bayangan. Root Bayangan membuat pohon bayangan yang terpisah dari pohon dokumen utama. Ini berarti bahwa sebagian besar gaya disertakan dalam dokumen ini. Gaya tertentu mengalami kebocoran seperti warna, dan gaya terkait font lainnya.

Bayangan DOM juga memperkenalkan konsep dan pemilih baru pada spesifikasi CSS:

:host, :host(:hover), :host([hover]) {
  /* Styles the element in which the shadow root is attached to */
}

slot[name="title"]::slotted(*), slot::slotted(:hover), slot::slotted([hover]) {
  /*
   * Styles the elements projected into a slot element. NOTE: the spec only allows
   * styling the direcly slotted elements. Children of those elements are not stylable.
   */
}

Berbagi Gaya

Lit memudahkan Anda berbagi gaya antar-komponen dalam bentuk CSSTemplateResults melalui tag template css. Contoh:

// typography.ts
export const body1 = css`
  .body1 {
    ...
  }
`;

// my-el.ts
import {body1} from './typography.ts';

@customElement('my-el')
class MyEl Extends {
  static get styles = [
    body1,
    css`/* local styles come after so they will override bod1 */`
  ]

  render() {
    return html`<div class="body1">...</div>`
  }
}

Tema

Akar bayangan memberikan sedikit tantangan untuk tema konvensional yang biasanya merupakan pendekatan tag gaya top-down. Cara konvensional untuk menanganinya dengan Komponen Web yang menggunakan DOM bayangan adalah mengekspos API gaya melalui Properti Khusus CSS. Misalnya, ini adalah pola yang digunakan Desain Material:

.mdc-textfield-outline {
  border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
  caret-color: var(--mdc-theme-primary, #...);
}

Pengguna tersebut kemudian akan mengubah tema situs dengan menerapkan nilai properti khusus:

html {
  --mdc-theme-primary: #F00;
}
html[dark] {
  --mdc-theme-primary: #F88;
}

Jika tema top-down adalah tema wajib dan Anda tidak dapat mengekspos gaya, Anda dapat menonaktifkan DOM Bayangan dengan mengganti createRenderRoot agar menampilkan this yang kemudian merender template komponen ke elemen khusus itu sendiri daripada ke akar bayangan yang disematkan ke elemen khusus. Dengan ini Anda akan kehilangan: enkapsulasi gaya, enkapsulasi DOM, dan slot.

Produksi

IE 11

Jika harus mendukung browser lama seperti IE 11, Anda harus memuat beberapa polyfill yang mencakup 33 kb lainnya. Informasi selengkapnya dapat ditemukan di sini.

Paket Bersyarat

Tim Lit merekomendasikan Anda untuk menyajikan dua paket yang berbeda, satu untuk IE 11 dan satu untuk browser modern. Ada beberapa manfaatnya:

  • Melayani ES 6 lebih cepat dan akan melayani sebagian besar klien Anda
  • ES 5 yang ditranspilasikan akan meningkatkan ukuran paket secara signifikan
  • Paket bersyarat memberi Anda dengan semua yang terbaik dari kedua dunia
    • Dukungan IE 11
    • Tidak ada pelambatan di browser modern

Info selengkapnya mengenai cara membuat paket yang dilayani secara bersyarat dapat ditemukan di situs dokumentasi kami di sini.