Reactjs Performance Profiling Usetransition Usedeferredvalue Memoization Heuristics
Definisi
Performance profiling di React.js adalah proses mengukur bagian mana yang lambat, kenapa lambat, dan optimisasi apa yang benar-benar memberi dampak. Fokusnya bukan membuat semua komponen “diproteksi” dengan memo, tetapi menemukan sumber biaya render yang paling mahal lalu memperbaikinya secara selektif.
Topik ini biasanya mencakup:
- profiling render untuk melihat komponen yang sering atau lama dirender
- useTransition untuk menurunkan prioritas update yang tidak harus terasa instan
- useDeferredValue untuk menunda konsumsi nilai yang mahal agar UI tetap responsif
- memoization heuristics untuk menentukan kapan
React.memo,useMemo, danuseCallbacklayak dipakai - reducing render cost untuk menekan jumlah kerja per render, bukan sekadar mengurangi jumlah render
Kapan optimisasi dilakukan
Optimisasi performa dilakukan setelah ada bukti biaya nyata, bukan karena intuisi semata.
Pakai optimisasi kalau ada tanda seperti:
- input terasa lag saat mengetik
- scroll atau interaksi terasa patah-patah
- render memakan waktu panjang di komponen tertentu
- daftar besar sering rerender tanpa perubahan yang relevan
- ada efek berantai dari state yang terlalu tinggi di tree
- data derivatif dihitung ulang terlalu sering
Jangan mulai dari optimisasi kalau:
- aplikasi belum terasa lambat
- belum ada baseline pengukuran
- masalahnya sebenarnya di network, data fetching, atau layout thrashing
- komponen masih kecil dan sederhana
Aturan praktisnya begini:
- ukur dulu
- temukan bottleneck
- ubah satu hal
- ukur ulang
- simpan hanya perubahan yang terbukti membantu
Cara kerja profiling
Profiling dipakai untuk menjawab tiga pertanyaan:
- Apa yang dirender?
- Berapa lama waktu render-nya?
- Apa pemicunya?
Di React DevTools Profiler, kamu biasanya melihat:
- commit yang terjadi saat UI berubah
- komponen yang ikut rerender
- durasi render dan commit
- apakah update berasal dari props, state, atau context
Yang dicari bukan sekadar “komponen ini rerender”, karena rerender itu normal. Yang dicari adalah:
- rerender yang mahal
- rerender yang tidak perlu
- rerender yang muncul berulang saat interaksi sederhana
Alur kerja yang sehat
useTransition
useTransition dipakai untuk menandai update yang tidak harus terasa instan. React akan memberi prioritas lebih rendah pada update tersebut, sehingga interaksi utama tetap responsif.
Gunakan saat:
- ada input yang harus tetap cepat meskipun update berikutnya mahal
- UI perlu menampilkan hasil filter, navigasi internal, atau perubahan view yang berat
- ada transisi state yang bisa ditunda tanpa merusak pengalaman pengguna
Contoh pola umum:
import { useState, useTransition } from 'react';
function SearchPage({ items }) {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e) {
const nextValue = e.target.value;
setQuery(nextValue);
startTransition(() => {
// update yang lebih berat, misalnya filtering atau perubahan route state
});
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending ? <p>Memproses...</p> : null}
</>
);
}Kapan tepat
- saat pengguna harus tetap bisa mengetik atau klik tanpa delay
- saat update berat bisa tampil sedikit terlambat
- saat kamu ingin memisahkan urgent update dari non-urgent update
Batasan penting
useTransition bukan alat untuk mempercepat komputasi. Dia hanya mengatur prioritas. Kalau bottleneck-nya adalah algoritma mahal, kamu tetap perlu memperbaiki komputasi, caching, atau struktur data.
useDeferredValue
useDeferredValue dipakai ketika kamu ingin mengonsumsi nilai terbaru dengan sedikit penundaan agar bagian UI yang mahal tidak ikut memblokir interaksi utama.
Gunakan saat:
- satu nilai input dipakai untuk komponen mahal, seperti daftar besar atau visualisasi kompleks
- kamu ingin input tetap responsif sementara hasil turunan menyusul
- ada perbedaan antara nilai yang sedang diketik dan nilai yang dipakai untuk render berat
Pola umumnya:
import { useDeferredValue, useMemo, useState } from 'react';
function ProductSearch({ products }) {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const filtered = useMemo(() => {
return products.filter((product) =>
product.name.toLowerCase().includes(deferredQuery.toLowerCase())
);
}, [products, deferredQuery]);
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ProductList products={filtered} />
</>
);
}Kapan tepat
- saat update ke child tree mahal dan tidak perlu mengikuti input secara instan
- saat kamu ingin menjaga typing tetap halus
- saat sebagian UI boleh sedikit tertinggal dari state utama
Batasan penting
useDeferredValue bukan debounce. Ia tidak mengatur delay waktu secara eksplisit, dan bukan pengganti throttling/debouncing untuk kasus jaringan atau event burst. Ia lebih cocok untuk menunda render turunan, bukan menunda sumber event.
Memoization heuristics
Memoization berguna, tapi tidak gratis. Setiap memo punya biaya:
- perbandingan dependency
- penyimpanan nilai cache
- kompleksitas mental saat debugging
Karena itu, heuristiknya harus jelas.
Pakai memoization kalau:
- kalkulasi derivatif mahal dan inputnya relatif stabil
- referensi function atau object memicu rerender yang tidak perlu
- komponen child berat dan props-nya sering sama
- kamu sudah melihat bottleneck nyata di profiler
Jangan pakai memoization kalau:
- komponen ringan dan rerender-nya murah
- nilai cached jarang dipakai ulang
- optimisasi menambah kompleksitas tanpa bukti manfaat
- dependency list sulit dijaga sehingga rawan stale value
Heuristik yang aman
- Mulai dari pembatasan scope state, bukan memo
- Ukur cost render, baru pilih memo yang tepat
- Stabilkan referensi hanya jika referensi itu benar-benar memicu rerender mahal
- Memoisasi data derivatif, bukan semua hal
- Pertahankan kode mudah dibaca; performa yang sulit dipahami sering kalah oleh maintenance cost
Contoh yang masuk akal:
const visibleItems = useMemo(() => {
return expensiveFilter(items, searchTerm);
}, [items, searchTerm]);Contoh yang sering berlebihan:
const onClick = useCallback(() => {
setOpen(true);
}, []);Kalau callback itu tidak memicu masalah nyata, useCallback sering tidak menambah nilai.
Reducing render cost
Reducing render cost berarti menurunkan kerja yang dilakukan React saat render, bukan cuma mengurangi jumlah render.
Cara utamanya:
1. Turunkan scope state
Simpan state sedekat mungkin dengan komponen yang benar-benar memakainya. State yang terlalu tinggi membuat banyak subtree ikut rerender.
2. Pecah komponen yang berat
Komponen besar dengan banyak tanggung jawab cenderung mahal untuk dirender. Pisahkan bagian yang stabil dari bagian yang sering berubah.
3. Kurangi kerja di render path
Hindari:
- transformasi data berat langsung di body komponen
- pembuatan object/array/function baru tanpa alasan
- render tree yang terlalu dalam untuk kebutuhan sederhana
4. Stabilkan props yang sensitif
Jika child memakai React.memo, props yang berubah referensi terus-menerus bisa membatalkan manfaat memo.
5. Virtualisasi list bila datanya besar
Untuk daftar panjang, render hanya item yang terlihat. Ini sering lebih efektif daripada memoisasi per item.
6. Pilih primitive sebelum optimization trick
Kadang perbaikan terbaik justru sederhana:
- simpan hasil komputasi di layer yang lebih tepat
- pindahkan logic berat ke server atau worker
- cache hasil yang benar-benar repeatable
Jebakan umum saat over-optimizing
Over-optimizing sering terlihat seperti kode yang “lebih canggih” tetapi justru lebih sulit dirawat.
Jebakan yang sering muncul
- menambah
React.memoke semua komponen tanpa profiling - memakai
useMemountuk nilai yang murah dihitung ulang - memakai
useCallbackhanya demi konsistensi gaya, bukan kebutuhan performa - menganggap semua rerender itu buruk
- mengunci code path sampai debugging jadi sulit
- lupa bahwa bug stale closure bisa muncul dari memoization yang salah
- mengoptimalkan sebelum tahu bottleneck sebenarnya
Risiko teknis
- memory footprint naik karena cache
- dependency array rawan salah
- logic jadi lebih sulit dibaca
- optimisasi lokal menutup masalah arsitektural yang lebih besar
Aturan anti-over-optimizing
- jangan optimisasi tanpa baseline
- jangan simpan cache kalau tidak ada reuse
- jangan stabilkan referensi kalau tidak ada konsumen yang peduli
- jangan korbankan kejelasan kode untuk gain kecil yang belum terukur
Contoh strategi baca profiler ke keputusan teknis
- Profiling menunjukkan daftar besar rerender saat mengetik.
- Cek apakah state query berada terlalu tinggi.
- Pindahkan state lokal atau defer nilai input dengan
useDeferredValue. - Jika filtering masih berat, memoize hasil filter dan kurangi kerja render item.
- Kalau input utama tetap perlu responsif sementara hasil daftar boleh menyusul, pertimbangkan
useTransition. - Profiling ulang untuk memastikan commit time turun dan interaksi terasa lebih halus.
Catatan implementasi
- Profiling React harus dilakukan pada skenario nyata, bukan hanya toy example.
- Perubahan performa yang baik biasanya terlihat dari kombinasi: commit lebih pendek, interaksi lebih responsif, dan logika lebih jelas.
- Optimisasi yang benar biasanya mengurangi kerja atau menunda kerja, bukan sekadar memindahkan kerja ke tempat lain.
- Kalau masalah performa berasal dari data besar, pertimbangkan juga sumber data, bentuk state, dan ukuran subtree, bukan hanya hook optimisasi.
Kesalahan umum
- memulai dari memoization tanpa profiling
- memakai
useTransitionuntuk semua update - menganggap
useDeferredValuesama dengan debounce - menilai performa hanya dari rerender count, bukan durasi dan pengalaman pengguna
- memperbaiki komponen kecil sementara bottleneck besar ada di parent atau list rendering