ReactJS #27: Optimasi Kinerja – React.memo dan useCallback


ReactJS #27: Mengoptimalkan Kinerja

React.memo dan useCallback – Biar Aplikasi Ngebut

Halo, pembalap kode! 🏎️

Pernahkah kamu merasa aplikasi React-mu mulai lemot saat digunakan? Bisa jadi karena banyak render ulang yang tidak perlu. Bayangkan seorang guru yang terus mengulang pelajaran yang sama padahal muridnya sudah hafal. Buang waktu, kan? Nah, di React kita punya dua alat ajaib untuk mencegah render ulang yang tidak berguna: React.memo dan useCallback.

💡 Analogi: React.memo itu seperti kertas catatan di pintu kamar. Setiap kali ada yang mau masuk, dia cek dulu apakah orangnya sama. Kalau sama, suruh balik (tidak usah render ulang). useCallback itu seperti fotokopi fungsi yang selalu sama setiap kali, sehingga tidak perlu membuat yang baru.

🔍 Masalah: Render Ulang Tidak Perlu

Di React, ketika state induk berubah, semua komponen anak ikut render ulang. Padahal mungkin props anak tidak berubah. Ini boros kinerja, terutama jika komponen anak berat (misal menampilkan daftar panjang).

Contoh kasus:

function App() { const [count, setCount] = useState(0); const [name, setName] = useState('Budi'); return ( <div> <button onClick={() => setCount(count + 1)}>Klik {count}</button> <Child name={name} /> </div> ); } function Child({ name }) { console.log('Child render!'); return <p>Nama: {name}</p> }

Setiap kali tombol diklik, count berubah, Child ikut render ulang meskipun props name tidak berubah. Lihat di console, pesan "Child render!" muncul terus.

🛡️ React.memo: Menghindari Render Ulang yang Tidak Perlu

React.memo adalah Higher Order Component yang membungkus komponen fungsi. Ia akan "mengingat" hasil render berdasarkan props. Jika props sama dengan sebelumnya, ia akan memakai hasil lama (skip render).

Perbaiki kode di atas dengan React.memo:

const Child = React.memo(function Child({ name }) { console.log('Child render!'); return <p>Nama: {name}</p> });

Sekarang, saat tombol diklik, console tidak akan mencetak "Child render!" lagi karena props name tidak berubah. Hemat!

⚠️ Ingat: React.memo hanya membandingkan props secara shallow (dangkal). Jika props berupa objek atau fungsi, perbandingan bisa salah. Untuk itu kita butuh useCallback dan useMemo.

🎣 useCallback: Menstabilkan Fungsi

Di React, fungsi dibuat ulang setiap render. Jika fungsi dikirim sebagai props ke komponen anak yang dibungkus React.memo, anak akan tetap render ulang karena fungsi dianggap berbeda (walaupun isinya sama).

Contoh bermasalah:

function App() { const [count, setCount] = useState(0); const [name, setName] = useState('Budi'); const handleClick = () => { console.log('Tombol diklik'); }; return ( <div> <button onClick={() => setCount(count + 1)}>Klik {count}</button> <Child name={name} onClick={handleClick} /> </div> ); } const Child = React.memo(function Child({ name, onClick }) { console.log('Child render!'); return <p onClick={onClick}>Nama: {name}</p> });

Meskipun Child dibungkus memo, ia tetap render ulang karena props onClick dianggap baru setiap render. Solusinya: gunakan useCallback untuk "menstabilkan" fungsi.

const handleClick = useCallback(() => { console.log('Tombol diklik'); }, []); // array kosong => fungsi tidak pernah berubah

Sekarang handleClick hanya dibuat sekali, sehingga props onClick stabil, dan Child tidak render ulang.

🧩 Contoh Lengkap: Daftar Tugas dengan Optimasi

Mari lihat contoh daftar tugas yang dioptimasi:

import { useState, useCallback } from 'react'; function App() { const [todos, setTodos] = useState(['Belajar React', 'Makan Siang']); const [count, setCount] = useState(0); const addTodo = useCallback((newTodo) => { setTodos(prev => [...prev, newTodo]); }, []); // stabil const deleteTodo = useCallback((index) => { setTodos(prev => prev.filter((_, i) => i !== index)); }, []); // stabil return ( <div> <button onClick={() => setCount(c => c + 1)}>Counter: {count}</button> <TodoList todos={todos} onAdd={addTodo} onDelete={deleteTodo} /> </div> ); } const TodoList = React.memo(function TodoList({ todos, onAdd, onDelete }) { console.log('TodoList render'); const [input, setInput] = useState(''); return ( <div> <input value={input} onChange={e => setInput(e.target.value)} /> <button onClick={() => { onAdd(input); setInput(''); }}>Tambah</button> <ul> {todos.map((todo, index) => ( <li key={index}> {todo} <button onClick={() => onDelete(index)}>Hapus</button> </li> ))} </ul> </div> ); });

Sekarang, setiap kali tombol counter diklik, TodoList tidak render ulang karena props-nya stabil (todos tidak berubah, fungsi dari useCallback stabil).

🧪 Kapan Harus Menggunakan?

  • Gunakan React.memo jika komponen sering render dengan props yang sama.
  • Gunakan useCallback untuk fungsi yang dikirim ke komponen anak yang sudah dibungkus memo.
  • Gunakan useMemo (tidak dibahas di sini) untuk nilai hasil kalkulasi berat.

Jangan gunakan secara berlebihan! Optimasi prematur bisa bikin kode rumit. Ukur dulu apakah ada masalah performa.

💡 Aturan praktis: Jika komponen render lebih dari yang seharusnya dan menyebabkan lag, gunakan memo. Jika tidak, biarkan saja. React sudah cukup cepat untuk kebanyakan kasus.

🧪 Latihan: Optimasi Aplikasi Cuaca

Buka aplikasi cuaca dari artikel #14. Identifikasi komponen mana yang mungkin sering render ulang tidak perlu. Coba terapkan React.memo pada komponen tampilan cuaca (WeatherCard) dan gunakan useCallback pada fungsi pencarian yang dikirim ke komponen anak (jika ada).

Petunjuk: Biasanya komponen yang menampilkan data statis (seperti card) bisa dibungkus memo.

🚀 Kesimpulan

  • React.memo mencegah render ulang komponen jika props tidak berubah.
  • useCallback mengembalikan fungsi yang sama di setiap render (jika dependensi tidak berubah).
  • Keduanya membantu mengoptimalkan kinerja dengan mengurangi render ulang tidak perlu.
  • Gunakan dengan bijak, jangan optimasi sebelum ada masalah.

Di artikel selanjutnya kita akan belajar tentang Testing Sederhana dengan React Testing Library. Sampai jumpa!

Lebih baru Lebih lama

نموذج الاتصال