Menggunakan Elemen Kustom di Svelte

Svelte sepenuhnya mendukung elemen kustom (misalnya <my-component> ) tanpa konfigurasi kustom atau komponen pembungkus dan memiliki skor sempurna pada Elemen Kustom Everywhere . Namun, masih ada beberapa kebiasaan yang perlu Anda waspadai, terutama seputar cara Svelte mengatur data pada elemen kustom. Di Alaska Airlines, kami mengalami banyak masalah ini secara langsung saat kami mengintegrasikan elemen khusus dari sistem desain kami ke dalam aplikasi Svelte.

Meskipun Svelte mendukung kompilasi ke elemen khusus , itu tidak termasuk dalam cakupan posting ini. Sebagai gantinya, saya akan fokus menggunakan elemen kustom yang dibuat dengan pustaka elemen kustom Lit dalam aplikasi Svelte. Konsep ini harus ditransfer ke elemen kustom yang dibuat dengan atau tanpa pustaka pendukung.

Properti atau atribut?

Untuk sepenuhnya memahami cara menggunakan elemen kustom di Svelte, Anda perlu memahami cara Svelte meneruskan data ke elemen kustom.

Svelte menggunakan heuristik sederhana untuk menentukan apakah akan meneruskan data ke elemen khusus sebagai properti atau atribut. Jika properti yang sesuai ada pada elemen kustom saat runtime, Svelte akan meneruskan data sebagai properti. Jika tidak, itu akan meneruskannya sebagai atribut. Hal ini tampaknya sederhana, tetapi memiliki implikasi yang menarik.

Misalnya, katakanlah Anda memiliki elemen kustom coffee-mug yang menggunakan properti size Anda dapat menggunakannya dalam komponen Svelte seperti:

 <coffee-mug class="mug" size="large"></coffee-mug>

Anda dapat membuka REPL Svelte ini untuk mengikuti. Anda akan melihat elemen kustom membuat teks “Ukuran cangkir kopi ini adalah: besar ️.”

Saat menulis HTML di dalam komponen, sepertinya Anda mengatur class dan size sebagai atribut. Namun, ini tidak terjadi. Klik kanan pada teks “This mug's size is” pada output REPL dan klik “Inspect.” Ini akan membuka inspektur DevTools. Saat Anda memeriksa HTML yang dirender, Anda akan melihat bahwa hanya class yang ditetapkan sebagai atribut — seolah-olah size menghilang begitu saja! Namun, size diatur, karena "besar" masih muncul di teks yang dirender elemen.

Ini karena size adalah properti pada elemen, tetapi class tidak. Karena Svelte mendeteksi size , ia memilih untuk menyetel properti itu alih-alih atribut. Tidak ada class , jadi Svelte menetapkannya sebagai atribut. Itu bukan masalah atau sesuatu yang mengubah cara kita mengharapkan komponen berperilaku, tetapi bisa sangat membingungkan jika Anda tidak menyadarinya, karena ada keterputusan antara HTML yang Anda pikir sedang Anda tulis dan apa yang sebenarnya dihasilkan Svelte.

Svelte tidak unik dalam perilaku ini — Preact menggunakan metode serupa untuk menentukan apakah akan menyetel atribut atau properti pada elemen kustom. Karena itu, kasus penggunaan yang saya bahas juga akan terjadi di Preact, meskipun penyelesaiannya akan berbeda. Anda tidak akan mengalami masalah ini dengan Angular atau Vue karena mereka memiliki sintaks khusus yang memungkinkan Anda memilih untuk mengatur atribut atau properti.

Heuristik Svelte memudahkan untuk melewatkan data kompleks seperti array dan objek yang perlu ditetapkan sebagai properti. Konsumen elemen kustom Anda tidak perlu memikirkan apakah mereka perlu menyetel atribut atau properti — itu hanya bekerja secara ajaib. Namun, seperti keajaiban dalam pengembangan web, Anda akhirnya mengalami beberapa kasus yang mengharuskan Anda untuk menggali lebih dalam dan memahami apa yang terjadi di balik layar.

Mari kita lihat beberapa kasus penggunaan di mana elemen kustom berperilaku aneh. Anda dapat menemukan contoh terakhir dalam Svelte REPL ini .

Atribut yang digunakan sebagai pengait gaya

Katakanlah Anda memiliki custom-text yang menampilkan beberapa teks. Jika ada flag , itu menambahkan emoji bendera dan kata "Dibenderai:" ke teks. Elemen dikodekan sebagai berikut:

 import { html, css, LitElement } from 'lit'; export class CustomText extends LitElement { static get styles() { return css` :host([flag]) p::before { content: '🚩'; } `; } static get properties() { return { flag: { type: Boolean } }; } constructor() { super(); this.flag = false; } render() { return html`<p> ${this.flag ? html`<strong>Flagged:</strong>` : ''} <slot></slot> </p>`; } } customElements.define('custom-text', CustomText);

Anda dapat melihat elemen beraksi di CodePen ini .

Namun, jika Anda mencoba menggunakan elemen kustom dengan cara yang sama di Svelte, itu tidak sepenuhnya berfungsi. Teks "Berbendera:" ditampilkan, tetapi emoji tidak. Apa yang memberi?

 <script> import './custom-elements/custom-text'; </script> <!-- This shows the "Flagged:" text, but not 🚩 --> <custom-text flag>Just some custom text.</custom-text>

Kuncinya di sini adalah pemilih :host([flag]) :host memilih akar bayangan elemen (yaitu elemen <custom-text> ), jadi pemilih ini hanya berlaku jika atribut flag ada pada elemen. Karena Svelte memilih untuk menyetel properti, pemilih ini tidak berlaku. Teks "Berbendera:" ditambahkan berdasarkan properti, itulah sebabnya itu masih ditampilkan.

Jadi apa pilihan kita di sini? Nah, elemen kustom seharusnya tidak berasumsi bahwa flag akan selalu ditetapkan sebagai atribut. Ini adalah praktik terbaik elemen kustom untuk menjaga atribut dan properti data primitif tetap sinkron karena Anda tidak tahu bagaimana konsumen elemen akan berinteraksi dengannya. Solusi ideal adalah bagi pembuat elemen untuk memastikan setiap properti primitif direfleksikan ke atribut, terutama jika atribut tersebut digunakan untuk penataan gaya. Lit memudahkan untuk mencerminkan properti Anda:

 static get properties() { return { flag: { type: Boolean, reflect: true } }; }

Dengan perubahan itu, flag direfleksikan kembali ke atribut, dan semuanya ditampilkan seperti yang diharapkan.

Namun, mungkin ada kasus di mana Anda tidak memiliki kontrol atas definisi elemen kustom. Dalam hal ini, Anda dapat memaksa Svelte untuk menyetel atribut menggunakan tindakan Svelte.

Menggunakan tindakan Svelte untuk memaksa atribut pengaturan

Tindakan adalah fitur Svelte yang kuat yang menjalankan fungsi ketika simpul tertentu ditambahkan ke DOM. Misalnya, kita dapat menulis tindakan yang akan menyetel atribut flag pada elemen custom-text

 <script> import './custom-elements/custom-text'; function setAttributes(node) { node.setAttribute('flag', ''); } </script> <custom-text use:setAttributes> Just some custom text. </custom-text>

Tindakan juga dapat mengambil parameter. Misalnya, kita dapat membuat tindakan ini lebih umum dan menerima objek yang berisi atribut yang ingin kita atur pada sebuah simpul.

 <script> import './custom-elements/custom-text'; function setAttributes(node, attributes) { Object.entries(attributes).forEach(([k, v]) => { if (v !== undefined) { node.setAttribute(k, v); } else { node.removeAttribute(k); } }); } </script> <custom-text use:setAttributes={{ flag: true }}> Just some custom text. </custom-text>

Terakhir, jika kita ingin atribut bereaksi terhadap perubahan status, kita dapat mengembalikan objek dengan update dari tindakan. Setiap kali parameter yang kami berikan ke tindakan berubah, update akan dipanggil.

 <script> import './custom-elements/custom-text'; function setAttributes(node, attributes) { const applyAttributes = () => { Object.entries(attributes).forEach(([k, v]) => { if (v !== undefined) { node.setAttribute(k, v); } else { node.removeAttribute(k); } }); }; applyAttributes(); return { update(updatedAttributes) { attributes = updatedAttributes; applyAttributes(); } }; } let flagged = true; </script> <label><input type="checkbox" bind:checked={flagged} /> Flagged</label> <custom-text use:setAttributes={{ flag: flagged ? '' : undefined }}> Just some custom text. </custom-text>

Dengan menggunakan pendekatan ini, kita tidak perlu memperbarui elemen kustom untuk mencerminkan properti — kita dapat mengontrol pengaturan atribut dari dalam aplikasi Svelte kita.

Elemen kustom yang memuat lambat

Elemen kustom tidak selalu ditentukan saat komponen pertama kali dirender. Misalnya, Anda mungkin menunggu untuk mengimpor elemen kustom Anda sampai setelah polyfill komponen web dimuat. Selain itu, dalam konteks rendering sisi server seperti Sapper atau SvelteKit , rendering server awal akan berlangsung tanpa memuat definisi elemen kustom.

Dalam kedua kasus tersebut, jika elemen kustom tidak ditentukan, Svelte akan mengatur semuanya sebagai atribut. Ini karena properti belum ada pada elemen. Ini membingungkan jika Anda sudah terbiasa dengan Svelte yang hanya mengatur properti pada elemen khusus. Ini dapat menyebabkan masalah dengan data kompleks seperti objek dan array.

Sebagai contoh, mari kita lihat elemen kustom berikut yang menampilkan salam diikuti dengan daftar nama.

 import { html, css, LitElement } from 'lit'; export class FancyGreeting extends LitElement { static get styles() { return css` p { border: 5px dashed mediumaquamarine; padding: 4px; } `; } static get properties() { return { names: { type: Array }, greeting: { type: String } }; } constructor() { super(); this.names = []; } render() { return html`<p> ${this.greeting}, ${this.names && this.names.length > 0 ? this.names.join(', ') : 'no one'}! </p>`; } } customElements.define('fancy-greeting', FancyGreeting);

Anda dapat melihat elemen beraksi di CodePen ini.

Jika kita mengimpor elemen secara statis dalam aplikasi Svelte, semuanya berfungsi seperti yang diharapkan.

 <script> import './custom-elements/fancy-greeting'; </script> <!-- This displays "Howdy, Amy, Bill, Clara!" --> <fancy-greeting greeting="Howdy" names={['Amy', 'Bill', 'Clara']} />

Namun, jika kita mengimpor komponen secara dinamis , elemen kustom tidak akan ditentukan sampai setelah komponen pertama kali dirender. Dalam contoh ini, saya menunggu untuk mengimpor elemen hingga komponen Svelte telah dipasang menggunakan fungsi siklus hidup onMount . Saat kami menunda pengimporan elemen kustom, daftar nama tidak disetel dengan benar dan konten cadangan ditampilkan sebagai gantinya.

 <script> import { onMount } from 'svelte'; onMount(async () => { await import('./custom-elements/fancy-greeting'); }); </script> <!-- This displays "Howdy, no one!"--> <fancy-greeting greeting="Howdy" names={['Amy', 'Bill', 'Clara']} />

Karena definisi elemen kustom tidak dimuat saat Svelte menambahkan fancy-greeting ke DOM, fancy-greeting tidak memiliki names dan Svelte menyetel names — tetapi sebagai string, bukan sebagai larik berdawai. Jika Anda memeriksa elemen di DevTools browser Anda, Anda akan melihat yang berikut:

 <fancy-greeting greeting="Howdy" names="Amy,Bill,Clara"></fancy-greeting>

Elemen kustom kami mencoba mengurai atribut nama sebagai larik menggunakan JSON.parse , yang memunculkan pengecualian. Ini ditangani secara otomatis menggunakan konverter larik default Lit , tetapi hal yang sama akan berlaku untuk elemen apa pun yang mengharapkan atribut berisi larik JSON yang valid.

Menariknya, setelah Anda memperbarui data yang diteruskan ke elemen kustom, Svelte akan mulai mengatur properti lagi. Dalam contoh di bawah ini, saya memindahkan array nama ke names variabel status sehingga saya dapat memperbaruinya. Saya juga menambahkan tombol "Tambah nama" yang akan menambahkan nama "Rory" ke akhir names saat diklik.

Setelah tombol diklik, names diperbarui, yang memicu rendering ulang komponen. Karena elemen kustom sekarang ditentukan, Svelte mendeteksi names pada elemen kustom dan menetapkannya sebagai ganti atribut. Ini menyebabkan elemen kustom menampilkan daftar nama dengan benar alih-alih konten cadangan.

 <script> import { onMount } from 'svelte'; onMount(async () => { await import('./custom-elements/fancy-greeting'); }); let names = ['Amy', 'Bill', 'Clara']; function addName() { names = [...names, 'Rory']; } </script> <!-- Once the button is clicked, the element displays "Howdy, Amy, Bill, Clara, Rory!" --> <fancy-greeting greeting="Howdy" {names} /> <button on:click={addName}>Add name</button>

Seperti pada contoh sebelumnya, kita dapat memaksa Svelte untuk mengatur data seperti yang kita inginkan menggunakan suatu tindakan. Kali ini, alih-alih mengatur semuanya sebagai atribut, kami ingin mengatur semuanya sebagai properti. Kami akan melewatkan objek sebagai parameter yang berisi properti yang ingin kami atur pada node. Berikut cara tindakan kita akan diterapkan ke elemen kustom:

 <fancy-greeting greeting="Howdy" use:setProperties={{ names: ['Amy', 'Bill', 'Clara'] }} />

Di bawah ini adalah pelaksanaan tindakan. Kami mengulangi objek properti dan menggunakan setiap entri untuk mengatur properti pada simpul elemen kustom. Kami juga mengembalikan fungsi pembaruan sehingga properti diterapkan kembali jika parameter yang diteruskan ke tindakan berubah. Lihat bagian sebelumnya jika Anda menginginkan penyegaran tentang bagaimana Anda dapat bereaksi terhadap perubahan status dengan suatu tindakan.

 function setProperties(node, properties) { const applyProperties = () => { Object.entries(properties).forEach(([k, v]) => { node[k] = v; }); }; applyProperties(); return { update(updatedProperties) { properties = updatedProperties; applyProperties(); } }; }

Dengan menggunakan tindakan, nama ditampilkan dengan benar pada render pertama. Svelte menyetel properti saat pertama kali merender komponen, dan elemen kustom mengambil properti itu setelah elemen ditentukan.

Atribut Boolean

Masalah terakhir yang kami hadapi adalah bagaimana Svelte menangani atribut boolean pada elemen kustom. Perilaku ini baru-baru ini berubah dengan Svelte 3.38.0, tetapi kami akan mengeksplorasi perilaku sebelum dan sesudah 3.38 karena tidak semua orang akan menggunakan versi Svelte terbaru.

Misalkan kita memiliki <secret-box> dengan properti boolean open yang menunjukkan apakah kotak terbuka atau tidak. Implementasinya terlihat seperti ini:

 import { html, LitElement } from 'lit'; export class SecretBox extends LitElement { static get properties() { return { open: { type: Boolean } }; } render() { return html`<div>The box is ${this.open ? 'open 🔓' : 'closed 🔒'}</div>`; } } customElements.define('secret-box', SecretBox);

Anda dapat melihat elemen beraksi di CodePen ini.

Seperti yang terlihat di CodePen, Anda dapat mengatur properti terbuka ke beberapa cara yang true Menurut spesifikasi HTML , keberadaan atribut boolean mewakili nilai true , dan ketidakhadirannya mewakili false .

 <secret-box open></secret-box> <secret-box open=""></secret-box> <secret-box open="open"></secret-box>

Menariknya, hanya opsi terakhir di atas yang menunjukkan "Kotak terbuka" saat digunakan di dalam komponen Svelte. Dua yang pertama menunjukkan "Kotak ditutup" meskipun mengatur atribut open Apa yang terjadi di sini?

Seperti contoh lainnya, semuanya kembali ke Svelte memilih properti daripada atribut. Jika Anda memeriksa elemen di browser DevTools, tidak ada atribut yang disetel — Svelte telah mengatur semuanya sebagai properti. Kita dapat console.log open di dalam metode render kami (atau kueri elemen di konsol) untuk menemukan ke mana Svelte mengatur properti open

 // <secret-box open> logs '' // <secret-box open=""> logs '' // <secret-box open="open"> logs 'open' render() { console.log(this.open); return html`<div>The box is ${this.open ? 'open 🔓' : 'closed 🔒'}</div>`; }

Dalam dua kasus pertama, open sama dengan string kosong. Karena string kosong salah dalam JavaScript, pernyataan ternary kami mengevaluasi ke kasus palsu dan menunjukkan bahwa kotak ditutup. Dalam kasus terakhir, open diatur ke string "terbuka" yang benar. Pernyataan terner mengevaluasi kasus yang sebenarnya dan menunjukkan bahwa kotak terbuka.

Sebagai catatan tambahan, Anda tidak mengalami masalah ini ketika Anda malas memuat elemen. Karena definisi elemen kustom tidak dimuat saat Svelte merender elemen, Svelte menetapkan atribut alih-alih properti. Lihat bagian di atas untuk penyegaran.

Ada cara mudah untuk mengatasi masalah ini. Jika Anda ingat bahwa Anda menyetel properti, bukan atribut, Anda dapat secara eksplisit menyetel properti open true dengan sintaks berikut.

 <secret-box open={true}></secret-box>

Dengan cara ini Anda tahu bahwa Anda menyetel properti open true . Menyetel ke string yang tidak kosong juga berfungsi, tetapi cara ini adalah yang paling akurat karena Anda menyetel true daripada sesuatu yang kebetulan benar.

Sampai saat ini, ini adalah satu-satunya cara untuk mengatur properti boolean dengan benar pada elemen kustom. Namun, dengan Svelte 3.38, saya memiliki perubahan yang dirilis yang memperbarui heuristik Svelte untuk memungkinkan pengaturan properti boolean singkatan. Sekarang, jika Svelte mengetahui bahwa properti yang mendasarinya adalah boolean, ia akan memperlakukan open dan open="" sama dengan open={true} .

Ini sangat membantu karena ini adalah cara Anda melihat contoh di banyak pustaka komponen elemen kustom. Perubahan ini memudahkan untuk menyalin-menempel dari dokumen tanpa harus memecahkan masalah mengapa atribut tertentu tidak berfungsi seperti yang Anda harapkan.

Namun, ada satu persyaratan di sisi pembuat elemen kustom — properti boolean memerlukan nilai default sehingga Svelte mengetahui bahwa itu adalah tipe boolean. Ini adalah praktik yang baik jika Anda ingin properti itu menjadi boolean.

Dalam secret-box kami, kami dapat menambahkan konstruktor dan menetapkan nilai default:

 constructor() { super(); this.open = true; }

Dengan perubahan itu, berikut ini akan menampilkan "Kotak terbuka" dengan benar di komponen Svelte.

 <secret-box open></secret-box> <secret-box open=""></secret-box>

Membungkus

Setelah Anda memahami bagaimana Svelte memutuskan untuk menetapkan atribut atau properti, banyak dari masalah yang tampaknya aneh ini mulai lebih masuk akal. Setiap kali Anda mengalami masalah dalam mengirimkan data ke elemen khusus di dalam aplikasi Svelte, cari tahu apakah itu disetel sebagai atribut atau properti dan mulai dari sana. Saya telah memberi Anda beberapa palka pelarian dalam artikel ini untuk memaksa satu atau yang lain ketika Anda perlu, tetapi itu biasanya tidak diperlukan. Sebagian besar waktu, elemen khusus di Svelte hanya berfungsi. Anda hanya perlu tahu ke mana harus mencari jika terjadi kesalahan.


Terima kasih khusus kepada Dale Sande, Gus Naughton, dan Nanette Ranes karena telah meninjau versi awal artikel ini.


Posting Using Custom Elements in Svelte muncul pertama kali di CSS-Tricks . Anda dapat mendukung Trik-CSS dengan menjadi Pendukung MVP .

June 22, 2021

codeorayo

Ampuh! Ini rahasia mengembangkan aplikasi secara instan, tinggal download dan kembangkan. Gabung sekarang juga! Premium Membership [PRIVATE] https://premium.codeorayo.com

Leave a Reply

Your email address will not be published. Required fields are marked *