Dari Komponen Web ke Elemen Lit

1. Pengantar

Terakhir diperbarui: 10-08-2021

Komponen Web

Komponen Web adalah sekumpulan API platform web yang memungkinkan Anda membuat tag HTML khusus, yang dapat digunakan kembali, dan terenkapsulasi untuk digunakan di halaman web dan aplikasi web. Widget dan komponen khusus yang dibuat berdasarkan standar Komponen Web akan berfungsi di seluruh browser modern dan dapat digunakan dengan pustaka atau framework JavaScript apa pun yang bekerja dengan HTML.

Apa yang dimaksud dengan Lit

Lit adalah library sederhana untuk membuat komponen web yang cepat dan ringan yang dapat berfungsi di framework apa pun, atau tanpa framework sama sekali. Dengan Lit, Anda dapat membuat komponen, aplikasi, sistem desain yang dapat dibagikan, dan banyak lagi.

Lit menyediakan API untuk menyederhanakan tugas Komponen Web umum seperti mengelola properti, atribut, dan rendering.

Yang akan Anda pelajari

  • Apa itu Komponen Web
  • Konsep Komponen Web
  • Cara membuat Komponen Web
  • Apa yang dimaksud dengan lit-html dan LitElement
  • Yang dilakukan Lit selain komponen web

Yang akan Anda bangun

  • Komponen Web vanila yang disukai / tidak disukai
  • Komponen Web Berbasis Lit yang disukai

Yang Anda butuhkan

  • Semua browser modern yang diupdate (Chrome, Safari, Firefox, Chromium Edge). Komponen Web yang berfungsi di semua browser modern dan polyfill tersedia untuk Microsoft Internet Explorer 11 dan Microsoft Edge non-chromium.
  • Pengetahuan terkait HTML, CSS, JavaScript, dan Chrome DevTools.

2. Mempersiapkan & menjelajahi Playground

Mengakses kode

Di sepanjang codelab ini, akan ada link ke playground Lit seperti ini:

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

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

// after
import './my-file.js';
import 'https://unpkg.com/lit?module';

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

Menjelajahi UI playground yang menyala

Bilah tab pemilih file diberi label Bagian 1, Bagian pengeditan kode sebagai Bagian 2, pratinjau output sebagai Bagian 3, dan tombol muat ulang pratinjau sebagai Bagian 4

Screenshot UI playground Lit menyoroti 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
  • Template Intel & pelengkapan otomatis

Jika Anda sudah menginstal NPM, VS Code (dengan plugin lit-plugin) dan mengetahui cara menggunakan lingkungan tersebut, Anda dapat mendownload dan memulai project ini dengan melakukan hal berikut:

  • Tekan tombol download
  • Ekstrak konten file tar ke direktori
  • Instal server dev yang dapat menyelesaikan penentu modul kosong (tim Lit merekomendasikan @web/dev-server)
  • Jalankan server dev dan buka browser (jika menggunakan @web/dev-server, Anda dapat menggunakan npx web-dev-server --node-resolve --watch --open)
    • Jika Anda menggunakan contoh package.json, gunakan npm run serve

3. Tentukan Elemen Khusus

Elemen Kustom

Komponen Web adalah kumpulan 4 API web native. Bagian-bagian tersebut adalah:

  • Modul ES
  • Elemen Kustom
  • DOM Bayangan
  • Template HTML

Anda sudah menggunakan spesifikasi modul ES, yang memungkinkan Anda membuat modul JavaScript dengan impor dan ekspor yang dimuat ke halaman dengan <script type="module">.

Menentukan Elemen Kustom

Spesifikasi Elemen Kustom memungkinkan pengguna menentukan elemen HTML mereka sendiri menggunakan JavaScript. Nama harus berisi tanda hubung (-) untuk membedakannya dari elemen browser native. Kosongkan file index.js dan tentukan class elemen kustom:

index.js

class RatingElement extends HTMLElement {}

customElements.define('rating-element', RatingElement);

Elemen kustom ditentukan dengan mengaitkan class yang memperluas HTMLElement dengan nama tag dengan tanda hubung. Panggilan ke customElements.define memberi tahu browser untuk mengaitkan class RatingElement dengan tagName ‘rating-element'. Artinya, setiap elemen dalam dokumen Anda dengan nama <rating-element> akan dikaitkan dengan class ini.

Tempatkan <rating-element> dalam isi dokumen dan lihat apa yang dirender.

index.html

<body>
 <rating-element></rating-element>
</body>

Sekarang, dengan melihat output-nya, Anda akan melihat bahwa tidak ada yang dirender. Ini wajar terjadi karena Anda belum memberi tahu browser cara merender <rating-element>. Anda dapat mengonfirmasi bahwa definisi Elemen Kustom telah berhasil dengan memilih <rating-element> di Chrome Dev Tools pemilih elemen dan, di konsol, memanggil:

$0.constructor

Yang akan menghasilkan output:

class RatingElement extends HTMLElement {}

Siklus Proses Elemen Kustom

Elemen Kustom dilengkapi dengan serangkaian hook siklus proses. Bagian-bagian tersebut adalah:

  • constructor
  • connectedCallback
  • disconnectedCallback
  • attributeChangedCallback
  • adoptedCallback

constructor dipanggil saat elemen pertama kali dibuat: misalnya, dengan memanggil document.createElement(‘rating-element') atau new RatingElement(). Konstruktor adalah tempat yang baik untuk menyiapkan elemen Anda, tetapi biasanya dianggap praktik yang buruk untuk melakukan manipulasi DOM dalam konstruktor untuk elemen "boot-up" alasan performa.

connectedCallback dipanggil saat elemen kustom dilampirkan ke DOM. Di sinilah manipulasi DOM awal terjadi.

disconnectedCallback dipanggil setelah elemen kustom dihapus dari DOM.

attributeChangedCallback(attrName, oldValue, newValue) dipanggil saat salah satu atribut yang ditentukan pengguna berubah.

adoptedCallback dipanggil saat elemen kustom diadopsi dari documentFragment lain ke dalam dokumen utama melalui adoptNode seperti di HTMLTemplateElement.

Render DOM

Sekarang, kembali ke elemen kustom dan kaitkan beberapa DOM dengannya. Setel konten elemen saat dilampirkan ke DOM:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   this.innerHTML = `
     <style>
       rating-element {
         display: inline-flex;
         align-items: center;
       }
       rating-element button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

Di constructor, Anda menyimpan properti instance yang disebut rating di elemen. Di connectedCallback, Anda menambahkan turunan DOM ke <rating-element> untuk menampilkan rating saat ini, beserta tombol suka dan tidak suka.

4. DOM Bayangan

Mengapa Shadow DOM?

Pada langkah sebelumnya, Anda akan melihat bahwa pemilih di tag gaya yang Anda sisipkan untuk memilih elemen rating pada halaman serta tombol apa pun. Hal ini dapat menyebabkan gaya bocor dari elemen dan memilih simpul lain yang mungkin tidak ingin Anda tata gayanya. Selain itu, gaya lain di luar elemen kustom ini mungkin tidak sengaja menata gaya node di dalam elemen kustom Anda. Misalnya, coba tempatkan tag gaya di bagian head dokumen utama:

index.html

<!DOCTYPE html>
<html>
 <head>
   <script src="./index.js" type="module"></script>
   <style>
     span {
       border: 1px solid red;
     }
   </style>
 </head>
 <body>
   <rating-element></rating-element>
 </body>
</html>

Output Anda akan memiliki kotak batas merah di sekitar rentang rating. Ini adalah kasus yang sepele, namun kurangnya enkapsulasi DOM dapat mengakibatkan masalah yang lebih besar untuk aplikasi yang lebih kompleks. Di sinilah Shadow DOM berperan.

Memasang Root Bayangan

Lampirkan Root Bayangan ke elemen dan render DOM di dalam root tersebut:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});

   shadowRoot.innerHTML = `
     <style>
       :host {
         display: inline-flex;
         align-items: center;
       }
       button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

Saat memuat ulang halaman, Anda akan melihat bahwa gaya dalam dokumen utama tidak lagi dapat memilih node di dalam Shadow Root.

Bagaimana Anda melakukannya? Dalam connectedCallback, Anda memanggil this.attachShadow yang melampirkan root bayangan ke elemen. Mode open berarti konten bayangan dapat diperiksa dan juga membuat root bayangan dapat diakses melalui this.shadowRoot. Lihat juga Komponen Web di pemeriksa Chrome:

Pohon dom di pemeriksa chrome. Ada <rating-element> dengan#shadow-root (open) sebagai turunannya, dan DOM dari sebelumnya dari dalam shadowroot tersebut.

Sekarang Anda akan melihat akar bayangan yang dapat diperluas yang menampung konten. Segala sesuatu di dalam shadow root itu disebut Shadow DOM. Jika Anda memilih elemen rating di Chrome Dev Tools dan memanggil $0.children, Anda akan melihat bahwa elemen tersebut tidak menampilkan turunan. Ini karena Shadow DOM tidak dianggap sebagai bagian dari hierarki DOM yang sama dengan turunan langsung, melainkan Shadow Tree.

DOM Ringan

Eksperimen: menambahkan node sebagai turunan langsung dari <rating-element>:

index.html

<rating-element>
 <div>
   This is the light DOM!
 </div>
</rating-element>

Muat ulang halaman, dan Anda akan melihat bahwa node DOM baru di Light DOM Elemen Kustom ini tidak muncul di halaman. Hal ini karena Shadow DOM memiliki fitur untuk mengontrol cara node Light DOM diproyeksikan ke dalam shadow dom melalui elemen <slot>.

5. Template HTML

Mengapa Template

Menggunakan innerHTML dan string literal template tanpa sanitasi dapat menyebabkan masalah keamanan dengan injeksi skrip. Metode di masa lalu telah mencakup penggunaan DocumentFragment, tetapi masalah ini juga disertai dengan masalah lain seperti pemuatan gambar dan skrip yang berjalan saat template ditentukan serta menghadirkan hambatan untuk digunakan kembali. Di sinilah elemen <template> berperan; template menyediakan DOM inert, metode berperforma tinggi untuk meng-clone node, dan template yang dapat digunakan kembali.

Menggunakan Template

Selanjutnya, transisikan komponen untuk menggunakan Template HTML:

index.html

<body>
 <template id="rating-element-template">
   <style>
     :host {
       display: inline-flex;
       align-items: center;
     }
     button {
       background: transparent;
       border: none;
       cursor: pointer;
     }
   </style>
   <button class="thumb_down" >
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
   </button>
   <span class="rating"></span>
   <button class="thumb_up">
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
   </button>
 </template>

 <rating-element>
   <div>
     This is the light DOM!
   </div>
 </rating-element>
</body>

Di sini Anda telah memindahkan konten DOM ke tag template di DOM dokumen utama. Sekarang faktorkan ulang definisi elemen kustom:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});
   const templateContent = document.getElementById('rating-element-template').content;
   const clonedContent = templateContent.cloneNode(true);
   shadowRoot.appendChild(clonedContent);

   this.shadowRoot.querySelector('.rating').innerText = this.rating;
 }
}

customElements.define('rating-element', RatingElement);

Untuk menggunakan elemen template ini, Anda perlu membuat kueri template, mendapatkan kontennya, dan meng-clone node tersebut dengan templateContent.cloneNode tempat argumen true melakukan clone mendalam. Anda kemudian melakukan inisialisasi dom dengan data.

Selamat, Anda sekarang memiliki Komponen Web! Sayangnya belum ada tindakan apa pun, jadi selanjutnya, tambahkan beberapa fungsi.

6. Menambahkan Fungsi

Binding Properti

Saat ini, satu-satunya cara untuk menetapkan rating pada elemen rating adalah dengan membuat elemen, menetapkan properti rating pada objek, lalu menempatkannya pada halaman. Sayangnya, ini bukan cara kerja elemen HTML native. Elemen HTML native cenderung diperbarui baik dengan perubahan properti maupun atribut.

Buat elemen kustom memperbarui tampilan saat properti rating berubah dengan menambahkan baris berikut:

index.js

constructor() {
  super();
  this._rating = 0;
}

set rating(value) {
  this._rating = value;
  if (!this.shadowRoot) {
    return;
  }

  const ratingEl = this.shadowRoot.querySelector('.rating');
  if (ratingEl) {
    ratingEl.innerText = this._rating;
  }
}

get rating() {
  return this._rating;
}

Anda menambahkan penyetel dan pengambil untuk properti rating, lalu memperbarui teks elemen rating jika tersedia. Ini berarti jika Anda menetapkan properti peringkat pada elemen, tampilan akan diperbarui; lakukan tes cepat di konsol Alat Dev Anda!

Binding Atribut

Sekarang, perbarui tampilan saat atribut berubah; ini mirip dengan input yang memperbarui tampilannya saat Anda menyetel <input value="newValue">. Untungnya, siklus proses Komponen Web mencakup attributeChangedCallback. Perbarui rating dengan menambahkan baris berikut:

index.js

static get observedAttributes() {
 return ['rating'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
 if (attributeName === 'rating') {
   const newRating = Number(newValue);
   this.rating = newRating;
 }
}

Agar attributeChangedCallback dapat dipicu, Anda harus menetapkan pengambil statis untuk RatingElement.observedAttributes which defines the attributes to be observed for changes. Anda kemudian menetapkan rating secara deklaratif di DOM. Cobalah:

index.html

<rating-element rating="5"></rating-element>

Sekarang rating akan diperbarui secara deklaratif.

Fungsi Tombol

Sekarang yang tidak ada hanyalah fungsi tombol. Perilaku komponen ini harus memungkinkan pengguna untuk memberikan satu peringkat suara naik atau turun dan memberikan masukan visual kepada pengguna. Anda bisa mengimplementasikannya dengan beberapa pemroses peristiwa dan properti refleksi, tetapi pertama-tama perbarui gaya untuk memberikan masukan visual dengan menambahkan baris berikut:

index.html

<style>
...

 :host([vote=up]) .thumb_up {
   fill: green;
 }
  :host([vote=down]) .thumb_down {
   fill: red;
 }
</style>

Dalam Shadow DOM, pemilih :host merujuk pada node atau elemen khusus yang dikaitkan dengan Shadow Root. Dalam hal ini, jika atribut vote adalah "up", tombol suka akan berubah menjadi hijau, tetapi jika vote adalah "down", then it will turn the thumb-down button red. Sekarang, terapkan logika untuk hal ini dengan membuat properti / atribut yang mencerminkan untuk vote seperti cara Anda menerapkan rating. Mulai dengan penyetel dan pengambil properti:

index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }
  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }
  this._vote = newValue;
  this.setAttribute('vote', newValue);
}

get vote() {
  return this._vote;
}

Anda melakukan inisialisasi properti instance _vote dengan null di constructor, dan di penyetel, periksa apakah nilai barunya berbeda. Jika ya, Anda akan menyesuaikan rating dengan semestinya dan, yang penting, merefleksikan kembali atribut vote ke host dengan this.setAttribute.

Selanjutnya, siapkan binding atribut:

index.js

static get observedAttributes() {
  return ['rating', 'vote'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
  if (attributeName === 'rating') {
    const newRating = Number(newValue);

    this.rating = newRating;
  } else if (attributeName === 'vote') {
    this.vote = newValue;
  }
}

Sekali lagi, ini adalah proses yang sama dengan yang Anda lakukan pada binding atribut rating; Anda menambahkan vote ke observedAttributes, dan menetapkan properti vote di attributeChangedCallback. Dan sekarang, tambahkan beberapa pemroses peristiwa klik untuk memberikan fungsi tombol!

index.js

constructor() {
 super();
 this._rating = 0;
 this._vote = null;
 this._boundOnUpClick = this._onUpClick.bind(this);
 this._boundOnDownClick = this._onDownClick.bind(this);
}

connectedCallback() {
  ...
  this.shadowRoot.querySelector('.thumb_up')
    .addEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .addEventListener('click', this._boundOnDownClick);
}

disconnectedCallback() {
  this.shadowRoot.querySelector('.thumb_up')
    .removeEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .removeEventListener('click', this._boundOnDownClick);
}

_onUpClick() {
  this.vote = 'up';
}

_onDownClick() {
  this.vote = 'down';
}

Di constructor, Anda mengikat beberapa pemroses klik ke elemen dan mempertahankan referensinya. Di connectedCallback, Anda memproses peristiwa klik pada tombol. Di disconnectedCallback, Anda membersihkan pemroses ini, dan pada pemroses klik itu sendiri, Anda menetapkan vote dengan benar.

Selamat, Anda sekarang memiliki Komponen Web dengan fitur lengkap; coba klik beberapa tombol! Masalahnya sekarang adalah bahwa file JS saya sekarang mencapai 96 baris, file HTML saya 43 baris, dan kodenya cukup panjang dan penting untuk komponen sederhana seperti itu. Di sinilah proyek Lit Google berperan.

7. Lit-html

Titik Pemeriksaan Kode

Mengapa lit-html

Pertama-tama, tag <template> berguna dan berperforma tinggi, tetapi tidak dikemas dengan logika komponen sehingga sulit mendistribusikan template dengan logika lainnya. Selain itu, cara penggunaan elemen template secara inheren meminjamkan kode imperatif, yang dalam banyak kasus, menyebabkan kode yang kurang mudah dibaca dibandingkan dengan pola coding deklaratif.

Di sinilah lit-html berperan! Lit HTML adalah sistem rendering Lit yang memungkinkan Anda menulis template HTML dalam JavaScript, lalu secara efisien merender dan merender ulang template tersebut bersama dengan data untuk membuat dan memperbarui DOM. Hal ini mirip dengan library JSX dan VDOM yang populer tetapi dapat berjalan secara native di browser dan jauh lebih efisien dalam banyak kasus.

Menggunakan Lit HTML

Selanjutnya, migrasikan Komponen Web native rating-element untuk menggunakan template Lit yang menggunakan Literal Template yang Diberi Tag yang merupakan fungsi yang menggunakan string template sebagai argumen dengan sintaksis khusus. Lit kemudian menggunakan elemen template di balik layar untuk menyediakan rendering cepat serta menyediakan beberapa fitur sanitasi untuk keamanan. Mulai dengan memigrasikan <template> di index.html ke template Lit dengan menambahkan metode render() ke komponen web:

index.js

// Dont forget to import from Lit!
import {render, html} from 'lit';

class RatingElement extends HTMLElement {
  ...
  render() {
    if (!this.shadowRoot) {
      return;
    }

    const template = html`
      <style>
        :host {
          display: inline-flex;
          align-items: center;
        }
        button {
          background: transparent;
          border: none;
          cursor: pointer;
        }

       :host([vote=up]) .thumb_up {
         fill: green;
       }

       :host([vote=down]) .thumb_down {
         fill: red;
       }
      </style>
      <button class="thumb_down">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
      </button>
      <span class="rating">${this.rating}</span>
      <button class="thumb_up">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
      </button>`;

    render(template, this.shadowRoot);
  }
}

Anda juga dapat menghapus template dari index.html. Dalam metode render ini, Anda menentukan variabel yang disebut template dan memanggil fungsi literal template yang diberi tag html. Anda juga akan melihat bahwa Anda telah menjalankan data binding sederhana di dalam elemen span.rating dengan menggunakan sintaksis interpolasi literal template ${...}. Artinya, pada akhirnya Anda tidak perlu lagi mengupdate node tersebut secara imperatif. Selain itu, Anda memanggil metode render menyala yang secara sinkron merender template ke shadow root.

Bermigrasi ke Sintaksis Deklaratif

Setelah Anda menghapus elemen <template>, faktorkan ulang kode untuk memanggil metode render yang baru ditetapkan. Anda dapat memulai dengan memanfaatkan binding pemroses peristiwa lit untuk membersihkan kode pemroses:

index.js

<button
    class="thumb_down"
    @click=${() => {this.vote = 'down'}}>
...
<button
    class="thumb_up"
    @click=${() => {this.vote = 'up'}}>

Template Lit dapat menambahkan pemroses peristiwa ke node dengan sintaksis binding @EVENT_NAME yang, dalam hal ini, Anda memperbarui properti vote setiap kali tombol ini diklik.

Selanjutnya, bersihkan kode inisialisasi pemroses peristiwa di constructor, connectedCallback, dan disconnectedCallback:

index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

connectedCallback() {
  this.attachShadow({mode: 'open'});
  this.render();
}

// remove disonnectedCallback and _onUpClick and _onDownClick

Anda dapat menghapus logika pemroses klik dari ketiga callback dan bahkan menghapus disconnectedCallback seluruhnya. Anda juga dapat menghapus semua kode inisialisasi DOM dari connectedCallback sehingga terlihat jauh lebih elegan. Ini juga berarti bahwa Anda dapat menghapus metode pemroses _onUpClick dan _onDownClick.

Terakhir, perbarui penyetel properti untuk menggunakan metode render sehingga dom dapat diperbarui saat properti atau atribut berubah:

index.js

set rating(value) {
  this._rating = value;
  this.render();
}

...

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }

  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }

  this._vote = newValue;
  this.setAttribute('vote', newValue);
  // add render method
  this.render();
}

Di sini, Anda dapat menghapus logika update DOM dari penyetel rating dan menambahkan panggilan ke render dari penyetel vote. Template ini kini jauh lebih mudah dibaca karena Anda dapat melihat di mana binding dan pemroses peristiwa diterapkan.

Muat ulang halaman, dan Anda akan memiliki tombol rating yang berfungsi seperti ini saat suara positif ditekan.

Penggeser rating suka dan tidak suka dengan nilai 6 dan jempol ke atas berwarna hijau

8. LitElement

Mengapa LitElement

Beberapa masalah masih ada terkait kode. Pertama, jika Anda mengubah properti atau atribut vote, hal ini dapat mengubah properti rating yang akan menyebabkan pemanggilan render dua kali. Meskipun panggilan render berulang pada dasarnya tidak beroperasi dan efisien, VM JavaScript masih menghabiskan waktu untuk memanggil fungsi itu dua kali secara sinkron. Kedua, menambahkan properti dan atribut baru adalah hal yang membosankan karena memerlukan banyak kode boilerplate. Di sinilah LitElement berperan.

LitElement adalah class dasar Lit untuk membuat Komponen Web yang cepat dan ringan yang dapat digunakan di berbagai framework dan lingkungan. Selanjutnya, lihat apa yang dapat dilakukan LitElement untuk kita di rating-element dengan mengubah implementasi untuk menggunakannya.

Menggunakan LitElement

Mulai dengan mengimpor dan membuat subclass class dasar LitElement dari paket lit:

index.js

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

class RatingElement extends LitElement {
// remove connectedCallback()
...

Anda mengimpor LitElement yang merupakan class dasar baru untuk rating-element. Selanjutnya, Anda mempertahankan impor html dan terakhir css yang memungkinkan kita menentukan literal template yang diberi tag css untuk matematika css, pembuatan template, dan fitur lainnya.

Selanjutnya, pindahkan gaya dari metode render ke stylesheet statis Lit:

index.js

class RatingElement extends LitElement {
  static get styles() {
    return css`
      :host {
        display: inline-flex;
        align-items: center;
      }
      button {
        background: transparent;
        border: none;
        cursor: pointer;
      }

      :host([vote=up]) .thumb_up {
        fill: green;
      }

      :host([vote=down]) .thumb_down {
        fill: red;
      }
    `;
  }
 ...

Di sinilah sebagian besar gaya berada di Lit. Lit akan menggunakan gaya ini dan menggunakan fitur browser seperti Constructable Stylesheets untuk memberikan waktu rendering yang lebih cepat serta meneruskannya melalui polyfill Komponen Web pada browser yang lebih lama jika perlu.

Lifecycle

Lit memperkenalkan serangkaian metode callback siklus proses render di atas callback Komponen Web native. Callback ini dipicu saat properti Lit yang dideklarasikan diubah.

Untuk menggunakan fitur ini, Anda harus secara statis mendeklarasikan properti mana yang akan memicu siklus proses render.

index.js

static get properties() {
  return {
    rating: {
      type: Number,
    },
    vote: {
      type: String,
      reflect: true,
    }
  };
}

// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()

Di sini, Anda menentukan bahwa rating dan vote akan memicu siklus proses rendering LitElement serta menentukan jenis yang akan digunakan untuk mengonversi atribut string menjadi properti.

<user-profile .name=${this.user.name} .age=${this.user.age}>
  ${this.user.family.map(member => html`
        <family-member
             .name=${member.name}
             .relation=${member.relation}>
        </family-member>`)}
</user-profile>

Selain itu, tanda reflect di properti vote akan otomatis memperbarui atribut vote elemen host yang Anda picu secara manual di penyetel vote.

Setelah memiliki blok properti statis, Anda dapat menghapus semua logika pembaruan render properti dan atribut. Ini berarti Anda dapat menghapus metode berikut:

  • connectedCallback
  • observedAttributes
  • attributeChangedCallback
  • rating (penyetel dan pengambil)
  • vote (penyetel dan pengambil, tetapi menyimpan logika perubahan dari penyetel)

Yang Anda pertahankan adalah constructor serta menambahkan metode siklus proses willUpdate baru:

index.js

constructor() {
  super();
  this.rating = 0;
  this.vote = null;
}

willUpdate(changedProps) {
  if (changedProps.has('vote')) {
    const newValue = this.vote;
    const oldValue = changedProps.get('vote');

    if (newValue === 'up') {
      if (oldValue === 'down') {
        this.rating += 2;
      } else {
        this.rating += 1;
      }
    } else if (newValue === 'down') {
      if (oldValue === 'up') {
        this.rating -= 2;
      } else {
        this.rating -= 1;
      }
    }
  }
}

// remove set vote() and get vote()

Di sini, Anda cukup melakukan inisialisasi rating dan vote lalu memindahkan logika penyetel vote ke metode siklus proses willUpdate. Metode willUpdate dipanggil sebelum render setiap kali properti yang diperbarui diubah, karena LitElement mengelompokkan perubahan properti dan membuat rendering asinkron. Perubahan pada properti reaktif (seperti this.rating) di willUpdate tidak akan memicu panggilan siklus proses render yang tidak perlu.

Terakhir, render adalah metode siklus proses LitElement yang mengharuskan kita menampilkan template Lit:

index.js

render() {
  return html`
    <button
        class="thumb_down"
        @click=${() => {this.vote = 'down'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
    </button>
    <span class="rating">${this.rating}</span>
    <button
        class="thumb_up"
        @click=${() => {this.vote = 'up'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
    </button>`;
}

Anda tidak perlu lagi memeriksa root bayangan, dan tidak perlu lagi memanggil fungsi render yang sebelumnya diimpor dari paket 'lit'.

Elemen Anda akan dirender dalam pratinjau sekarang; klik!

9. Selamat

Selamat, Anda telah berhasil membuat Komponen Web dari awal dan mengembangkannya menjadi LitElement.

Lit sangat kecil (< 5 kb diminifikasi + gzip), sangat cepat, dan sangat menyenangkan untuk membuat kode. Anda dapat membuat komponen untuk digunakan oleh framework lain, atau Anda dapat membangun aplikasi yang lengkap dengannya.

Sekarang Anda telah mengetahui apa yang dimaksud dengan Komponen Web, cara membuatnya, dan bagaimana Lit memudahkan pembuatannya.

Titik Pemeriksaan Kode

Ingin membandingkan kode final Anda dengan kode kami? Bandingkan di sini.

Apa selanjutnya?

Lihat beberapa codelab lainnya.

Bacaan lebih lanjut

Komunitas