Topik wawancara representatif

Wawancara Frontend: Bagaimana Cara Membangun Sortable List Drag-and-Drop yang Aksesibel?

FrontendSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Bangun sortable task list yang mendukung dragging mouse dan sentuhan tanpa menjadikan dragging sebagai satu-satunya cara untuk menyusun ulang. Jelaskan model state, operasi penyusunan ulang dengan ID stabil, alternatif keyboard dan single-pointer, perilaku fokus dan screen-reader, pembatalan, kegagalan persistensi, serta rencana verifikasi aksesibilitas.

Prompt dan Konteks yang Berlaku

Bangun task list yang item-itemnya dapat disusun ulang. Pengguna mouse dan layar sentuh dapat men-drag sebuah item, tetapi setiap penyusunan ulang juga harus dapat dilakukan dengan keyboard dan dengan klik atau ketukan yang tidak memerlukan gerakan dragging. Pengguna screen-reader harus mengetahui item mana yang berpindah dan posisi barunya. Desain harus mempertahankan fokus, mendukung pembatalan, menghindari hilangnya atau duplikasi item, dan menangani kegagalan penyimpanan tanpa membuat urutan yang terlihat menjadi ambigu.

Asumsikan list dasar dirender secara penuh, setiap item memiliki ID unik yang stabil, dan hanya satu item yang berpindah dalam satu waktu. Menyusun ulang dalam virtualized list, memindahkan beberapa item yang dipilih, dan menyelesaikan pengeditan konkuren adalah pertanyaan lanjutan. Framework bukanlah keputusan utama: React dapat merender view, tetapi wawancara menguji pemodelan input, semantik yang aksesibel, kepemilikan state, dan verifikasi.

Dragging adalah sebuah peningkatan (enhancement), bukan operasi bisnis. Operasi bisnisnya adalah “pindahkan item X ke posisi akhir Y.” Pointer drag, perintah keyboard, dan kontrol pemindahan yang terlihat semuanya harus memanggil operasi yang sama tersebut. Pemisahan ini mencegah tiga jalur input menghasilkan tiga urutan yang sedikit berbeda.

Apa yang Dievaluasi oleh Pewawancara

Sinyal pertama adalah apakah kandidat memisahkan identitas dari posisi. Indeks array adalah posisi sementara, sehingga tidak boleh dijadikan React key atau identitas item yang persisten. ID item yang stabil memilih apa yang berpindah; indeks akhir atau ID stabil tetangga memilih ke mana item tersebut berpindah.

Sinyal kedua adalah presisi aksesibilitas. Pengoperasian keyboard menurut WCAG dan persyaratan dragging-movement menurut WCAG 2.2 saling terkait tetapi independen. Pintasan khusus keyboard saja tidak secara otomatis memberikan alternatif single-pointer bagi pengguna yang dapat mengeklik atau mengetuk tetapi tidak dapat menahan dan men-drag. Kontrol Move up, Move down, atau Move to yang terlihat dapat memenuhi kedua jalur input tersebut.

Sinyal ketiga adalah kedisiplinan interaction-state. Jawaban yang kuat membedakan antara idle, active reorder, commit, dan cancel; mempertahankan urutan asli untuk rollback; menangani pointercancel, Escape, dan drop yang tidak valid; serta tidak mempersistensikan setiap posisi hover sebagai pembaruan bisnis.

Sinyal keempat adalah perilaku teknologi asistif (assistive technology). Semantik native list dan button, drag handle yang eksplisit, instruksi yang ringkas, fokus yang stabil, pengumuman status yang sopan (polite status announcement), dan indikator drop yang terlihat lebih penting daripada menambahkan banyak atribut ARIA. aria-grabbed dan aria-dropeffect sudah usang (deprecated) dan tidak boleh disajikan sebagai solusi.

Terakhir, pewawancara mencari verifikasi berbasis bukti: pengujian reorder murni, pengujian mouse dan sentuhan, pengoperasian khusus keyboard, pemeriksaan screen-reader, asersi fokus, perilaku reduced-motion dan forced-colors, serta pemulihan kegagalan penyimpanan. Pemindaian aksesibilitas otomatis tidak dapat membuktikan apakah alur penyusunan ulang yang diucapkan dapat dipahami.

Pertanyaan yang Perlu Diklarifikasi Sebelum Menjawab

  • Apakah penyusunan ulang dilakukan dalam satu list atau antar list? Satu list memerlukan posisi akhir; perpindahan lintas list juga memerlukan kepemilikan sumber dan tujuan, tipe drop yang diizinkan, dan pembaruan atomik.
  • Apakah urutan harus disimpan secara remote? Urutan lokal saja dapat langsung di-commit. Persistensi remote memerlukan perilaku pending, sukses, gagal, coba lagi, dan konflik versi.
  • Input mana saja yang masuk dalam cakupan? Event drag HTML khusus mouse tidak mencukupi jika pengguna sentuhan, keyboard, switch, suara, atau screen-reader diperlukan.
  • Apakah alternatif klik atau ketuk terlihat? Pintasan keyboard memenuhi kebutuhan yang berbeda. Untuk sortable list yang tidak esensial, sediakan kontrol yang dapat diaktifkan dengan satu pointer tanpa menahan dan menggerakkannya.
  • Apakah pratinjau perpindahan langsung terlihat atau hanya saat drop? Pratinjau langsung memberikan umpan balik visual yang lebih kuat, tetapi pembatalan harus mengembalikan urutan asli dan pengumuman tidak boleh berulang-ulang pada setiap piksel pointer.
  • Bisakah beberapa item dipilih sekaligus? Perpindahan multi-item mengubah model dari satu ID item menjadi ordered set dan memerlukan aturan untuk campuran item yang dapat dipindahkan dan yang terkunci.
  • Apakah list tersebut divirtualisasi (virtualized)? Posisi di luar layar tidak ada sebagai DOM drop target, sehingga hit testing, pengumuman, jumlah total, dan perpindahan keyboard harus beroperasi pada model data.
  • Bisakah klien lain menyusun ulang secara konkuren? Jika ya, kontrak penyimpanan memerlukan versi list atau prasyarat yang setara serta kebijakan konflik yang eksplisit.

Kerangka Jawaban 30 Detik

“Saya akan memodelkan reorder sebagai satu perintah murni: memindahkan ID item yang stabil ke indeks akhir. Pointer drag, aksi keyboard, dan tombol Move yang terlihat semuanya memanggil perintah ini. Saya akan menggunakan semantik native list dan button, mempertahankan fokus pada handle yang dipindahkan, dan mengumumkan posisi barunya. Sesi menyimpan urutan asli, sehingga Escape, pointercancel, drop yang tidak valid, atau kegagalan penyimpanan dapat memulihkannya. Kemudian saya akan memverifikasi invarian permutasi dan menguji alur mouse, sentuhan, khusus klik, keyboard, screen-reader, fokus, dan kegagalan.”

Pembahasan Mendalam Langkah demi Langkah

Mulailah dengan satu operasi data kanonikal. Input menggunakan ID yang stabil dan indeks akhir berbasis nol; output-nya adalah urutan baru. Operasi harus mempertahankan setiap ID tepat satu kali dan tidak mengubah array input.

typescript
interface SortableItem {
  id: string
  label: string
}

function moveItem(
  items: ReadonlyArray<SortableItem>,
  itemId: string,
  targetIndex: number,
): ReadonlyArray<SortableItem> {
  const fromIndex = items.findIndex((item) => item.id === itemId)

  if (fromIndex === -1 || items.length < 2) return items

  const finalIndex = Math.max(0, Math.min(targetIndex, items.length - 1))

  if (fromIndex === finalIndex) return items

  const next = [...items]
  const [movedItem] = next.splice(fromIndex, 1)

  if (!movedItem) return items

  next.splice(finalIndex, 0, movedItem)
  return next
}

Perintah ini membutuhkan waktu O(n) dan ruang O(n) karena penghapusan, penyisipan, dan penyalinan array menggeser item. Hal ini sesuai untuk task list normal yang dirender penuh. Virtualized list yang sangat besar dapat menyimpan sortable rank key atau menerapkan perpindahan di sisi server berdasarkan ID tetangga, tetapi kompleksitas tambahan tersebut memerlukan bukti dari persyaratan skala.

Gunakan ID yang stabil sebagai React key. Dengan key={item.id}, React dapat memindahkan node item yang ada daripada memperlakukan indeks baru sebagai identitas baru. Ini membantu mempertahankan handle yang sedang fokus dan state lokal item. Jika pembaruan asinkron tetap menghilangkan fokus, simpan ref dengan kunci ID item dan pulihkan fokus ke handle yang sama setelah render yang di-commit. Jangan memindahkan fokus ke awal list, dan jangan memaksakan perubahan fokus untuk pengguna pointer.

Buat markup dasar dapat dipahami tanpa drag. Ordered list dan list item mengekspos struktur koleksi. Setiap item memiliki tombol native yang dinamai, misalnya, “Reorder Review.” Sediakan tombol Move up dan Move down yang terlihat, atau aksi Move to position, dengan nama item disertakan dalam setiap accessible name. Nonaktifkan aksi batas yang mustahil. Kontrol ini bekerja melalui klik, ketukan, dan aktivasi keyboard, sehingga menjadi alternatif yang mudah ditemukan sekaligus jalur pemulihan sederhana ketika drag gagal.

Pembedaan antara dua persyaratan aksesibilitas mengubah desain. Pengoperasian keyboard berarti penyusunan ulang lengkap dapat dilakukan tanpa pointing device. Secara terpisah, dragging movement harus memiliki metode single-pointer yang tidak memerlukan penahanan dan pergerakan pointer saat dragging tidak esensial. Mode drag tombol panah dapat memuaskan pengguna keyboard tetapi tidak memenuhi persyaratan kedua kecuali kontrolnya juga dapat dioperasikan dengan klik atau ketuk. Tombol perpindahan yang terlihat memuaskan keduanya.

Mode keyboard drag ringkas opsional dapat mengurangi penekanan tombol Tab berulang kali. Fokuskan drag handle dan tekan Enter atau Space untuk memulai; Panah Atas dan Panah Bawah mengubah posisi kandidat; Enter atau Space melakukan commit; Escape membatalkan dan memulihkan snapshot. Tempatkan instruksi ini dalam teks yang direferensikan oleh handle agar pengguna dapat menemukannya. Jika item juga mendukung pemilihan, pisahkan perintah seleksi dan reorder agar Space tidak memiliki dua arti pada target fokus yang sama.

Representasikan interaksi sebagai sesi berumur pendek. Saat memulai, simpan itemId, jenis input, urutan asli, dan indeks kandidat saat ini. Pergerakan pointer atau keyboard hanya memperbarui kandidat melalui moveItem. Commit menghasilkan satu permintaan persistensi dan satu pengumuman. Cancel memulihkan urutan asli dan mengumumkan pembatalan. Respons server baru tidak boleh diizinkan menimpa sesi yang lebih baru hanya karena permintaannya selesai lebih lambat.

Untuk input pointer, gunakan handle eksplisit daripada membuat seluruh baris dapat di-drag. Ini mencegah drag mencuri klik dari tautan, checkbox, seleksi teks, atau scrolling. Jika menerapkan pengurutan dalam halaman dengan Pointer Events, tunggu ambang batas pergerakan kecil, tangkap pointer (pointer capture), hitung posisi kandidat dari titik tengah item, tampilkan indikator penyisipan yang tidak hanya mengandalkan warna, dan tangani pointerup, pointercancel, kehilangan capture, scrolling, serta drop di luar list. Library drag yang teruji lebih disukai ketika sensor sentuh, auto-scroll, deteksi tabrakan (collision detection), nested scroll container, dan perilaku teknologi asistif semuanya menjadi persyaratan.

Native HTML drag and drop cukup baik untuk transfer data desktop atau lintas aplikasi, tetapi tidak menyediakan alur kerja penyusunan ulang untuk keyboard atau screen-reader. Model event-nya juga memerlukan target drop valid yang eksplisit dan menekan event input perangkat lain selama drag. Memilihnya tidak menghilangkan kebutuhan akan kontrol pemindahan, perilaku fokus, pengumuman, atau verifikasi sentuhan.

Komunikasikan hasil melalui visual dan semantik. Tunjukkan titik penyisipan kandidat dengan bentuk atau batas serta warna, pertahankan indikator fokus yang terlihat, dan hormati preferensi reduced-motion. Gunakan region status polite yang persisten untuk pesan singkat setelah pemindahan keyboard yang disengaja, commit, pembatalan, dan kegagalan. Pesan seperti “Moved Review to position 2 of 5” memuat item, hasil, posisi, dan total. Jangan umumkan setiap hover pointer, dan jangan membuat ulang live region pada saat yang sama dengan pesannya.

Hindari semantik drag yang sudah usang. WAI-ARIA 1.2 menandai aria-grabbed dan aria-dropeffect sebagai deprecated. Elemen native, accessible name, instruksi deskriptif, state saat ini yang disampaikan dalam teks, dan pengumuman yang teruji memberikan kontrak yang lebih andal. ARIA tidak menambahkan perilaku keyboard yang hilang atau alternatif klik yang tidak ada.

Untuk persistensi remote, kirim ID terurut yang stabil atau perintah pemindahan beserta versi yang diedit klien. Tampilkan pratinjau hasil secara optimis, ekspos state penyimpanan tanpa memblokir fokus keyboard, dan commit pengumuman hanya berdasarkan aturan produk yang dipilih. Pada kegagalan jaringan, pertahankan urutan lokal yang tertunda dengan opsi Retry atau lakukan rollback ke snapshot dan umumkan rollback tersebut. Pada konflik versi, ambil urutan saat ini dan minta pengguna untuk mencoba lagi atau terapkan merge yang telah ditentukan; menimpa urutan klien lain secara diam-diam bukanlah strategi pemulihan.

Verifikasi dimulai dari perintah murni. Uji pemindahan pertama, terakhir, bersebelahan, posisi yang sama, ID yang tidak dikenal, dan di luar rentang. Untuk input yang di-generate, pastikan output berisi ID yang sama dengan jumlah yang sama dan hanya perpindahan relatif yang diminta. Kemudian jalankan matriks interaksi:

  • drag mouse, drag sentuh, kontrol khusus klik, dan drop di luar list;
  • pemindahan khusus keyboard, commit, aksi batas, dan pembatalan dengan Escape;
  • fokus tetap berada pada handle item yang dipindahkan setelah setiap render dan rollback;
  • VoiceOver dengan Safari dan NVDA dengan Firefox atau Chrome mengumumkan item, instruksi, posisi baru, pembatalan, dan kegagalan penyimpanan sebanyak satu kali;
  • zoom 200%, forced colors, kontras tinggi, reduced motion, label panjang, scrolling, dan tata letak RTL;
  • keberhasilan yang tertunda, reordering permintaan, kegagalan offline, retry, dan konflik versi.

Alat aksesibilitas otomatis dapat mendeteksi nama yang hilang, atribut yang tidak valid, dan beberapa masalah focusability. Alat tersebut tidak dapat menilai apakah model keyboard mudah ditemukan, apakah urutan yang diucapkan koheren, atau apakah pembaca layar sentuh dapat menyelesaikan tugas. Hal-hal tersebut memerlukan pengujian manual.

Contoh Jawaban Berkualitas Tinggi

“Saya akan menjaga identitas item independen dari posisi array. Reducer menerima ID item dan indeks akhir, mengembalikan permutasi baru, dan merupakan satu-satunya kode yang diizinkan untuk mengubah urutan. Pointer drag, tombol Move up atau Move down, dan mode keyboard drag apa pun semuanya mendispatch perintah yang sama tersebut.

Baseline semantiknya adalah ordered list dengan tombol native. Setiap baris memiliki reorder handle yang eksplisit dan kontrol pemindahan yang terlihat dengan accessible name yang menyertakan label item. Kontrol pemindahan ini penting karena dukungan keyboard dan alternatif klik-atau-ketuk untuk dragging adalah persyaratan yang terpisah. Saya tidak akan bergantung pada aria-grabbed atau aria-dropeffect; keduanya sudah usang.

Ketika penyusunan ulang dimulai, saya menyimpan urutan asli dan ID item yang aktif. Hit testing pointer atau tombol panah hanya memperbarui posisi kandidat. Commit mengirimkan satu penyimpanan dan mengumumkan, misalnya, ‘Moved Review to position 2 of 5.’ Escape, pointercancel, drop yang tidak valid, atau aturan kegagalan penyimpanan yang dipilih akan memulihkan snapshot. React key yang stabil menjaga handle yang sama tetap fokus setelah urutan DOM berubah.

Untuk pengurutan dengan pointer, saya akan menggunakan handle khusus, ambang batas pergerakan, pointer capture, indikator penyisipan yang jelas, perilaku auto-scroll, dan pembersihan pembatalan. Jika sentuhan, nested scrolling, dan penanganan tabrakan lintas browser diperlukan, saya akan memilih library yang teruji hanya setelah memeriksa perilaku keyboard dan screen-reader-nya; demo mouse saja tidak cukup.

Saya akan melakukan unit-test pada invarian permutasi dan kemudian menyelesaikan tugas secara manual menggunakan mouse, sentuhan, keyboard, kontrol khusus klik, VoiceOver, dan NVDA. Saya juga akan menguji fokus, Escape, drop di luar, reduced motion, forced colors, penyimpanan yang tertunda, respons yang basi (stale), dan konflik versi. Hal tersebut memverifikasi kontrak interaksi yang sebenarnya; pemindaian otomatis saja tidak cukup.”

Kesalahan Umum

  • Menggunakan indeks array sebagai key → penyusunan ulang mengubah identitas, fokus, dan state lokal item → Gunakan ID item yang stabil untuk key dan perintah.
  • Menjadikan drag sebagai satu-satunya interaksi → pengguna yang tidak dapat menahan dan menggerakkan pointer tidak dapat menyusun ulang → Sediakan kontrol klik atau ketuk yang terlihat dengan hasil yang setara.
  • Menambahkan tombol panah dan mengklaim semua persyaratan terpenuhi → kesetaraan keyboard saja mungkin masih belum memiliki metode single-pointer non-dragging → Evaluasi persyaratan keyboard dan dragging-movement secara terpisah.
  • Memasang drag listener ke seluruh baris → tautan, seleksi, checkbox, dan scrolling akan berkonflik dengan reorder → Gunakan handle fokus yang eksplisit.
  • Menerapkan algoritma reorder yang terpisah untuk setiap input → mouse, sentuhan, dan keyboard menghasilkan edge case yang berbeda → Arahkan setiap input ke satu perintah dengan ID stabil.
  • Memutasi array atau DOM secara langsung → state yang dirender dan state yang disimpan menjadi tidak sinkron → Kembalikan permutasi baru dari pemilik state.
  • Menggunakan aria-grabbed dan aria-dropeffect sebagai rencana aksesibilitas → atribut tersebut sudah usang dan tidak menambahkan interaksi apa pun → Gunakan kontrol native, instruksi, state, fokus, dan pengumuman yang teruji.
  • Mengumumkan setiap pergerakan pointer → live region menjadi berisik dan tertunda → Umumkan langkah keyboard yang disengaja dan hasil akhir, bukan piksel pointer.
  • Memindahkan fokus ke awal list setelah penyusunan ulang → pengguna kehilangan posisi mereka → Pertahankan atau pulihkan fokus berdasarkan ID item yang stabil.
  • Menyimpan setiap posisi hover → race condition jaringan dapat menerapkan urutan perantara yang basi → Persistensikan satu pemindahan yang telah di-commit dengan prasyarat versi.
  • Hanya mengandalkan audit otomatis → audit tidak dapat memvalidasi instruksi lisan atau alur input lengkap → Uji dengan keyboard nyata, sentuhan, dan screen reader.

Pertanyaan Lanjutan dan Tanggapan

Lanjutan 1: Apa yang berubah ketika list divirtualisasi (virtualized)?

Jangan memperlakukan baris DOM yang terpasang sebagai koleksi yang lengkap. Simpan perpindahan, jumlah total, dan posisi kandidat dalam model data. Perintah keyboard dapat berpindah berdasarkan indeks logis bahkan ketika tujuannya berada di luar layar, kemudian scroll item yang dipindahkan ke dalam tampilan tanpa kehilangan target fokusnya. Pergerakan pointer memerlukan hit testing yang sadar model (model-aware) dan auto-scroll. Jika library tidak dapat mengekspos semantik koleksi yang akurat atau fokus yang stabil melalui virtualisasi, mode aksesibel non-virtualisasi mungkin menjadi kompromi yang lebih aman pada skala yang diukur.

Lanjutan 2: Bagaimana Anda memindahkan beberapa item yang dipilih?

Simpan ordered set dari ID yang stabil, pertahankan urutan relatifnya, hapus sebagai satu grup, dan masukkan grup tersebut pada satu batas akhir. Pengumuman mencakup jumlah item dan tujuan. Seleksi dan drag handle memerlukan perintah terpisah, terutama ketika Space sudah digunakan untuk beralih seleksi. Tolak seleksi campuran yang berisi item terkunci atau tentukan secara tepat item mana yang tersisa; memindahkan sebagian dari seleksi secara diam-diam bersifat ambigu.

Lanjutan 3: Bagaimana jika dua klien menyusun ulang list yang sama secara konkuren?

Lampirkan versi list atau ETag ke permintaan pemindahan. Server menerima pemindahan hanya jika versinya cocok. Pada konflik, ambil urutan saat ini, pertahankan item dan tujuan yang diinginkan pengguna sebagai konteks, dan tawarkan percobaan ulang atau terapkan aturan merge yang terdokumentasi. Last-write-wins hanya dapat diterima jika produk secara eksplisit menerima hilangnya keputusan pengurutan pengguna lain.

Lanjutan 4: Apakah Anda akan menggunakan native HTML drag and drop, Pointer Events, atau library?

Native drag and drop berguna untuk transfer data desktop dan eksternal, tetapi tidak menyediakan alur kerja keyboard dan teknologi asistif yang lengkap. Pointer Events menawarkan kontrol untuk pengurutan sentuh dan mouse dalam halaman tetapi memerlukan deteksi tabrakan, capture, auto-scroll, dan pembersihan. Library adalah pilihan terbaik ketika perilaku tersebut sudah teruji, asalkan semantik keyboard nyata, pembaca layar sentuh, fokus, dan DOM-nya lolos matriks verifikasi produk. Kontrol pemindahan dan perintah reorder kanonikal tetap ada apa pun pilihan sensornya.

Lanjutan 5: Bagaimana jika penyimpanan gagal setelah pengguna terus bekerja?

Beri tag pada setiap penyusunan ulang yang di-commit dengan urutan klien dan versi server yang menjadi dasarnya. Kegagalan yang terlambat hanya boleh me-rollback state yang berasal dari permintaan tersebut; itu tidak boleh menggantikan urutan terkonfirmasi yang lebih baru dengan snapshot lama. Kebijakan aman yang paling sederhana adalah menserialisasikan penyimpanan, mempertahankan satu urutan tertunda yang eksplisit, dan memblokir commit kedua sambil tetap mengizinkan fokus dan pembacaan. Antrean yang lebih responsif memerlukan rebase dan pengujian konflik sebelum dapat dijustifikasi penggunaannya.

Sumber publik

Pertanyaan terkait