Bottleneck DOM yang Jarang Dibahas

Bayangkan sebuah dashboard dukungan yang menarik sepuluh ribu entri log. Atau sebuah CRM yang mencoba menampilkan setiap kontak dalam satu tabel yang dapat digulir. Di React, kode untuk membangun ini terlihat cukup tidak berbahaya. Anda melakukan mapping pada sebuah array, mengembalikan beberapa JSX, dan membiarkan framework melakukan tugasnya. Semuanya berjalan lancar di tahap pengembangan dengan seratus baris. Kemudian data produksi masuk, dan halaman tersebut menjadi sangat berat dan lambat.

Browser tidak sedang malas. Ia melakukan tepat seperti yang Anda minta, dan itulah masalahnya. Setiap baris menjadi node DOM. Setiap node diberi gaya, tata letak, dilukis, dan dilacak dalam memori. Saat Anda menggulir, browser menghitung ulang posisi untuk seluruh tree, bukan hanya bagian yang Anda lihat. Event listener menumpuk. Penggunaan memori melonjak. Akhirnya, main thread kewalahan cukup lama sehingga antarmuka berhenti merespons klik, ketukan tombol, atau bahkan guliran itu sendiri. Aplikasi tidak mengalami crash dalam pengertian teknis, tetapi bagi pengguna yang duduk di depannya, pengalamannya tetap rusak.

Ini terjadi karena browser mencoba menahan setiap elemen dalam memori aktif sekaligus. React mungkin efisien dalam membuat deskripsi virtual dari UI Anda, tetapi begitu deskripsi tersebut menjadi node nyata dalam dokumen, biayanya sama dengan HTML yang ditulis secara manual. Tidak ada jalan keluar dalam framework itu sendiri. Anda memerlukan perubahan struktural dalam cara Anda menyuapkan daftar tersebut ke DOM.

Apa Arti Virtualisasi yang Sebenarnya

Virtualisasi adalah perubahan struktural tersebut. Alih-alih meminta React untuk merender seluruh array, Anda hanya merender item yang dapat muat di dalam viewport, ditambah sedikit buffer di atas dan di bawahnya. Saat pengguna menggulir, aplikasi membuang node yang keluar dari pandangan dan membuat instans baru yang masuk dari tepi yang berlawanan. Bagi pengguna, ini tetap terasa seperti satu daftar yang berkelanjutan karena total tinggi yang dapat digulir tetap dipertahankan, biasanya melalui satu elemen kontainer tinggi atau spacer yang dihitung dengan cermat. Item yang terlihat hanyalah sebuah jendela yang bergeser melintasi dataset.

Bayangkan seperti strip film yang berjalan melalui gerbang proyektor. Penonton melihat gerakan yang mulus, tetapi mesin hanya menyinari bingkai yang saat ini berada di posisi. Sisa gulungan film ada pada spool pengumpan dan penggulung, bukan di jalur cahaya. Daftar yang divirtualisasi bekerja dengan cara yang sama. Dataset adalah gulungannya. Viewport adalah gerbangnya.

Ini bukan lazy loading dalam pengertian tradisional. Lazy loading menunda pengambilan data hingga pengguna menggulir mendekatinya. Virtualisasi mengasumsikan Anda sudah memiliki datanya, tetapi Anda selektif tentang bagian mana yang dipromosikan menjadi elemen DOM nyata. Kedua teknik ini dapat bekerja bersama, tetapi mereka menyelesaikan masalah yang berbeda.

Mengapa Perbedaannya Terasa Instan

Manfaatnya muncul di empat tempat, semuanya terkait dengan satu hal yang sama: Anda berhenti membayar untuk apa yang tidak dapat dilihat oleh pengguna.

Waktu pemuatan awal yang lebih cepat. Saat browser membuka halaman, ia melukis mungkin lima belas baris, bukan lima belas ribu baris. First meaningful paint tiba lebih cepat. Time-to-interactive turun karena mesin JavaScript menghabiskan lebih sedikit waktu untuk membuat node dan menempelkannya ke dokumen.

Penggunaan memori yang lebih rendah. Sebuah node DOM adalah objek yang mahal. Masing-masing membawa referensi ke aturan gaya, metrik tata letak, dan pengikatan event. Kurangi jumlah node aktif menjadi beberapa lusin, dan jejak memori akan menyusut drastis. Pada perangkat kelas bawah atau sesi yang lama, hal ini saja dapat mencegah tab dihentikan oleh sistem operasi.

Performa pengguliran yang mulus. Dengan lebih sedikit node dalam tree, browser menghabiskan lebih sedikit waktu dalam fase layout dan paint selama peristiwa gulir. Thread compositor dapat menangani pergerakan tanpa terus-menerus menghitung ulang geometri konten yang tersembunyi. Hasilnya adalah pengguliran yang tetap mendekati refresh rate monitor.

Frame rate yang stabil. Karena main thread tidak lagi tenggelam dalam pekerjaan layout, ada ruang sisa untuk aktivitas lain. Animasi tetap lancar. Respons jaringan dapat diproses. UI tidak membeku saat data baru tiba karena jalur render bukan lagi sebuah bottleneck.

Melakukan Implementasi dengan Benar

Dalam ekosistem React, library seperti react-window dan react-virtualized yang lebih berat menyediakan mekanisme untuk pola ini. Ide intinya konsisten: Anda mendefinisikan item renderer, memasukkan jumlah total item, dan library tersebut mengelola perhitungan windowing. Namun, detail-detail kecil sering kali membuat orang terkecoh.

Pertama, kontainer memerlukan tinggi yang ditentukan. Jika daftar berada di dalam induk yang membesar untuk menyesuaikan isinya, virtualisasi tidak dapat menghitung item mana yang terlihat karena tidak ada batas viewport. Anda harus mengunci daftar ke dalam tinggi tetap atau kontainer flex dengan batasan yang diketahui.

Kedua, ukuran item sangatlah penting. Baris dengan tinggi tetap adalah kasus yang paling sederhana. Library tersebut mengalikan tinggi baris dengan indeks dan mengetahui dengan tepat di mana harus memposisikan setiap elemen. Konten dengan tinggi variabel, seperti pesan chat dengan gambar tersemat atau utas komentar, memaksa library untuk melakukan pengukuran setelah mount dan menyesuaikannya secara langsung. Langkah pengukuran tersebut dapat menyebabkan jitter saat scroll jika terjadi terlalu lambat. Jika data Anda memungkinkan, terapkan tinggi yang seragam atau tinggi minimum. Jika tidak, gunakan virtualizer dengan tinggi variabel dan terima kompleksitas tambahannya.

Ketiga, overscanning adalah teman Anda. Merender tepat apa yang pas di layar akan menghasilkan strip putih kosong saat pengguna melakukan scroll dengan cepat. Sebagian besar library memungkinkan Anda merender beberapa item ekstra di atas dan di bawah area yang terlihat (fold). Dua atau tiga baris overscan biasanya sudah cukup untuk menyembunyikan celah tanpa membuat DOM membengkak kembali.

Keempat, jangan abaikan prop key. Dalam daftar yang divirtualisasi, item menggunakan kembali node DOM saat Anda melakukan scroll. Key yang stabil mencegah React menebak secara salah selama proses rekonsiliasi dan menghancurkan state di dalam komponen baris. Jika baris daftar Anda berisi input, toggle, atau bagian yang dapat diperluas, key yang buruk akan merusak state UI dengan cara yang tampak seperti bug pada lapisan data Anda, padahal sebenarnya itu adalah kesalahan rendering.

Satu jebakan halus adalah fitur "find-in-page" pada browser. Karena item yang tersembunyi tidak ada di dalam DOM, kotak pencarian browser tidak akan melihatnya. Jika pengguna Anda mengandalkan Ctrl+F untuk menemukan teks di dalam daftar yang besar, Anda perlu membangun pencarian kustom yang beroperasi pada dataset, bukan pada dokumen. Screen reader juga dapat kehilangan konteks jika semantik daftar tidak ditangani dengan hati-hati, jadi ujilah dengan teknologi asistif dan pertimbangkan untuk menambahkan pengumuman live region untuk pemuatan dinamis.

Kapan Anda Harus Melewatkannya

Virtualisasi tidaklah gratis. Ia menambah beban dependensi, matematika koordinat, dan overhead batasan. Jika daftar Anda hanya berjumlah lima puluh atau seratus item, browser dapat menanganinya tanpa bantuan. Render saja semuanya dan lanjutkan. Hal yang sama berlaku jika item daftar Anda masing-masing sangat kompleks. Virtualisasi menyelamatkan Anda dari ribuan node, tetapi tidak dapat menyelamatkan Anda dari satu node yang berisi elemen grafik atau video yang masif. Perbaiki pembengkakan item terlebih dahulu.

Hindari juga virtualisasi saat daftar tidak dapat di-scroll. Jika Anda menggunakan paginasi dengan tombol berikutnya dan sebelumnya dan hanya menampilkan dua puluh item per halaman, tidak ada yang perlu di-window. Teknik ini hanya memberikan manfaat ketika pengguna mengharapkan untuk melakukan scroll melalui urutan yang besar dan berkesinambungan.

Intisari Sebenarnya

Virtualisasi lebih merupakan sebuah pola pikir daripada sekadar pilihan library. Ia memaksa Anda untuk mengakui bahwa DOM adalah sumber daya yang terbatas, bukan kanvas yang tak terbatas. Sebelum Anda menambahkannya, buka Chrome DevTools, rekam profil performa, dan pastikan bahwa waktu layout atau paint adalah penyebab sebenarnya. Setelah Anda tahu bahwa DOM adalah hambatan utamanya, berkomitmenlah pada batasan tersebut. Kunci tinggi Anda, perhatikan key Anda, gunakan overscan secukupnya, dan uji aksesibilitas Anda. Jika dilakukan dengan benar, daftar yang divirtualisasi mengubah dinding data yang tidak dapat digunakan menjadi sesuatu yang terasa seringan tampilan scroll asli. Browser berhenti melawan, pengguna Anda berhenti menunggu, dan aplikasi akhirnya berperilaku seperti antarmuka cepat yang Anda maksudkan untuk dibangun.