Menu

Hook useMemo di React: Cache untuk Perhitungan Berat

useMemo menyimpan hasil sebuah perhitungan di cache di antara render dan hanya menghitungnya ulang saat dependensinya berubah. Pelajari sintaksnya, cara menjaga object tetap stabil untuk child yang memakai memo, dan kapan useMemo tidak sepadan.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

useMemo menyimpan hasil sebuah perhitungan di cache di antara render. React menjalankan fungsimu di render pertama, menyimpan hasilnya, dan mengembalikan hasil yang sama di render berikutnya sampai salah satu nilai di array dependensi berubah.

Buka Console dan toggle tema beberapa kali: tidak ada yang dicatat, dan toggle terasa instan. Ganti tab dan filtering for active muncul, dengan jeda singkat, karena tab adalah dependensi. Sekarang ganti baris useMemo dengan const visible = filterTodos(todos, tab); lalu toggle tema lagi: setiap klik mencatat log dan tersendat selama 100 ms.

Sintaksnya

const cachedValue = useMemo(calculateValue, dependencies);

const visible = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  • calculateValue adalah fungsi tanpa argumen yang mengembalikan nilai yang ingin kamu simpan di cache. React memanggilnya saat render.
  • dependencies adalah daftar setiap nilai dari komponen yang dibaca fungsi itu: props, state, dan variabel yang dideklarasikan di badan komponen.
  • Di setiap render, React membandingkan setiap dependensi dengan nilainya dari render sebelumnya memakai Object.is. Kalau semuanya cocok, React mengembalikan nilai yang di-cache tanpa memanggil fungsimu. Kalau ada yang berbeda, React memanggil fungsi itu dan menyimpan hasil yang baru.

useMemo adalah hook, jadi mengikuti aturan hook: panggil di tingkat teratas komponen, bukan di dalam perulangan atau kondisi.

Dependensi harus lengkap

Kalau sebuah nilai tertinggal dari array, cache terus menyajikan hasil yang dihitung dari nilai lamanya. Di sini total membaca qty tapi hanya mencantumkan price:

Klik Quantity: nilainya menjadi 2, tapi total tetap 10 dan tidak ada yang dicatat. Klik Price dan total melonjak ke 22, karena baru saat itulah perhitungannya berjalan lagi, sekarang dengan membaca qty yang terbaru. Tambahkan qty ke array dan kedua tombol akan memperbarui total. Aturan lint react-hooks/exhaustive-deps menangkap kesalahan ini di proyek sungguhan.

Kesalahan sebaliknya adalah dependensi yang baru di setiap render, seperti object yang dibuat di badan komponen. Dependensi itu tidak pernah cocok dengan nilai sebelumnya, jadi cache tidak pernah terpakai:

const filters = { tab, query }; // a new object every render
const visible = useMemo(() => applyFilters(todos, filters), [todos, filters]); // recomputes every time

// List the primitive values instead
const visible = useMemo(() => applyFilters(todos, { tab, query }), [todos, tab, query]);

Menjaga object tetap stabil untuk child yang memakai memo

Alasan kedua memakai useMemo tidak ada hubungannya dengan perhitungan yang lambat. Child yang dibungkus memo hanya melewati render-nya saat setiap prop sama dengan sebelumnya. Object literal yang ditulis di badan komponen adalah object baru di setiap render, jadi object itu menggagalkan memo bahkan saat isinya tidak berubah. useMemo mempertahankan object yang sama sampai input-nya berubah.

Ketik di field catatan dan Chart tetap diam, sedangkan Switch unit mencatat satu render. Ganti pemanggilan useMemo dengan const options = { unit, max: unit === 'km' ? 100 : 62 }; biasa, dan setiap ketukan tombol di catatan akan mencatat render Chart.

Hal yang sama berlaku untuk effect yang bergantung pada sebuah object. Kalau sebuah effect mencantumkan options di dependensinya dan options dibuat ulang di setiap render, effect itu berjalan setelah setiap render. useMemo memperbaikinya, tapi perbaikan yang lebih sederhana sering kali adalah membuat object itu di dalam effect dan bergantung pada nilai primitifnya:

useEffect(() => {
    const options = { serverUrl, roomId };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
}, [serverUrl, roomId]);

Kapan tidak memakai useMemo

Sebagian besar perhitungan di sebuah komponen itu ringan: menjumlahkan angka, menggabungkan dua string, memfilter beberapa puluh item. Menyimpannya di cache hampir tidak menghemat apa pun: React tetap menyimpan nilainya dan membandingkan dependensi di setiap render, dan kodenya jadi lebih sulit dibaca.

// No useMemo needed
const fullName = first + ' ' + last;
const doneCount = todos.filter((t) => t.done).length;

Untuk mengetahui apakah sesuatu itu lambat, ukur waktunya. Bungkus perhitungannya dengan console.time('filter') dan console.timeEnd('filter'), jalankan interaksinya, lalu lihat angkanya. Sebagai patokan kasar, pekerjaan yang memakan satu milidetik atau lebih di mesinmu layak dipertimbangkan, dengan mengingat bahwa perangkat penggunamu mungkin lebih lambat. Sebelum memakai useMemo, periksa juga apakah pekerjaan itu memang harus ada di render: data yang tidak pernah berubah bisa dihitung sekali di luar komponen, dan state yang hanya dipakai satu bagian kecil halaman bisa dipindahkan ke komponen yang lebih kecil (lihat rendering).

useMemo vs useEffect untuk nilai turunan

Pola yang umum adalah menghitung sebuah nilai di effect lalu menyimpannya di state:

// Two renders per change, and the first one has stale data
const [visible, setVisible] = useState([]);
useEffect(() => {
    setVisible(filterTodos(todos, tab));
}, [todos, tab]);

// One render, always in sync
const visible = useMemo(() => filterTodos(todos, tab), [todos, tab]);

Versi effect merender sekali dengan visible yang lama, lalu menjalankan effect, mengatur state, dan merender lagi. Itu satu render tambahan, dan di render pertama daftarnya tidak cocok dengan tab-nya. Menghitung saat render dengan useMemo (atau tanpa hook sama sekali, kalau pekerjaannya ringan) memberikan nilai yang benar di render yang sama. Effect dipakai untuk sinkronisasi dengan hal-hal di luar React, bukan untuk menurunkan satu state dari state lain.

Petunjuk performa, bukan jaminan

useMemo memberi tahu React bahwa React boleh memakai ulang sebuah hasil. Hook ini tidak menjanjikan bahwa React pasti akan melakukannya. React menyimpan nilai yang di-cache dalam operasi normal, tapi bisa membuangnya, misalnya saat kamu mengedit file selama development, atau saat sebuah komponen ditangguhkan pada mount pertamanya. Tulis kodenya agar tetap benar kalau perhitungannya berjalan lagi: fungsinya harus murni, tanpa efek samping seperti request, subscription, atau mengubah variabel di luarnya. Efek samping tempatnya di event handler atau effect.

Di development, di bawah StrictMode, React memanggil fungsi perhitunganmu dua kali untuk membantumu menemukan kode yang tidak murni. Pratinjau di sini berjalan seperti build production, jadi kamu akan melihat setiap log filtering for sekali saja.

useMemo dan React Compiler

React Compiler 1.0 adalah build tool yang menganalisis komponenmu dan menambahkan memoisasi secara otomatis, menyimpan perhitungan dan JSX di cache di tempat-tempat yang akan terbantu oleh useMemo, useCallback, dan memo. Di proyek yang memakainya, kamu akan lebih jarang menulisnya secara manual. useMemo manual tetap berfungsi bersama compiler, tetap menjadi alatnya saat compiler belum dipasang, dan akan kamu temui di sebagian besar kode React yang sudah ada.

Pertanyaan yang sering diajukan

Apa fungsi useMemo di React?

useMemo memanggil fungsi perhitunganmu di render pertama, menyimpan hasilnya, dan di render berikutnya mengembalikan hasil yang tersimpan selama setiap nilai di array dependensi sama dengan sebelumnya (dibandingkan dengan Object.is).

Kapan sebaiknya memakai useMemo?

Saat sebuah perhitungan terasa lambat dan input-nya jarang berubah, atau saat kamu butuh object atau array yang referensinya tetap sama di antara render karena child yang memakai memo atau sebuah effect bergantung padanya. Untuk pekerjaan yang ringan, lewati saja.

Apa bedanya useMemo dan useCallback?

useMemo menyimpan nilai yang dikembalikan fungsimu. useCallback menyimpan fungsinya sendiri. useCallback(fn, deps) sama dengan useMemo(() => fn, deps).

Apakah useMemo menjamin nilainya tidak pernah dihitung ulang?

Tidak. useMemo hanyalah petunjuk performa. React bisa membuang nilai yang di-cache dalam beberapa kasus, misalnya di development saat kamu mengedit file, jadi kodemu harus tetap benar kalau perhitungannya berjalan lagi.

Apakah saya masih butuh useMemo dengan React Compiler?

React Compiler bisa menambahkan memoisasi secara otomatis saat build-mu memakainya. Tanpa compiler, atau untuk kasus yang dilewatinya, useMemo manual tetap bekerja dengan cara yang sama dan masih umum di kode yang sudah ada.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai