Praktik Terbaik Elemen Kustom

Elemen kustom memungkinkan Anda membuat tag HTML sendiri. Checklist ini mencakup praktik terbaik untuk membantu Anda membuat elemen berkualitas tinggi.

Elemen khusus memungkinkan Anda memperluas HTML dan menentukan tag Anda sendiri. Fungsi ini adalah fitur yang sangat canggih, tetapi juga level rendah, yang berarti tidak selalu jelas cara terbaik untuk menerapkan elemen Anda sendiri.

Untuk membantu Anda menciptakan pengalaman terbaik, kami telah menyusun checklist ini. Elemen ini menguraikan semua hal yang kami anggap diperlukan sebagai elemen kustom yang berperilaku baik.

Checklist

DOM Bayangan

Buat root bayangan untuk mengenkapsulasi gaya.

Apa tujuannya? Gaya enkapsulasi dalam shadow root elemen memastikan bahwa elemen tersebut akan berfungsi di mana pun digunakan. Hal ini sangat penting jika developer ingin menempatkan elemen Anda di dalam shadow root elemen lain. Hal ini berlaku bahkan untuk elemen sederhana seperti kotak centang atau tombol pilihan. Mungkin saja satu-satunya konten di dalam shadow root Anda adalah gaya itu sendiri.
Contoh Elemen <howto-checkbox>.

Buat root bayangan Anda di konstruktor.

Apa tujuannya? Konstruktor adalah ketika Anda memiliki pengetahuan eksklusif tentang elemen. Inilah waktu yang tepat untuk menyiapkan detail implementasi yang tidak ingin dirusak oleh elemen lain. Melakukan pekerjaan ini di callback selanjutnya, seperti connectedCallback, berarti Anda harus terlindung dari situasi saat elemen terlepas lalu dipasang kembali ke dokumen.
Contoh Elemen <howto-checkbox>.

Tempatkan turunan apa pun yang dibuat elemen ke dalam root bayangannya.

Apa tujuannya? Turunan yang dibuat oleh elemen Anda adalah bagian dari implementasinya dan harus bersifat pribadi. Tanpa perlindungan root bayangan, JavaScript luar dapat mengganggu turunan ini secara tidak sengaja.
Contoh Elemen <howto-tabs>.

Gunakan <slot> untuk memproyeksikan turunan light DOM ke dalam shadow DOM Anda

Apa tujuannya? Izinkan pengguna komponen Anda menentukan konten dalam komponen karena turunan HTML membuat komponen Anda lebih dapat dikomposisi. Bila browser tidak mendukung elemen khusus, konten yang disarangkan akan tetap tersedia, terlihat, dan dapat diakses.
Contoh Elemen <howto-tabs>.

Tetapkan gaya tampilan :host (misalnya block, inline-block, flex) kecuali jika Anda lebih memilih default inline.

Apa tujuannya? Elemen kustom bersifat display: inline secara default, jadi menyetel width atau height tidak akan berpengaruh. Hal ini sering kali mengejutkan developer dan dapat menyebabkan masalah terkait tata letak halaman. Kecuali Anda lebih memilih tampilan inline, Anda harus selalu menetapkan nilai display default.
Contoh Elemen <howto-checkbox>.

Tambahkan gaya tampilan :host yang mematuhi atribut tersembunyi.

Apa tujuannya? Elemen kustom dengan gaya display default, misalnya :host { display: block }, akan mengganti kekhususan yang lebih rendah atribut hidden bawaan. Hal ini mungkin mengejutkan jika Anda mengharapkan penetapan atribut hidden pada elemen untuk merendernya display: none. Selain gaya display default, tambahkan dukungan untuk hidden dengan :host([hidden]) { display: none }.
Contoh Elemen <howto-checkbox>.

Atribut dan properti

Jangan ganti atribut global yang ditetapkan penulis.

Apa tujuannya? Atribut global adalah atribut yang ada di semua elemen HTML. Beberapa contoh mencakup tabindex dan role. Elemen kustom mungkin ingin menetapkan tabindex awal ke 0 agar dapat difokuskan keyboard. Namun, Anda harus selalu memeriksa terlebih dahulu untuk mengetahui apakah developer yang menggunakan elemen Anda telah menetapkannya ke nilai lain. Misalnya, jika properti telah menetapkan tabindex ke -1, itu adalah sinyal bahwa pengguna tidak menginginkan elemen tersebut menjadi interaktif.
Contoh Elemen <howto-checkbox>. Hal ini dijelaskan lebih lanjut di Jangan ganti penulis halaman.

Selalu terima data primitif (string, angka, boolean) sebagai atribut atau properti.

Apa tujuannya? Elemen kustom, seperti elemen bawaannya, harus dapat dikonfigurasi. Konfigurasi dapat diteruskan secara deklaratif, melalui atribut, atau secara imperatif melalui properti JavaScript. Idealnya, setiap atribut juga harus ditautkan ke properti yang sesuai.
Contoh Elemen <howto-checkbox>.

Usahakan atribut dan properti data dasar tetap sinkron, yang tercermin dari properti ke atribut, dan sebaliknya.

Apa tujuannya? Anda tidak pernah tahu bagaimana pengguna akan berinteraksi dengan elemen. Mereka mungkin menetapkan properti di JavaScript, lalu berharap membaca nilai tersebut menggunakan API seperti getAttribute(). Jika setiap atribut memiliki properti yang sesuai, dan keduanya mencerminkan, ini akan memudahkan pengguna untuk menggunakan elemen Anda. Dengan kata lain, memanggil setAttribute('foo', value) juga harus menetapkan properti foo yang sesuai dan sebaliknya. Tentu saja ada pengecualian untuk aturan ini. Anda tidak boleh mencerminkan properti frekuensi tinggi, misalnya, currentTime di pemutar video. Gunakan penilaian terbaik Anda. Jika sepertinya pengguna akan berinteraksi dengan properti atau atribut, dan tidak merepotkan untuk mencerminkannya, lakukan saja.
Contoh Elemen <howto-checkbox>. Hal ini dijelaskan lebih lanjut dalam Menghindari masalah reentrancy.

Usahakan hanya menerima data lengkap (objek, array) sebagai properti.

Apa tujuannya? Secara umum, tidak ada contoh elemen HTML bawaan yang menerima data lengkap (objek dan array JavaScript biasa) melalui atributnya. Sebagai gantinya, data lengkap diterima melalui panggilan metode atau properti. Ada beberapa kerugian nyata dari menerima data lengkap sebagai atribut: serialisasi objek besar ke string bisa menjadi mahal, dan referensi objek apa pun akan hilang dalam proses string ini. Misalnya, jika Anda membuat string untuk sebuah objek yang memiliki referensi ke objek lain, atau mungkin sebuah node DOM, referensi tersebut akan hilang.

Jangan mencerminkan properti data lengkap ke atribut.

Apa tujuannya? Merefleksikan properti data lengkap ke atribut sangat mahal, sehingga memerlukan serialisasi dan deserialisasi objek JavaScript yang sama. Kecuali jika Anda memiliki kasus penggunaan yang hanya dapat diselesaikan dengan fitur ini, sebaiknya hindari hal tersebut.

Sebaiknya periksa properti yang mungkin telah ditetapkan sebelum elemen diupgrade.

Apa tujuannya? Developer yang menggunakan elemen Anda mungkin mencoba menetapkan properti pada elemen sebelum definisinya dimuat. Hal ini terutama berlaku jika developer menggunakan framework yang menangani pemuatan komponen, memberi stempel ke halaman, dan mengikat propertinya ke model.
Contoh Elemen <howto-checkbox>. Dijelaskan lebih lanjut di Membuat properti menjadi lambat.

Jangan menerapkan kelas secara mandiri.

Apa tujuannya? Elemen yang perlu menyatakan statusnya harus melakukannya dengan menggunakan atribut. Atribut class umumnya dianggap dimiliki oleh developer menggunakan elemen Anda, dan menulis ke atribut tersebut sendiri dapat secara tidak sengaja menginjak class developer.

Peristiwa

Kirim peristiwa sebagai respons terhadap aktivitas komponen internal.

Apa tujuannya? Komponen Anda mungkin memiliki properti yang berubah sebagai respons terhadap aktivitas yang hanya diketahui oleh komponen Anda, misalnya, jika timer atau animasi selesai, atau resource selesai dimuat. Sebaiknya kirim peristiwa sebagai respons terhadap perubahan ini untuk memberi tahu host bahwa status komponen berbeda.

Jangan mengirim peristiwa sebagai respons terhadap setelan host (aliran data ke bawah).

Apa tujuannya? Mengirim peristiwa sebagai respons terhadap host yang menetapkan properti berlebihan (host mengetahui status saat ini karena baru saja menetapkannya). Mengirim peristiwa sebagai respons terhadap setelan host yang memiliki properti dapat menyebabkan loop tak terbatas dengan sistem data binding.
Contoh Elemen <howto-checkbox>.

Penjelasan

Jangan ganti penulis halaman

Ada kemungkinan bahwa developer yang menggunakan elemen Anda ingin mengganti sebagian status awalnya. Misalnya, mengubah role ARIA-nya atau kemampuan fokus dengan tabindex. Periksa apakah atribut ini dan atribut global lainnya telah ditetapkan sebelum menerapkan nilai Anda sendiri.

connectedCallback() {
  if (!this.hasAttribute('role'))
    this.setAttribute('role', 'checkbox');
  if (!this.hasAttribute('tabindex'))
    this.setAttribute('tabindex', 0);

Membuat properti menjadi lambat

Developer mungkin mencoba menetapkan properti pada elemen Anda sebelum definisinya dimuat. Hal ini terutama berlaku jika developer menggunakan framework yang menangani komponen pemuatan, memasukkannya ke halaman, dan mengikat propertinya ke model.

Pada contoh berikut, Angular secara deklaratif mengikat properti isChecked modelnya ke properti checked kotak centang. Jika definisi untuk kotak centang petunjuk dimuat dengan lambat, kemungkinan Angular akan mencoba menyetel properti yang dicentang sebelum elemen diupgrade.

<howto-checkbox [checked]="defaults.isChecked"></howto-checkbox>

Elemen kustom harus menangani skenario ini dengan memeriksa apakah ada properti yang telah ditetapkan pada instance-nya. <howto-checkbox> mendemonstrasikan pola ini menggunakan metode yang disebut _upgradeProperty().

connectedCallback() {
  ...
  this._upgradeProperty('checked');
}

_upgradeProperty(prop) {
  if (this.hasOwnProperty(prop)) {
    let value = this[prop];
    delete this[prop];
    this[prop] = value;
  }
}

_upgradeProperty() menangkap nilai dari instance yang belum diupgrade dan menghapus properti sehingga tidak bayangan penyetel properti elemen kustom itu sendiri. Dengan demikian, ketika definisi elemen akhirnya dimuat, definisi tersebut dapat segera mencerminkan status yang benar.

Menghindari masalah reentrancy

Anda mungkin ingin menggunakan attributeChangedCallback() untuk mencerminkan status ke properti dasar, misalnya:

// When the [checked] attribute changes, set the checked property to match.
attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'checked')
    this.checked = newValue;
}

Namun, cara ini dapat menciptakan loop tak terbatas jika penyetel properti juga mencerminkan ke atribut.

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    // OOPS! This will cause an infinite loop because it triggers the
    // attributeChangedCallback() which then sets this property again.
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

Alternatifnya adalah mengizinkan penyetel properti mencerminkan ke atribut, dan membuat pengambil menentukan nilainya berdasarkan atribut.

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

get checked() {
  return this.hasAttribute('checked');
}

Dalam contoh ini, menambahkan atau menghapus atribut juga akan menetapkan properti.

Terakhir, attributeChangedCallback() dapat digunakan untuk menangani efek samping seperti menerapkan status ARIA.

attributeChangedCallback(name, oldValue, newValue) {
  const hasValue = newValue !== null;
  switch (name) {
    case 'checked':
      // Note the attributeChangedCallback is only handling the *side effects*
      // of setting the attribute.
      this.setAttribute('aria-checked', hasValue);
      break;
    ...
  }
}