Dari Komponen Web ke Elemen Lit

1. Pengantar

Terakhir Diperbarui: 10-08-2021

Web Components

Komponen Web adalah sekumpulan API platform web yang memungkinkan Anda membuat tag HTML kustom, yang dapat digunakan kembali, dan dienkapsulasi untuk digunakan di halaman web dan aplikasi web. Komponen dan widget kustom yang dibuat berdasarkan standar Komponen Web akan berfungsi di seluruh browser modern dan dapat digunakan dengan library atau framework JavaScript apa pun yang berfungsi dengan HTML.

Apa itu Lit

Lit adalah library sederhana untuk membuat komponen web yang cepat dan ringan yang berfungsi di framework apa pun, atau tanpa framework sama sekali. Dengan bantuan Lit, Anda dapat membuat komponen yang bisa dibagikan, membuat aplikasi, sistem desain, dan masih 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 itu lit-html dan LitElement
  • Yang dilakukan Lit di atas komponen web

Yang akan Anda bangun

  • Komponen Web vanila suka / tidak suka
  • Komponen Web berbasis Lit dengan ikon suka / tidak suka

Yang Anda butuhkan

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

2. Menyiapkan & menjelajahi Playground

Mengakses kode

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

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://unpkg.com/lit?module';

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 ini:

  • 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
  • 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. Menentukan Elemen Kustom

Elemen Kustom

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

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

Anda telah menggunakan spesifikasi modul ES, yang memungkinkan Anda membuat modul javascript dengan impor dan ekspor yang dimuat ke dalam 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 bawaan. Hapus 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 bergaris 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 kelas ini.

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

index.html

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

Sekarang, dengan melihat output, Anda akan melihat bahwa tidak ada yang dirender. Hal ini wajar terjadi karena Anda belum memberi tahu browser cara merender <rating-element>. Anda dapat mengonfirmasi bahwa definisi Elemen Kustom berhasil dengan memilih <rating-element> di pemilih elemen Chrome DevTools 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 tepat untuk menyiapkan elemen, tetapi biasanya dianggap sebagai praktik yang buruk untuk melakukan manipulasi DOM di konstruktor karena alasan performa "boot-up" elemen.

connectedCallback dipanggil saat elemen kustom dilampirkan ke DOM. Di sinilah biasanya 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 dokumen utama melalui adoptNode seperti pada HTMLTemplateElement.

Render DOM

Sekarang, kembali ke elemen kustom dan kaitkan beberapa DOM dengannya. Tetapkan konten elemen saat elemen 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 pada elemen. Di connectedCallback, Anda menambahkan elemen turunan DOM ke <rating-element> untuk menampilkan rating saat ini, bersama dengan 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 memilih elemen rating apa pun di halaman serta tombol apa pun. Hal ini dapat menyebabkan gaya keluar dari elemen dan memilih node lain yang mungkin tidak ingin Anda beri gaya. Selain itu, gaya lain di luar elemen kustom ini dapat secara tidak sengaja menata gaya node di dalam elemen kustom Anda. Misalnya, coba letakkan tag gaya di 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 harus memiliki kotak batas merah di sekitar rentang untuk rating. Ini adalah kasus sepele, tetapi kurangnya enkapsulasi DOM dapat menyebabkan masalah yang lebih besar untuk aplikasi yang lebih kompleks. Di sinilah Shadow DOM berperan.

Melampirkan Shadow Root

Lampirkan Shadow Root 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 di dokumen utama tidak dapat lagi memilih node di dalam Shadow Root.

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

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

Sekarang Anda akan melihat root shadow yang dapat diluaskan yang menyimpan konten. Semua yang ada di dalam root bayangan tersebut 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. Hal ini karena Shadow DOM tidak dianggap sebagai bagian dari hierarki DOM yang sama dengan elemen 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 ini 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 shadow DOM melalui elemen <slot>.

5. Template HTML

Alasan Menggunakan Template

Menggunakan innerHTML dan string literal template tanpa sanitasi dapat menyebabkan masalah keamanan dengan injeksi skrip. Metode sebelumnya mencakup penggunaan DocumentFragment, tetapi metode ini juga menimbulkan masalah lain seperti gambar yang dimuat dan skrip yang berjalan saat template ditentukan, serta menimbulkan hambatan untuk penggunaan ulang. Di sinilah elemen <template> berperan; template menyediakan DOM inert, metode berperforma tinggi untuk meng-clone node, dan pembuatan 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 memindahkan konten DOM ke dalam tag template di DOM dokumen utama. Sekarang, refaktor 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 mengkueri template, mendapatkan isinya, dan meng-clone node tersebut dengan templateContent.cloneNode di mana argumen true melakukan clone mendalam. Kemudian, Anda menginisialisasi DOM dengan data.

Selamat, Anda kini memiliki Komponen Web. Sayangnya, kode ini belum melakukan apa pun, jadi selanjutnya, tambahkan beberapa fungsi.

6. Menambahkan Fungsi

Pengikatan Properti

Saat ini, satu-satunya cara untuk menyetel rating pada elemen rating adalah dengan membuat elemen, menyetel properti rating pada objek, lalu menempatkannya di halaman. Sayangnya, elemen HTML native biasanya tidak berfungsi seperti ini. Elemen HTML asli cenderung diperbarui dengan perubahan properti dan 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. Artinya, jika Anda menetapkan properti rating pada elemen, tampilan akan diperbarui; lakukan pengujian cepat di konsol DevTools Anda.

Binding Atribut

Sekarang, perbarui tampilan saat atribut berubah; hal ini mirip dengan input yang memperbarui tampilannya saat Anda menetapkan <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 menyetel getter statis untuk RatingElement.observedAttributes which defines the attributes to be observed for changes. Kemudian, Anda menetapkan rating secara deklaratif di DOM. Cobalah:

index.html

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

Rating sekarang akan diperbarui secara deklaratif.

Fungsi Tombol

Sekarang yang kurang adalah fungsi tombol. Perilaku komponen ini harus memungkinkan pengguna memberikan rating suara positif atau negatif tunggal dan memberikan respons visual kepada pengguna. Anda dapat menerapkannya 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>

Di Shadow DOM, pemilih :host merujuk ke node atau elemen kustom yang terlampir pada 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 ini dengan membuat properti / atribut refleksi untuk vote mirip dengan cara Anda menerapkan rating. Mulai dengan setter dan getter 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 menginisialisasi properti instance _vote dengan null di constructor, dan di setter, Anda memeriksa apakah nilai baru berbeda. Jika demikian, Anda menyesuaikan rating yang sesuai dan, yang penting, mencerminkan kembali atribut vote ke host dengan this.setAttribute.

Selanjutnya, siapkan pengikatan 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 saat mengikat atribut rating; Anda menambahkan vote ke observedAttributes, dan Anda menetapkan properti vote di attributeChangedCallback. Terakhir, 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 menyimpan 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 tepat.

Selamat, Anda kini memiliki Komponen Web berfitur lengkap; coba klik beberapa tombol. Masalahnya sekarang adalah file JS saya mencapai 96 baris, file HTML 43 baris, dan kodenya cukup panjang dan imperatif untuk komponen yang begitu sederhana. Di sinilah project Lit Google berperan.

7. Lit-html

Code Checkpoint

Alasan menggunakan lit-html

Pertama-tama, tag <template> berguna dan berperforma baik, tetapi tidak dikemas dengan logika komponen sehingga sulit untuk mendistribusikan template dengan logika lainnya. Selain itu, cara elemen template digunakan secara inheren mengarah pada kode imperatif, yang dalam banyak kasus, menghasilkan kode yang kurang mudah dibaca dibandingkan dengan pola pengodean deklaratif.

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

Menggunakan Lit HTML

Selanjutnya, migrasikan rating-element Komponen Web native 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 memberikan rendering cepat serta menyediakan beberapa fitur sanitasi untuk keamanan. Mulai dengan memigrasikan <template> di index.html ke dalam template Lit dengan menambahkan metode render() ke webcomponent:

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 melakukan binding data sederhana di dalam elemen span.rating dengan menggunakan sintaksis interpolasi literal template ${...}. Artinya, pada akhirnya Anda tidak perlu lagi memperbarui node tersebut secara imperatif. Selain itu, Anda memanggil metode render lit yang secara sinkron merender template ke dalam shadow root.

Bermigrasi ke Sintaksis Deklaratif

Setelah menghapus elemen <template>, refaktorkan kode untuk memanggil metode render yang baru ditentukan. 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 sintaks binding @EVENT_NAME yang dalam hal ini Anda memperbarui properti vote setiap kali tombol ini diklik.

Selanjutnya, bersihkan kode inisialisasi pemroses peristiwa di constructor dan connectedCallback serta 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 click listener dari ketiga callback dan bahkan menghapus disconnectedCallback sepenuhnya. Anda juga dapat menghapus semua kode inisialisasi DOM dari connectedCallback sehingga terlihat jauh lebih elegan. Hal ini juga berarti Anda dapat menghapus metode pemroses _onUpClick dan _onDownClick.

Terakhir, perbarui setter 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 pembaruan DOM dari setter rating dan menambahkan panggilan ke render dari setter vote. Sekarang template jauh lebih mudah dibaca karena Anda dapat melihat tempat penerapan binding dan pemroses peristiwa.

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

Penggeser rating jempol ke atas dan ke bawah dengan nilai 6 dan jempol ke atas berwarna hijau

8. LitElement

Mengapa LitElement

Beberapa masalah masih ada pada kode. Pertama, jika Anda mengubah properti atau atribut vote, properti rating dapat berubah sehingga render dipanggil dua kali. Meskipun panggilan render berulang pada dasarnya tidak beroperasi dan efisien, VM JavaScript masih menghabiskan waktu untuk memanggil fungsi tersebut dua kali secara sinkron. Kedua, penambahan properti dan atribut baru sangat 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 seluruh 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 menyimpan impor html dan terakhir css yang memungkinkan kita menentukan literal template yang diberi tag CSS untuk matematika CSS, pembuatan template, dan fitur lainnya di balik layar.

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 mengambil gaya ini dan menggunakan fitur browser seperti Stylesheet yang Dapat Dibangun untuk memberikan waktu rendering yang lebih cepat serta meneruskannya melalui polyfill Web Components di browser lama jika diperlukan.

Lifecycle

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

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

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 pada properti vote akan otomatis memperbarui atribut vote elemen host yang Anda picu secara manual di setter vote.

Setelah memiliki blok properti statis, Anda dapat menghapus semua logika pembaruan rendering atribut dan properti. Artinya, Anda dapat menghapus metode berikut:

  • connectedCallback
  • observedAttributes
  • attributeChangedCallback
  • rating (penyetel dan pengambil)
  • vote (penyetel dan pengambil, tetapi tetap menggunakan 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 setter vote ke metode siklus proses willUpdate. Metode willUpdate dipanggil sebelum render setiap kali ada 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 shadow root, dan Anda tidak perlu lagi memanggil fungsi render yang sebelumnya diimpor dari paket 'lit'.

Elemen Anda akan dirender di pratinjau sekarang; klik elemen tersebut.

9. Selamat

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

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

Sekarang Anda mengetahui apa itu Komponen Web, cara membuatnya, dan cara Lit mempermudah pembuatannya.

Code Checkpoint

Ingin memeriksa kode akhir Anda dengan kode kami? Bandingkan di sini.

Apa selanjutnya?

Lihat beberapa codelab lainnya.

Bacaan lebih lanjut

Komunitas