Prompt dan Ruang Lingkup
Diberikan sebuah API pencarian, rancanglah komponen autocomplete yang dapat digunakan kembali. Komponen tersebut harus meminta paling banyak 10 saran setelah pengguna berhenti mengetik selama 300 md, dan API memiliki latensi p95 sebesar 400 md. Di desktop maupun mobile, komponen harus mendukung keyboard, mouse, sentuh, pembaca layar, dan input IME, serta tidak boleh pernah menampilkan hasil untuk kueri yang lebih lama saat pengetikan cepat. Jelaskan API komponen, model state, permintaan asinkron, semantik aksesibilitas, caching, penanganan error, dan pengujian.
Angka-angka tersebut adalah asumsi wawancara. Ruang lingkup dasar adalah komponen frontend; peringkat sisi server, koreksi ejaan, personalisasi, multi-pilih, dan infinite scrolling berada di luar ruang lingkup. Sebuah hasil dapat berupa teks biasa atau baris yang di-render secara kustom, tetapi setiap hasil harus memiliki ID yang stabil dan label yang dapat dibaca. Komponen mengikuti pola combobox yang dapat diedit dengan daftar saran pilihan tunggal.
Pertanyaan ini cocok untuk peran frontend dan full-stack. Keterampilan intinya adalah UI browser, state asinkron, dan interaksi yang aksesibel, sehingga kategorinya adalah frontend. Artikel Trie yang ada membahas pencarian prefiks sebagai masalah struktur data dan hanya menyebut Top K sebagai tindak lanjut. Di sini, API pencarian adalah dependensi yang diberikan; masalah yang perlu diselesaikan secara mandiri adalah race condition sisi klien, semantik fokus, perilaku IME, dan kontrak interaksi yang dapat diverifikasi.
Apa yang Dievaluasi oleh Pewawancara
Sinyal pertama adalah apakah kandidat memisahkan pengurangan permintaan dari kebenaran. Debounce 300 md mengurangi permintaan selama pengetikan terus-menerus, tetapi tidak mencegah permintaan yang lebih lama kembali setelah permintaan yang lebih baru. Jawaban yang kuat menggabungkan pembatalan dengan urutan permintaan yang meningkat secara monoton dan hanya membiarkan permintaan terbaru untuk kueri saat ini yang mengonfirmasi hasilnya.
Sinyal kedua adalah apakah semantik aksesibilitas membentuk kontrak yang lengkap. Gaya visual saja tidak dapat menghubungkan input, popup, dan opsi. Fokus DOM harus tetap pada input sementara aria-activedescendant mengidentifikasi opsi yang aktif. aria-expanded, aria-controls, aria-autocomplete, listbox, option, dan aria-selected harus berubah secara konsisten sesuai dengan state yang terlihat.
Sinyal ketiga adalah apakah model input teks sudah akurat. IME bahasa Mandarin, Jepang, dan lainnya menghasilkan beberapa pembaruan selama satu sesi komposisi. Mencari setiap string perantara menghasilkan permintaan yang tidak relevan dan dapat mengganggu pemilihan kandidat. Komponen harus melacak komposisi dan menjadwalkan pencarian untuk nilai yang telah dikonfirmasi setelah compositionend.
Sinyal keempat adalah apakah state dan UI dapat terbukti konsisten. Satu boolean loading dan sebuah array tidak dapat dengan jelas mengekspresikan kueri pendek, sedang memuat, berhasil, hasil kosong, gagal, dan popup yang tertutup. Jawaban yang kuat mendefinisikan transisi, ID opsi yang stabil, perilaku pemulihan, dan aturan untuk opsi aktif setelah hasil berubah, kemudian menguji invariant tersebut dengan respons yang tidak berurutan, keyboard, dan pembaca layar.
Pertanyaan Klarifikasi Sebelum Menjawab
- Apa yang terjadi setelah saran dipilih? Jika hanya mengisi input, panggil
onSelectdan tutup daftarnya. Jika langsung menavigasi, kegagalan navigasi dan pemulihan input saat kembali memerlukan kontrak tersendiri. Desain dasar mengisi input dan memancarkan callback. - Siapa yang mengontrol nilai input? Sebuah form mungkin memerlukan
valuedanonValueChange; sebuah kotak pencarian mandiri mungkin mendukung nilai awal yang tidak dikontrol. Komponen tidak boleh berganti mode selama masa pakainya. - Apakah hasilnya berupa teks biasa? Hasil yang kaya memerlukan
renderItem, tetapigetKeydangetLabeltetap harus menyediakan ID yang stabil dan nama yang aksesibel. Menerima HTML sembarang meningkatkan risiko injeksi. - Berapa karakter yang memulai pencarian? Nilai default dasarnya adalah dua. Di bawah ambang batas tersebut, batalkan pekerjaan yang tertunda, tutup daftar, dan hapus opsi aktif. Menampilkan riwayat untuk kueri kosong akan memerlukan
aria-autocompletedan kebijakan cache yang berbeda. - Apakah Tab memilih saran yang aktif? Dalam desain dasar, tidak; Tab meninggalkan widget. Jika produk menginginkan Tab menerima saran, perilaku tersebut memerlukan komunikasi eksplisit kepada pengguna dan pengujian terpisah, bukan mengesampingkan pergerakan fokus normal secara diam-diam.
- Apakah hasil lama harus tetap ada setelah terjadi error? Desain ini menghapusnya dan menampilkan error yang dapat dicoba ulang agar pengguna tidak keliru mengira saran kueri lama sebagai kueri saat ini. Produk yang mengutamakan offline dapat menampilkan entri cache yang ditandai sebagai mungkin sudah usang, tetapi itu adalah kontrak yang berbeda.
Kerangka Jawaban 30 Detik
"Saya akan memisahkan rendering yang dapat dikustomisasi dari kontroler state tanpa tampilan. Kontroler memiliki kueri, status permintaan, state popup, ID opsi aktif, state IME, dan urutan permintaan terbaru. Setelah input yang dikonfirmasi, ia melakukan debounce selama 300 milidetik dan membatalkan permintaan sebelumnya; sebuah respons harus tetap cocok dengan urutan terbaru dan kueri saat ini sebelum dapat dikonfirmasi. Input menggunakan semantik combobox dan mempertahankan fokus DOM, sementara tombol panah memperbarui aria-activedescendant, Enter memilih, dan Escape menutup. Pencarian menunggu komposisi selesai, dan pesan status terpisah mengumumkan sedang memuat, jumlah hasil, dan tidak ada hasil. Saya akan memverifikasinya dengan respons jaringan yang diurutkan ulang, matriks keyboard, input IME, pembaca layar, dan invalidasi cache."
Penjelasan Mendalam Langkah demi Langkah
Langkah 1: Tentukan batas dan API publik
Algoritma pencarian milik server. Komponen menerima fungsi kueri dan kebijakan rendering. Antarmuka yang netral terhadap framework bisa terlihat seperti ini:
Autocomplete<T>({
value,
onValueChange,
fetchSuggestions(query, signal),
getKey(item),
getLabel(item),
renderItem,
onSelect,
minChars = 2,
limit = 10,
debounceMs = 300,
})fetchSuggestions menerima AbortSignal agar pemanggil dapat meneruskan satu rantai pembatalan ke jaringan. getKey menyediakan ID DOM opsi yang stabil, dan getLabel menyediakan teks pengisian serta nama yang aksesibel. Rendering kustom tidak boleh menggantikan semantik keyboard, fokus, atau pemilihan. Jika komponen mengekspos callback perubahan nilai, sertakan alasan terstruktur seperti input, selection, atau clear agar konsumen tidak perlu menyimpulkan maksud pengguna dari perubahan string.
Langkah 2: Batasi rendering dengan state machine
State inti adalah:
query status = idle | loading | success | empty | error items isOpen activeId selectedItem isComposing latestRequestSeq
query adalah teks input saat ini; selectedItem adalah pilihan yang dikonfirmasi. Keduanya adalah fakta yang berbeda dan tidak boleh berbagi satu field. Daftar hanya dapat terbuka ketika kueri memenuhi panjang minimum, input masih dalam konteks interaktif, dan status memiliki hasil atau umpan balik untuk ditampilkan. Hapus activeId ketika kueri baru dimulai. Ketika hasil baru tiba, pertahankan ID aktif lama hanya jika masih ada; jika tidak, hapus agar aria-activedescendant tidak pernah merujuk ke node yang tidak ada.
empty dan error adalah state yang berbeda. Respons kosong adalah valid; error dapat dicoba ulang. Menutup popup tidak harus menghancurkan kueri atau cache, tetapi harus mereset isOpen dan activeId.
Langkah 3: Pisahkan debounce, pembatalan, dan konfirmasi hasil
Sebuah event input pertama-tama memperbarui query secara sinkron. Jika komposisi sedang aktif, kueri yang dinormalisasi memiliki kurang dari dua karakter, atau hanya berisi spasi, hapus timer, batalkan permintaan saat ini, dan reset daftar. Jika tidak, mulai permintaan setelah 300 md. Alur permintaan dapat diekspresikan sebagai pseudocode:
async function search(rawQuery) { const query = normalize(rawQuery) const seq = ++latestRequestSeq
controller?.abort() controller = new AbortController() setStatus("loading")
try { const items = await fetchSuggestions(query, controller.signal) if (seq !== latestRequestSeq || query !== normalize(currentQuery)) return commit(items.slice(0, 10)) } catch (error) { if (isAbort(error)) return if (seq === latestRequestSeq && query === normalize(currentQuery)) { commitError(error) } } }
AbortController dapat menghentikan permintaan Fetch yang belum selesai dan konsumsi body respons, yang menghemat pekerjaan. Urutan permintaan adalah gerbang kebenaran. Operasi lama mungkin sudah selesai, atau pemanggil mungkin menggunakan lapisan data yang tidak sepenuhnya menghormati sinyal, sehingga memanggil abort() saja tidak membuktikan bahwa hasil yang sudah usang tidak dapat menimpa yang baru.
Dengan debounce 300 md dan p95 API 400 md, jalur p95 dari penekanan tombol terakhir ke hasil adalah sekitar 700 md sebelum rendering. Penurunan ini membuat trade-off menjadi eksplisit. Jika pengalaman perlu lebih cepat, gunakan cache kueri-tepat atau turunkan debounce setelah mengukur biaya permintaan; jangan klaim bahwa debounce 300 md masih dapat menghasilkan respons tanpa cache 150 md.
Langkah 4: Tangani IME, pointer, dan fokus dengan benar
compositionstart menetapkan isComposing menjadi true. Selama komposisi, input memperbarui teks yang terlihat tetapi tidak menjadwalkan pencarian. compositionend menghapus flag dan menjadwalkan satu pencarian untuk teks akhir yang dikonfirmasi. Event keyboard mungkin masih terjadi saat komposisi aktif dan melaporkan isComposing, sehingga Enter tidak boleh memilih saran pada saat itu. Uji pembungkus event framework di browser target alih-alih mengasumsikan bahwa input bahasa Inggris di desktop mewakili setiap urutan.
Pemilihan dengan mouse dan sentuh harus memperhitungkan input yang kehilangan fokus sebelum klik berjalan, yang dapat menghapus opsi yang diklik. pointerdown utama dapat mempertahankan fokus input, sementara click atau pointerup yang tetap dalam ambang pergerakan menyelesaikan pemilihan melalui satu jalur bersama. Menggulir daftar tidak boleh mengubah pergerakan pointer biasa menjadi pemilihan. Klik di luar menutup popup, sementara callback pemilihan tetap berjalan tepat satu kali.
Fokus DOM tetap pada input. Opsi popup dikecualikan dari urutan Tab, dan Tab mengikuti default browser untuk meninggalkan widget. Ini menjaga perilaku pengeditan teks native dan mode input teknologi asistif tetap stabil.
Langkah 5: Implementasikan combobox dan kontrak keyboard
Input memiliki label yang terlihat, atau mendapatkan namanya melalui aria-labelledby atau aria-label. Input menggunakan role="combobox", aria-autocomplete="list", aria-expanded yang disinkronkan dengan popup, aria-controls yang menunjuk ke daftar, dan aria-activedescendant hanya ketika opsi aktif ada.
Kontainer saran menggunakan role="listbox". Setiap item memiliki role="option" dan ID DOM yang stabil, dan item yang aktif secara visual juga memiliki aria-selected="true". Tombol Panah Bawah membuat opsi pertama aktif lalu bergerak ke bawah; Panah Atas bergerak sebaliknya sementara fokus DOM tetap pada input. Desain dasar berhenti di batas alih-alih memutar. Enter menerima opsi yang aktif. Escape menutup popup tetapi mempertahankan teks input.
Jangan mencegat Left, Right, Home, End, Backspace, atau karakter yang dapat dicetak secara tanpa syarat. Combobox yang dapat diedit harus mempertahankan perilaku pengeditan satu baris native browser. Panggil preventDefault() hanya ketika komponen benar-benar menangani pergerakan daftar, pemilihan, atau penutupan.
Daftar hasil tidak perlu menjadi live region berprioritas tinggi pada setiap pembaruan. Gunakan elemen role="status" terpisah untuk mengumumkan "Sedang mencari," "10 hasil," atau "Tidak ada hasil" tanpa memindahkan fokus. Mengumumkan pada setiap penekanan tombol dapat membuat widget terlalu bawel.
Langkah 6: Batasi caching dan pemulihan kegagalan
Kunci cache mencakup setidaknya kueri yang dinormalisasi, lokal, filter, dan versi data. Caching hanya berdasarkan kueri mengembalikan data yang salah setelah perubahan lokal atau filter. Gunakan cache kueri-tepat dengan TTL dan batas kapasitas. Sebuah hit dapat langsung dirender, diikuti dengan refresh latar belakang hanya jika kebijakan kesegaran produk memerlukannya.
Respons yang di-cache tetap melewati pemeriksaan kueri-saat-ini dan urutan-permintaan. Menghapus input, memilih hasil, melepas komponen, atau mengubah dependensi membatalkan timer dan permintaan. Error server mengekspos tindakan coba ulang kecil; tidak menjadi baris yang dapat dipilih dalam daftar saran. Coba ulang membuat urutan permintaan baru, sehingga error lama tidak dapat menimpa keberhasilan yang lebih baru.
Sorot kecocokan dengan node teks atau fragmen terstruktur yang dipercaya, bukan markup server yang tidak disanitasi. Enkode parameter kueri dengan benar, dan jangan catat kueri sensitif lengkap kecuali benar-benar diperlukan.
Langkah 7: Verifikasi invariant dengan skenario adversarial
Tes unit dengan clock yang dikontrol membuktikan bahwa 299 md tidak menyebabkan permintaan, 300 md menyebabkan satu permintaan, pengetikan lanjutan membatalkan timer sebelumnya, dan kueri yang lebih pendek dari dua karakter mereset state. Tes state mencakup berhasil, hasil kosong, error, coba ulang, tutup, dan pemilihan.
Tes race membuat respons lambat untuk a tiba setelah respons cepat untuk ab; daftar akhir mungkin hanya berisi hasil ab. Uji tiga jalur secara terpisah: permintaan yang dapat dibatalkan, permintaan yang sudah selesai, dan lapisan data yang mengabaikan sinyal pembatalan.
Tes interaksi mencakup panah, Enter, Escape, Tab, klik, sentuh, klik di luar, komposisi, dan opsi aktif yang menghilang setelah hasil berubah. Setiap langkah menegaskan state yang terlihat, nilai input, jumlah callback, fokus DOM, dan atribut ARIA secara bersamaan.
Pemindaian aksesibilitas otomatis hanya menangkap sebagian dari kontrak. Gunakan keyboard dan pembaca layar target untuk menyelesaikan input, sedang memuat, pengumuman hasil, pemilihan, hasil kosong, dan pemulihan error. Uji sentuh dan keyboard perangkat lunak di browser mobile yang didukung. Tes performa mencatat distribusi dari penekanan tombol terakhir ke hasil interaktif pertama, tingkat pembatalan permintaan, tingkat cache hit, dan jumlah respons usang yang dibuang.
Contoh Jawaban Berkualitas Tinggi
"Saya akan membatasi ruang lingkup ini pada komponen saran pilihan tunggal di frontend; API yang ada memiliki pencarian dan peringkat. Komponen menerima nilai yang dikontrol, fungsi kueri, kunci stabil, label yang dapat dibaca, renderer baris kustom, dan callback pemilihan. Secara internal, teks kueri, objek yang dipilih, status permintaan, state popup, opsi aktif, state IME, dan urutan permintaan tetap terpisah, sehingga hasil kosong, error, dan popup yang tertutup tidak runtuh menjadi satu boolean.
Input diperbarui secara langsung. Setelah memiliki dua karakter dan komposisi telah selesai, saya melakukan debounce selama 300 milidetik, membatalkan permintaan sebelumnya, dan memulai permintaan yang diberi urutan. Sebuah respons hanya dapat dikonfirmasi jika urutannya masih yang terbaru dan kuerinya masih cocok dengan input. Pembatalan menghemat pekerjaan; pengurutan menjamin kebenaran. Asumsi 300 ditambah 400 milidetik menempatkan jalur p95 tanpa cache sekitar 700 milidetik, sehingga latensi nyata dan biaya permintaan harus menentukan debounce.
Secara semantik, input yang diberi nama adalah combobox yang mengontrol listbox. Fokus DOM tetap pada input; tombol panah mengubah aria-activedescendant yang stabil, Enter memilih, Escape menutup, dan Tab meninggalkan secara normal. Opsi menggunakan option dan aria-selected, sementara region status terpisah mengumumkan sedang memuat, jumlah hasil, dan tidak ada hasil. Pencarian dan pemilihan Enter keduanya ditangguhkan selama komposisi.
Cache dibatasi oleh TTL dan kapasitas serta dikunci berdasarkan kueri yang dinormalisasi, lokal, dan filter. Saya akan memverifikasi respons lambat-lama versus cepat-baru, IME, perilaku keyboard dan pembaca layar, blur pointer, hasil kosong, coba ulang, dan invalidasi cache. Keberhasilan berarti setiap hasil yang terlihat milik kueri saat ini dan state fokus serta aksesibilitas cocok dengan state visual di setiap transisi."
Kesalahan Umum
- Hanya melakukan debounce → Debounce mengurangi permintaan tetapi tidak mengurutkan respons → Tambahkan pembatalan dan batasi konfirmasi pada urutan terbaru dan kueri saat ini.
- Hanya memanggil
abort()→ Pekerjaan lama mungkin sudah selesai atau lapisan data mungkin mengabaikan sinyal → Perlakukan pembatalan sebagai optimasi dan pemeriksaan urutan sebagai kondisi kebenaran. - Menggunakan indeks array sebagai item aktif → Indeks yang sama mungkin mengidentifikasi hasil yang berbeda setelah pengurutan ulang → Gunakan ID hasil yang stabil dan konfirmasi bahwa itu masih ada setelah mengganti daftar.
- Memindahkan fokus DOM ke setiap opsi → Input, pengeditan teks, dan fokus teknologi asistif dapat berdivergensi → Pertahankan fokus pada input dan ekspos item aktif melalui
aria-activedescendant. - Mencegat setiap event keyboard → Perilaku pengeditan native Home, End, panah, dan IME rusak → Cegat hanya tombol yang benar-benar digunakan komponen untuk navigasi daftar, pemilihan, atau penutupan.
- Mencari setiap pembaruan IME → Teks fonetis yang belum dikonfirmasi menghasilkan permintaan yang buruk, dan Enter mungkin memilih secara tidak sengaja → Lacak sesi komposisi dan cari hanya setelah selesai.
- Menjadikan seluruh daftar hasil sebagai pengumuman mendesak → Setiap pembaruan input memicu ucapan yang panjang → Gunakan pesan
role="status"yang terkendali untuk sedang memuat, jumlah, dan hasil kosong. - Caching hanya berdasarkan kueri → Perubahan lokal atau filter dapat mengenai data yang salah → Masukkan setiap kondisi yang mempengaruhi hasil dan versi dalam kunci, dengan batas TTL dan kapasitas.
Tindak Lanjut dan Cara Menanganinya
Tindak Lanjut 1: Apa yang berubah jika daftar harus menampilkan 1.000 hasil?
Tantang persyaratan tersebut terlebih dahulu: autocomplete biasanya harus memberi peringkat dan membatasi kumpulan saran yang relevan dalam jumlah kecil. Jika penelusuran kumpulan besar diperlukan, tambahkan windowing. Opsi aktif harus tetap terpasang, atau digulir ke dalam jendela yang terpasang sebelum memperbarui aria-activedescendant; atribut tidak dapat merujuk ke node yang sudah divirtualisasi. Ekspos semantik posisi dan ukuran total yang bermakna dan verifikasi dengan pembaca layar, bukan hanya pengukuran frame rate.
Tindak Lanjut 2: Bagaimana beberapa halaman dapat berbagi permintaan dan entri cache?
Pindahkan penyimpanan cache dan deduplikasi permintaan ke lapisan data tingkat halaman sementara komponen tetap hanya menggunakan fetchSuggestions. Kunci bersama mencakup lokal, filter, cakupan otorisasi, dan versi data. Panggilan untuk kunci yang sama dapat berbagi Promise, tetapi setiap komponen mempertahankan urutan permintaannya sendiri karena dua input dapat memiliki kueri saat ini dan waktu pelepasan yang berbeda.
Tindak Lanjut 3: Bagaimana Anda akan menambahkan riwayat lokal dan saran kueri kosong?
Tandai setiap sumber sebagai history atau remote, lalu tentukan penghapusan, privasi, dan sinkronisasi lintas perangkat. Riwayat pada kueri kosong adalah state yang berbeda dari melengkapi input yang diketik, sehingga memerlukan aria-autocomplete, judul, dan kebijakan pengumuman yang eksplisit. Pemilihan riwayat tetap mengikuti kontrak ID-stabil, dan pencarian sensitif tidak boleh dipertahankan secara default.
Tindak Lanjut 4: Bagaimana ini akan bekerja dengan server rendering dan hidrasi?
Server dapat merender label dan input kosong, sementara klien memiliki popup interaktif. ID input, daftar, dan opsi harus stabil di antara server dan klien; ID render pertama yang acak dapat merusak aria-controls setelah hidrasi. Jika server memuat saran terlebih dahulu, serialisasikan kunci cache, kueri, dan versi data secara bersamaan dan gunakan kembali payload hanya ketika ketiganya masih cocok.
Tindak Lanjut 5: Bagaimana Anda akan mengubah komponen untuk mendukung chip multi-pilih?
Itu mengubah semantik fokus, penghapusan, dan item yang dipilih; mengganti selectedItem dengan array tidaklah cukup. Tentukan pergerakan kiri dan kanan antara input dan chip, konfirmasi Backspace, duplikat, jumlah maksimum, dan pengumuman pembaca layar. Daftar saran dapat tetap menjadi combobox, tetapi chip yang dipilih memerlukan region navigasi tersendiri dan pengujian keyboard serta teknologi asistif yang baru.