Konteks dan cakupan
Anda harus merilis elemen kustom <account-summary> untuk host yang dirender oleh React, Vue, atau template server. Elemen ini menampilkan status akun, bereaksi terhadap atribut status, mengirimkan (dispatch) event publik, dan melepaskan langganan saat dihapus dari dokumen. Gaya (style) harus tetap terisolasi tanpa menghilangkan semantik keyboard atau teknologi asistif.
Asumsikan autonomous custom element terlebih dahulu. Jika persyaratannya adalah memperluas button native atau elemen bawaan lainnya, evaluasi customized built-in elements secara terpisah karena dukungan browser mengubah desainnya. Wawancara ini menguji batas siklus hidup dan kepemilikan sumber daya, bukan sintaksis komponen framework.
Hal yang dievaluasi oleh pewawancara
- Apakah Anda dapat memberikan tanggung jawab yang berbeda untuk constructor, connectedCallback, disconnectedCallback, dan pengamatan atribut.
- Apakah Anda menangani penyambungan ulang (reconnects), pemindahan (moves), peningkatan (upgrades), dan atribut awal alih-alih mengasumsikan setiap callback hanya berjalan sekali.
- Apakah Anda menghubungkan enkapsulasi Shadow DOM dengan aksesibilitas, propagasi event, dan integrasi host.
- Apakah Anda dapat mengusulkan registrasi yang idempoten, kompatibilitas, pembersihan, dan pengujian.
Sekadar menghafal nama-nama callback tidaklah cukup. Jawaban yang kuat mendefinisikan kepemilikan sumber daya terlebih dahulu, lalu menjelaskan bagaimana koneksi, atribut, dan status render berinteraksi.
Pertanyaan untuk diklarifikasi terlebih dahulu
- Apakah elemen harus memperluas elemen semantik native? Jika ya, sintaksis
isdan dukungan browser akan memengaruhi pilihan tersebut. - Apakah input berupa string, atau pemanggil harus meneruskan objek dan callback? Nilai kompleks memerlukan kontrak properti atau metode yang eksplisit.
- Apakah status (state) harus bertahan saat dipindahkan dalam dokumen yang sama? Ini menentukan apakah perlu mengevaluasi
connectedMoveCallbackatau mempertahankan status secara eksplisit. - Apakah event harus melintasi batas Shadow DOM? Sepakati
bubbles,composed, dan payload sebelum memilih API event.
Kerangka jawaban 30 detik
“Saya memisahkan definisi, koneksi, sinkronisasi atribut, rendering, dan pembersihan. constructor hanya menginisialisasi field yang ringan dan tidak bergantung pada node anak atau dokumen eksternal. connectedCallback membentuk langganan dan merender secara idempoten; disconnectedCallback melepaskan listener, timer, dan observer. Saya hanya mengamati atribut yang diperlukan, menormalisasi nilai string-nya di attributeChangedCallback, dan mengarahkan pembaruan melalui satu jalur render. Shadow DOM mengisolasi gaya implementasi, sementara semantik publik, fokus, dan event tetap menjadi bagian dari kontrak host yang teruji. Saya mengakhirinya dengan pengujian untuk reconnects, moves, atribut awal, penghapusan, upgrades, dan browser yang tidak didukung.”
Analisis langkah demi langkah
1. Tentukan kepemilikan status dan sumber daya
Lacak status terhubung, berlangganan, dan ter-render secara terpisah. constructor tidak boleh membaca DOM luar atau memulai pekerjaan jaringan: browser dapat mengonstruksi elemen sebelum menghubungkannya ke dokumen.
class AccountSummary extends HTMLElement {
static observedAttributes = ["status"];
#connected = false;
#unsubscribe = null;
#root;
constructor() {
super();
this.#root = this.attachShadow({ mode: "open" });
}
}2. Buat connectedCallback aman untuk diulang
Elemen dapat dihapus dan dimasukkan kembali, sehingga connectedCallback tidak boleh mendaftarkan listener lain secara tanpa syarat. Periksa status koneksi, buat langganan, dan buat rendering aman untuk dipanggil berulang kali.
connectedCallback() {
if (this.#connected) return;
this.#connected = true;
this.#unsubscribe = accountStore.subscribe(() => this.#render());
this.#render();
}3. Lepaskan setiap efek samping di disconnectedCallback
Event listener, setInterval, ResizeObserver, AbortController, dan langganan store memerlukan pemilik yang jelas. Bersihkan referensi setelah pembersihan sehingga koneksi berikutnya membuat tepat satu set sumber daya baru.
disconnectedCallback() {
this.#unsubscribe?.();
this.#unsubscribe = null;
this.#connected = false;
}Pola pemindahan yang lebih lama dapat memicu callback putus dan sambung secara berurutan. Jika mempertahankan status saat pemindahan itu penting, evaluasi connectedMoveCallback jika didukung, atau bangun kembali dari atribut dan status yang tahan lama alih-alih mengandalkan urutan callback.
4. Amati atribut dan pisahkan properti
observedAttributes hanya boleh mencantumkan nilai yang memerlukan sinkronisasi. attributeChangedCallback dapat berjalan untuk atribut awal selama penguraian (parsing), sehingga “berubah” tidak selalu berarti pengguna baru saja mengeditnya. Tangani nilai awal dan nilai berikutnya melalui jalur yang sama dan hindari menulis kembali atribut yang sama dari callback-nya.
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) return;
if (name === "status") this.#renderStatus(newValue ?? "unknown");
}Atribut string berfungsi baik untuk HTML deklaratif. Teruskan array, objek, dan callback melalui properti atau metode yang terdokumentasi alih-alih memperlakukan string JSON arbitrer sebagai protokol tanpa batas.
5. Rancang Shadow DOM dan kontrak host
Shadow DOM mengisolasi CSS internal dan kueri DOM, tetapi tidak menyediakan semantik secara otomatis. Gunakan role yang benar, nama yang dapat diakses (accessible names), urutan fokus, dan visibilitas. Jika host harus berpartisipasi dalam tata letak, ekspos kontrak yang sempit melalui :host, slot, atau CSS custom properties.
:host { display: block; }
:host([hidden]) { display: none; }Untuk event yang harus diamati oleh host, atur bubbles dan composed secara sengaja dan jaga agar payload tetap stabil. Melintasi batas tidak memerlukan pengungkapan node internal.
6. Tangani registrasi, peningkatan (upgrades), dan kompatibilitas
Sebuah nama hanya dapat ditentukan sekali dalam registry global. Library bersama harus menggunakan prefiks namespace dan memeriksa customElements.get(name) sebelum mendefinisikan; jangan sembunyikan bentrokan dengan menangkap exception. Elemen yang di-parse sebelum registrasi akan di-upgrade setelah definisi, sehingga implementasi harus mendukung urutan tersebut.
const name = "account-summary";
if (!customElements.get(name)) {
customElements.define(name, AccountSummary);
}Elemen otonom biasanya lebih mudah di-deploy di berbagai browser. MDN mendokumentasikan keterbatasan Safari untuk customized built-in elements; jika memperluas elemen native adalah hal wajib, verifikasi matriks browser target dan siapkan fallback.
Contoh jawaban berkualitas tinggi
Pertama-tama saya akan menulis tabel kepemilikan: constructor membuat Shadow Root dan field default, connectedCallback berlangganan dan merender, disconnectedCallback berhenti berlangganan, dan attributeChangedCallback hanya menangani konfigurasi deklaratif yang diamati. Setiap fase dapat diulang atau dilewati dengan aman; “berjalan sekali” tidak dianggap sebagai jaminan siklus hidup.
Saya akan memilih autonomous custom element dan melindungi registrasi dengan customElements.get. Shadow DOM berisi gaya implementasi, sementara komponen tetap mengekspos nama yang dapat diakses, perilaku fokus, dan kontrak event. Event lintas batas menggunakan pengaturan bubbles dan composed yang eksplisit dengan payload yang dibatasi. Atribut membawa konfigurasi deklaratif primitif; objek kompleks menggunakan properti atau metode.
Verifikasi mencakup upgrade parse-before-define, atribut awal, reconnects, moves, pembersihan, event lintas batas, perilaku keyboard, dan semantik pembaca layar (screen-reader). Jika customized built-in elements diperlukan, saya akan memvalidasi matriks browser terlebih dahulu, kemudian memilih jalur tersebut hanya jika biaya kompatibilitas dapat diterima.
Kesalahan umum dan perbaikan
- Kesalahan → Membaca node anak atau memulai request di constructor → Mengapa gagal → Elemen mungkin belum terhubung → Perbaikan → Pindahkan pekerjaan dokumen dan jaringan ke connectedCallback dan ikat request ke AbortController.
- Kesalahan → Menambahkan listener pada setiap connectedCallback → Mengapa gagal → Reconnect menduplikasi pekerjaan dan menyebabkan kebocoran memori → Perbaikan → Simpan handle langganan dan jadikan penyiapan/pembersihan sebagai pasangan yang idempoten.
- Kesalahan → Memanggil setAttribute tanpa syarat dari callback atribut → Mengapa gagal → Hal ini dapat membentuk loop callback, dan parsing atribut awal juga memanggil callback tersebut → Perbaikan → Bandingkan nilai lama dan baru, normalisasi sekali, dan hindari menulis atribut yang sama.
- Kesalahan → Memperlakukan Shadow DOM sebagai solusi aksesibilitas → Mengapa gagal → Enkapsulasi tidak membuat nama, fokus, atau semantik → Perbaikan → Uji kontrak publik dengan alur keyboard dan teknologi asistif.
Pertanyaan lanjutan dan tanggapan
Elemen di-parse sebelum didefinisikan. Bagaimana Anda menghindari flash (kedipan tampilan)?
Perlakukan elemen yang belum terdefinisi sebagai status yang dapat di-upgrade dan sediakan konten fallback yang dapat dibaca atau status loading. Browser meng-upgrade instance setelah registrasi. Jika kode harus menunggu, host dapat melakukan await pada customElements.whenDefined("account-summary"), tetapi seluruh halaman tidak boleh terblokir oleh skrip komponen.
Haruskah objek kompleks ditempatkan di atribut?
Biasanya tidak. Atribut berupa string dan cocok untuk konfigurasi deklaratif yang dapat diserialisasi; objek dan callback harus menggunakan properti atau metode yang terdokumentasi dengan perilaku sebelum dan sesudah koneksi yang jelas. Jika serialisasi diperlukan, tentukan versi, ukuran, dan penanganan kesalahan parse.
Mengapa host tidak menerima klik dari tombol di dalam Shadow DOM?
Propagasi lintas batas bergantung pada composed; bubbling ke atas bergantung pada bubbles. Kirimkan event semantik yang stabil dengan kedua opsi dipilih secara eksplisit. Di luar batas, target dapat diarahkan ulang (retargeted) ke shadow host, sehingga host tidak boleh bergantung pada node internal.
Bagaimana Anda membuktikan bahwa reconnects tidak bocor?
Masukkan dan hapus instance yang sama berulang kali, hitung langganan, pengiriman event, dan handle timer, lalu gunakan snapshot memori untuk memverifikasi bahwa node lama dapat dikumpulkan oleh garbage collector. Cakup callback atribut awal, moves, dan upgrade parse-before-define; render pertama saja membuktikan sangat sedikit hal.