Komponen Web: Panduan Pemula



Pelajari tentang manfaat menggunakan komponen web, cara kerjanya, dan cara memulai.



Dengan komponen web (selanjutnya disebut sebagai komponen), pengembang dapat membuat elemen HTML mereka sendiri. Dalam panduan ini, Anda akan mempelajari semua yang perlu diketahui tentang komponen. Kita akan mulai dengan apa saja komponennya, apa manfaatnya, dan terbuat dari apa.



Setelah itu, kita akan mulai membuat komponen, pertama dengan template HTML dan antarmuka shadow DOM, kemudian menyelami topik sedikit dan melihat cara membuat elemen build-in yang disesuaikan.



Apa itu komponen?



Pengembang menyukai komponen (di sini yang kami maksud adalah penerapan pola desain "Modul"). Ini adalah cara terbaik untuk menentukan blok kode yang dapat digunakan kapan saja, di mana saja. Selama bertahun-tahun, beberapa upaya yang kurang lebih berhasil telah dilakukan untuk mempraktikkan ide ini.



XML Binding Language Mozilla dan spesifikasi Komponen HTML Microsoft untuk Internet Explorer 5 sudah ada sekitar 20 tahun yang lalu. Sayangnya, kedua implementasi tersebut sangat kompleks dan gagal menarik minat produsen browser lain, dan karenanya segera dilupakan. Meskipun demikian, merekalah yang meletakkan dasar dari apa yang kita miliki di bidang ini saat ini.



Kerangka kerja JavaScript seperti React, Vue, dan Angular mengambil pendekatan serupa. Salah satu alasan utama kesuksesan mereka adalah kemampuan untuk merangkum logika umum aplikasi ke dalam beberapa templat yang dengan mudah berpindah dari satu bentuk ke bentuk lainnya.



Meskipun kerangka kerja ini meningkatkan pengalaman pengembangan, semuanya ada harganya. Fitur bahasa seperti JSX perlu dikompilasi, dan sebagian besar framework menggunakan mesin JavaScript untuk mengelola abstraksinya. Apakah ada pendekatan lain untuk memecahkan masalah membagi kode menjadi beberapa komponen? Jawabannya adalah komponen web.



4 pilar komponen



Komponen terdiri dari tiga API - elemen khusus, template HTML dan shadow DOM, serta modul JavaScript yang mendasarinya (modul ES6). Dengan menggunakan alat yang disediakan oleh antarmuka ini, Anda dapat membuat elemen HTML khusus yang berperilaku seperti rekan aslinya.



Komponen digunakan dengan cara yang sama seperti elemen HTML biasa. Mereka dapat disesuaikan menggunakan atribut, diambil menggunakan JavaScript, ditata menggunakan CSS. Hal utama adalah memberi tahu browser bahwa mereka ada.



Ini memungkinkan komponen untuk berinteraksi dengan framework dan library lain. Dengan menggunakan mekanisme komunikasi yang sama sebagai elemen biasa, mereka dapat digunakan oleh kerangka kerja yang ada serta alat yang akan muncul di masa mendatang.



Perlu juga dicatat bahwa komponen tersebut sesuai dengan standar web. Web didasarkan pada gagasan kompatibilitas ke belakang. Artinya, komponen yang dibuat hari ini akan bekerja dengan baik untuk waktu yang lama.



Mari kita lihat spesifikasi masing-masing secara terpisah.







1. Elemen khusus



Fitur utama:



  • Mendefinisikan Perilaku Elemen
  • Bereaksi terhadap Perubahan Atribut
  • Memperluas elemen yang ada


Seringkali ketika orang berbicara tentang komponen, yang mereka maksud adalah antarmuka elemen khusus.



API ini memungkinkan Anda untuk memperluas elemen dengan mendefinisikan perilakunya saat ditambahkan, diperbarui, dan dihapus.



class ExampleElement extends HTMLElement {
  static get observedAttributes() {
      return [...]
  }
  attributeChangedCallback(name, oldValue, newValue) {}
  connectedCallback() {}
}
customElements.define('example-element', ExampleElement)


Setiap elemen kustom memiliki struktur yang serupa. Ini memperluas fungsionalitas kelas HTMLElements yang ada.



Di dalam elemen khusus, ada beberapa metode yang disebut reaksi yang bertanggung jawab untuk menangani perubahan tertentu pada elemen. Misalnya connectedCallback dipanggil saat item ditambahkan ke halaman. Ini mirip dengan tahapan siklus hidup yang digunakan dalam kerangka kerja (componentDidMount di React, dipasang di Vue).



Mengubah atribut elemen memerlukan perubahan perilakunya. Ketika pembaruan terjadi, atributChangedCallback dipanggil berisi informasi tentang perubahan tersebut. Ini hanya terjadi untuk atribut yang ditentukan dalam larik yang dikembalikan oleh observAttributes.



Elemen harus ditentukan sebelum browser dapat menggunakannya. Metode "define" membutuhkan dua argumen - nama tag dan kelasnya. Semua tag harus berisi karakter "-" untuk menghindari konflik dengan elemen asli yang ada dan yang akan datang.



<example-element>Content</example-element>


Elemen tersebut dapat digunakan seperti tag HTML biasa. Ketika elemen seperti itu ditemukan, browser mengaitkan perilakunya dengan kelas yang ditentukan. Proses ini disebut "peningkatan".



Ada dua jenis item - "otonom" dan "build-in yang disesuaikan". Sejauh ini, kami telah melihat item yang berdiri sendiri. Ini adalah elemen yang tidak terkait dengan elemen HTML yang ada. Seperti tag div dan span, yang tidak memiliki arti semantik tertentu.



Elemen sebaris kustom - seperti namanya - memperluas fungsionalitas elemen HTML yang ada. Mereka mewarisi perilaku semantik dari elemen-elemen ini dan dapat mengubahnya. Misalnya, jika elemen "masukan" telah disesuaikan, itu akan tetap menjadi bidang masukan dan bagian dari formulir saat dikirim.



class CustomInput extends HTMLInputElement {}
customElements.define('custom-input', CustomInput, { extends: 'input' })


Kelas elemen sebaris kustom memperluas kelas elemen kustom. Saat menentukan elemen sebaris, elemen yang dapat diperluas diteruskan sebagai argumen ketiga.



<input is="custom-input" />


Penggunaan tag juga sedikit berbeda. Sebagai ganti tag baru, tag yang sudah ada digunakan, menentukan atribut ekstensi "adalah" khusus. Saat browser menemukan atribut ini, ia tahu itu berurusan dengan elemen khusus dan memperbaruinya sesuai.



Meskipun elemen mandiri didukung oleh sebagian besar browser modern, elemen sebaris kustom hanya didukung oleh Chrome dan Firefox. Saat digunakan di browser yang tidak mendukungnya, yang terakhir akan diperlakukan seperti elemen HTML biasa, jadi pada umumnya elemen ini aman digunakan bahkan di browser tersebut.



2. Template HTML



  • Penciptaan struktur yang sudah jadi
  • Tidak ditampilkan di halaman sebelum panggilan
  • Berisi HTML, CSS dan JS


Secara historis, pembuatan template sisi klien melibatkan penggabungan string dalam JavaScript atau menggunakan pustaka seperti Handlebars untuk mengurai blok markup khusus. Baru-baru ini, spesifikasi memiliki tag 'template' yang dapat berisi apa saja yang ingin kita gunakan.



<template id="tweet">
  <div class="tweet">
    <span class="message"></span>
      Written by @
    <span class="username"></span>
  </div>
</template>


Dengan sendirinya, itu tidak mempengaruhi halaman dengan cara apa pun, mis. itu tidak diurai oleh mesin, permintaan untuk sumber daya (audio, video) tidak dikirim. JavaScript tidak dapat mengaksesnya, dan untuk browser itu adalah elemen kosong.



const template = document.getElementById('tweet')
const node = document.importNode(template.content, true)
document.body.append(node)


Pertama kita mendapatkan elemen "template". Metode importNode membuat salinan kontennya, argumen kedua (true) berarti salinan dalam. Terakhir, kami menambahkannya ke halaman seperti elemen lainnya.



Template dapat berisi apa saja yang dapat dimuat dalam HTML normal, termasuk CSS dan JavaScript. Ketika sebuah elemen ditambahkan ke halaman, gaya akan diterapkan padanya dan skrip akan diluncurkan. Ingatlah bahwa gaya dan skrip bersifat global, yang berarti mereka dapat menimpa gaya dan nilai lain yang digunakan oleh skrip.



Kerangka tidak terbatas pada ini. Mereka muncul dengan segala kemegahannya saat digunakan dengan bagian lain dari komponen, khususnya shadow DOM.



3. Shadow DOM



  • Hindari konflik gaya
  • Mencari nama (kelas, misalnya) menjadi lebih mudah
  • Mengenkapsulasi logika implementasi


Model Objek Dokumen (DOM) adalah cara browser menafsirkan struktur halaman. Dengan membaca markup, browser menentukan elemen mana yang berisi konten apa dan, berdasarkan ini, membuat keputusan tentang apa yang harus ditampilkan di halaman. Saat menggunakan document.getElemetById (), misalnya, browser mengakses DOM untuk menemukan elemen yang dibutuhkannya.



Untuk tata letak halaman, ini bagus, tapi bagaimana dengan detail yang tersembunyi di dalam elemen? Misalnya, halaman tidak peduli antarmuka apa yang terkandung dalam elemen "video". Di sinilah shadow DOM berguna.



<div id="shadow-root"></div>
<script>
  const host = document.getElementById('shadow-root')
  const shadow = host.attachShadow({ mode: 'open' })
</script>


Shadow DOM dibuat saat diterapkan ke sebuah elemen. Konten apa pun dapat ditambahkan ke shadow DOM, seperti DOM biasa ("ringan"). Shadow DOM tidak terpengaruh oleh apa yang terjadi di luar, mis. di luar itu. DOM biasa juga tidak dapat mengakses bayangan secara langsung. Ini berarti bahwa dalam shadow DOM kita dapat menggunakan nama kelas, gaya, dan skrip apa pun dan tidak mengkhawatirkan kemungkinan konflik.



Hasil terbaik diperoleh dengan menggunakan shadow DOM bersama dengan elemen khusus. Berkat shadow DOM, saat sebuah komponen digunakan kembali, gaya dan strukturnya tidak memengaruhi elemen lain di halaman dengan cara apa pun.



Modul ES dan HTML


  • Menambahkan sesuai kebutuhan
  • Tidak diperlukan pra-generasi
  • Semuanya disimpan di satu tempat


Sementara tiga spesifikasi sebelumnya telah melalui perjalanan panjang dalam pengembangannya, bagaimana mereka dikemas dan digunakan kembali tetap menjadi subyek perdebatan sengit.



Spesifikasi Impor HTML menentukan bagaimana dokumen HTML, serta CSS dan JavaScript, diekspor dan diimpor. Ini akan memungkinkan elemen khusus, bersama dengan template dan shadow DOM, ditempatkan di tempat lain dan digunakan sesuai kebutuhan.



Namun, Firefox menolak untuk mengimplementasikan spesifikasi ini di browsernya dan menawarkan cara berbeda berdasarkan modul JavaScript.



export class ExampleElement external HTMLElement {}

import { ExampleElement } from 'ExampleElement.js'


Modul memiliki namespace sendiri secara default, mis. konten mereka tidak global. Variabel, fungsi dan kelas yang diekspor dapat diimpor di mana saja dan kapan saja dan digunakan sebagai sumber daya lokal.



Ini berfungsi dengan baik untuk komponen. Elemen khusus yang berisi template dan shadow DOM dapat diekspor dari satu file dan digunakan di file lain.



import { ExampleElement } from 'ExampleElement.html'


Microsoft telah mengajukan proposal untuk memperluas spesifikasi modul JavaScript dengan ekspor / impor HTML. Ini akan memungkinkan Anda membuat komponen menggunakan HTML deklaratif dan semantik. Fitur ini akan segera hadir di Chrome dan Edge.



Membuat komponen Anda sendiri



Meskipun ada banyak hal tentang komponen yang mungkin Anda anggap rumit, membuat dan menggunakan komponen sederhana hanya membutuhkan beberapa baris kode. Mari pertimbangkan beberapa contoh.





Komponen memungkinkan Anda menampilkan komentar pengguna menggunakan template HTML dan antarmuka shadow DOM.



Mari buat komponen untuk menampilkan komentar pengguna menggunakan template HTML dan shadow DOM.



1. Membuat template


Komponen membutuhkan templat untuk disalin sebelum membuat markup. Template dapat ditempatkan di mana saja di halaman, kelas elemen kustom dapat mengaksesnya melalui ID.



Tambahkan elemen "template" ke halaman. Gaya apa pun yang ditentukan pada elemen ini hanya akan mempengaruhinya.



<template id="user-comment-template">
  <style>
      ...
  </style>
</template>


2. Menambahkan markup


Selain gaya, komponen bisa berisi tata letak (struktur). Untuk tujuan ini, elemen "div" digunakan.



Konten dinamis melewati slot. Tambahkan slot untuk avatar, nama, dan pesan pengguna dengan atribut "nama" yang sesuai:



<div class="container">
  <div class="avatar-container">
    <slot name="avatar"></slot>
  </div>
  <div class="comment">
    <slot name="username"></slot>
    <slot name="comment"></slot>
  </div>
</div>


Konten Slot Default




Konten default akan ditampilkan jika tidak ada informasi yang dikirimkan ke slot.



Data yang diteruskan ke slot menimpa data di template. Jika tidak ada informasi yang diteruskan ke slot, konten default akan ditampilkan.



Dalam hal ini, jika nama pengguna tidak ditransfer, pesan "Tanpa nama" akan ditampilkan di tempatnya:



<slot name="username">
  <span class="unknown">No name</span>
</slot>


3. Membuat kelas


Membuat elemen khusus dimulai dengan memperluas kelas "HTMLElement". Bagian dari proses penyiapan adalah membuat akar bayangan untuk merender konten elemen. Kami membukanya untuk akses di langkah berikutnya.



Terakhir, kami memberi tahu browser tentang kelas UserComment baru.



class UserComment extends HTMLElement {
  constructor() {
      super()
      this.attachShadow({ mode: 'open' })
  }
}
customElements.define('user-comment', UserComment)


4. Menerapkan konten bayangan


Saat browser menemukan elemen "komentar pengguna", browser akan melihat node akar bayangan untuk mengambil kontennya. Argumen kedua memberi tahu browser untuk menyalin semua konten, bukan hanya lapisan pertama (elemen tingkat atas).



Kami menambahkan markup ke simpul akar bayangan, yang segera memperbarui tampilan komponen.



connectedCallback() {
  const template = document.getElementById('user-comment-template')
  const node = document.importNode(template.content, true)
  this.shadowRoot.append(node)
}


5. Menggunakan komponen


Komponen tersebut sekarang siap digunakan. Tambahkan tag "komentar pengguna" dan teruskan informasi yang diperlukan ke sana.



Karena semua slot memiliki nama, apa pun yang diteruskan di luarnya akan diabaikan. Semua yang ada di dalam slot disalin persis seperti yang diteruskan, termasuk gaya.



<user-comment>
  <img alt="" slot="avatar" src="avatar.png" />
  <span slot="username">Matt Crouch</span>
  <div slot="comment">This is an example of a comment</div>
</user-comment>


Kode contoh tambahan:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components Example</title>
    <style>
      body {
        display: grid;
        place-items: center;
      }
      img {
        width: 80px;
        border-radius: 4px;
      }
    </style>
  </head>
  <body>
    <template id="user-comment-template">
      <div class="container">
        <div class="avatar-container">
          <slot name="avatar">
            <slot class="unknown"></slot>
          </slot>
        </div>
        <div class="comment">
          <slot name="username">No name</slot>
          <slot name="comment"></slot>
        </div>
      </div>
      <style>
        .container {
          width: 320px;
          clear: both;
          margin-bottom: 1rem;
        }
        .avatar-container {
          float: left;
          margin-right: 1rem;
        }
        .comment {
          height: 80px;
          display: flex;
          flex-direction: column;
          justify-content: center;
        }
        .unknown {
          display: block;
          width: 80px;
          height: 80px;
          border-radius: 4px;
          background: #ccc;
        }
      </style>
    </template>

    <user-comment>
      <img alt="" slot="avatar" src="avatar1.jpg" />
      <span slot="username">Matt Crouch</span>
      <div slot="comment">Fisrt comment</div>
    </user-comment>
    <user-comment>
      <img alt="" slot="avatar" src="avatar2.jpg" />
      <!-- no username -->
      <div slot="comment">Second comment</div>
    </user-comment>
    <user-comment>
      <!-- no avatar -->
      <span slot="username">John Smith</span>
      <div slot="comment">Second comment</div>
    </user-comment>

    <script>
      class UserComment extends HTMLElement {
        constructor() {
          super();
          this.attachShadow({ mode: "open" });
        }
        connectedCallback() {
          const template = document.getElementById("user-comment-template");
          const node = document.importNode(template.content, true);
          this.shadowRoot.append(node);
        }
      }
      customElements.define("user-comment", UserComment);
    </script>
  </body>
</html>










Membuat elemen inline kustom



Seperti disebutkan sebelumnya, elemen kustom dapat memperluas yang sudah ada. Ini menghemat waktu dengan mempertahankan perilaku default dari elemen yang disediakan oleh brooder. Di bagian ini, kita akan melihat bagaimana Anda dapat memperpanjang elemen "waktu".



1. Membuat kelas


Elemen bawaan, seperti elemen yang berdiri sendiri, muncul saat kelas diperluas, tetapi bukannya kelas umum "HTMLElement", mereka memperluas kelas tertentu.



Dalam kasus kami, kelas ini adalah HTMLTimeElement - kelas yang digunakan oleh elemen "waktu". Ini mencakup perilaku yang terkait dengan atribut "datetime", termasuk format data.



class RelativeTime extends HTMLTimeElement {}


2. Definisi elemen


Elemen tersebut didaftarkan oleh browser menggunakan metode "definisikan". Namun, tidak seperti elemen yang berdiri sendiri, saat mendaftarkan elemen sebaris, metode "mendefinisikan" harus memberikan argumen ketiga - objek dengan pengaturan.



Objek kami akan berisi satu kunci dengan nilai elemen khusus. Itu mengambil nama tag. Jika tidak ada kunci seperti itu, pengecualian akan dilemparkan.



customElements.define('relative-time', RelativeTime, { extends: 'time' })


3. Mengatur waktu


Karena kita dapat memiliki beberapa komponen dalam satu halaman, komponen tersebut harus menyediakan metode untuk menyetel nilai elemen. Di dalam metode ini, komponen meneruskan nilai waktu ke pustaka "timeago" dan menyetel nilai yang dikembalikan dari pustaka itu sebagai nilai item (maaf untuk tautologi).



Terakhir, kami menetapkan atribut judul sehingga pengguna dapat melihat nilai yang ditetapkan saat hover.



setTime() {
  this.innerHTML = timeago().format(this.getAttribute('datetime'))
  this.setAttribute('title', this.getAttribute('datetime'))
}


4. Pembaruan koneksi


Komponen dapat menggunakan metode ini segera setelah ditampilkan di halaman. Karena komponen sebaris tidak memiliki shadow DOM, mereka tidak memerlukan konstruktor.



connectedCAllback() {
  this.setTime()
}


5. Melacak perubahan atribut


Jika Anda memperbarui waktu secara terprogram, komponen tidak akan merespons. Dia tidak tahu bahwa dia harus memperhatikan perubahan pada atribut "datetime".



Setelah atribut yang diamati ditentukan, atributChangedCallback akan dipanggil setiap kali berubah.



static get observedAttributes() {
  return ['datetime']
}
attributeChangedCallback() {
  this.setTime()
}


6. Menambahkan ke halaman


Karena elemen kami merupakan perpanjangan dari elemen asli, implementasinya sedikit berbeda. Untuk menggunakannya, tambahkan tag "waktu" ke halaman dengan atribut khusus "adalah", yang nilainya adalah nama elemen bawaan yang ditentukan selama pendaftaran. Browser yang tidak mendukung komponen akan merender konten fallback.



<time is="relative-time" datetime="2020-09-20T12:00:00+0000">
  20  2020 . 12:00
</time>


Kode contoh tambahan:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components Another Example</title>
    <!-- timeago.js -->
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/timeago.js/4.0.2/timeago.min.js"
      integrity="sha512-SVDh1zH5N9ChofSlNAK43lcNS7lWze6DTVx1JCXH1Tmno+0/1jMpdbR8YDgDUfcUrPp1xyE53G42GFrcM0CMVg=="
      crossorigin="anonymous"
    ></script>
    <style>
      body {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
      input,
      button {
        margin-bottom: 0.5rem;
      }
      time {
        font-size: 2rem;
      }
    </style>
  </head>
  <body>
    <input type="text" placeholder="2020-10-20" value="2020-08-19" />
    <button>Set Time</button>

    <time is="relative-time" datetime="2020-09-19">
      19  2020 .
    </time>

    <script>
      class RelativeTime extends HTMLTimeElement {
        setTime() {
          this.innerHTML = timeago.format(this.getAttribute("datetime"));
          this.setAttribute("title", this.getAttribute("datetime"));
        }
        connectedCallback() {
          this.setTime();
        }
        static get observedAttributes() {
          return ["datetime"];
        }
        attributeChangedCallback() {
          this.setTime();
        }
      }
      customElements.define("relative-time", RelativeTime, { extends: "time" });

      const button = document.querySelector("button");
      const input = document.querySelector("input");
      const time = document.querySelector("time");

      button.onclick = () => {
        const { value } = input;
        time.setAttribute("datetime", value);
      };
    </script>
  </body>
</html>










Mudah-mudahan saya telah membantu Anda mendapatkan pemahaman dasar tentang apa itu komponen web, untuk apa mereka, dan bagaimana mereka digunakan.



All Articles