REACT WEB-PERFORMANCE OPTIMIZATION FRONTEND JAVASCRIPT HOOKS MEMOIZATION DEVELOPER-EXPERIENCE BEST-PRACTICES MODERN-WEB

Optimasi Re-render React: useMemo, useCallback, dan React.memo dalam Praktik

⏱️ 10 menit baca
👨‍💻

Optimasi Re-render React: useMemo, useCallback, dan React.memo dalam Praktik

1. Pendahuluan

Pernahkah Anda merasa aplikasi React yang Anda bangun mulai terasa lambat atau kurang responsif seiring bertambahnya fitur? Seringkali, akar masalahnya bukan pada kode yang buruk secara fundamental, melainkan pada re-render yang tidak perlu. Re-render adalah proses ketika React “menggambar ulang” komponen Anda ke DOM. Meskipun React sangat efisien, re-render yang berlebihan, terutama pada komponen yang kompleks atau sering berubah, bisa memakan banyak sumber daya dan menurunkan performa aplikasi.

Bayangkan Anda memiliki sebuah rumah yang catnya perlu diperbarui. Setiap kali ada perubahan kecil (misalnya, ada noda di dinding), Anda tidak perlu mengecat ulang seluruh rumah, bukan? Cukup bagian yang bernoda saja. Nah, di React, terkadang komponen kita “mengecat ulang” dirinya sendiri dan semua anak-anaknya (child components) padahal tidak ada perubahan yang signifikan. Inilah yang disebut re-render yang tidak perlu.

Artikel ini akan membawa Anda menyelami tiga senjata utama dalam toolbox React untuk mengoptimalkan re-render: useMemo, useCallback, dan React.memo. Kita akan membahas kapan dan bagaimana menggunakannya secara efektif, serta kapan sebaiknya tidak menggunakannya untuk menghindari premature optimization. Mari kita bangun aplikasi React yang lebih cepat dan mulus!

2. Memahami Re-render di React: Mengapa dan Kapan Terjadi?

Sebelum kita masuk ke solusi, penting untuk memahami masalahnya. Sebuah komponen React akan mengalami re-render ketika salah satu kondisi berikut terpenuhi:

  1. State-nya berubah: Ini adalah alasan paling umum. Ketika useState atau useReducer mengubah nilai state, komponen akan re-render.
  2. Props-nya berubah: Jika komponen menerima props dari parent-nya, dan nilai props tersebut berubah, komponen akan re-render.
  3. Parent-nya re-render: Ini adalah poin krusial. Jika komponen parent re-render, secara default, semua child components-nya juga akan re-render, terlepas apakah props yang mereka terima benar-benar berubah atau tidak.
  4. Context yang digunakannya berubah: Jika komponen menggunakan React Context, dan nilai context tersebut berubah, komponen akan re-render.

Poin ketiga adalah penyebab utama re-render yang tidak perlu. Sebuah komponen Child mungkin menerima props yang sama persis dari Parent, tetapi jika Parent re-render (karena state-nya berubah), Child juga akan re-render. Jika Child ini memiliki logika komputasi yang berat atau memiliki banyak child components lagi, efeknya bisa berantai dan signifikan.

🎯 Tujuan Optimasi: Mencegah komponen re-render jika props atau state yang relevan dengannya tidak berubah.

3. useMemo: Mengoptimalkan Nilai yang Dikomputasi

useMemo adalah hook yang digunakan untuk memoize (menghafal) hasil komputasi suatu nilai. Ini berarti React hanya akan menghitung ulang nilai tersebut jika salah satu dependencies (ketergantungan) yang Anda tentukan berubah.

Kapan menggunakan useMemo?

Contoh Praktis:

import React, { useState, useMemo } from 'react';

function ItemList({ items, filterText }) {
  // Ini adalah komputasi yang mungkin mahal jika 'items' sangat besar
  // dan 'filterText' sering berubah.
  const filteredItems = useMemo(() => {
    console.log('Menghitung ulang item yang difilter...');
    return items.filter(item =>
      item.name.toLowerCase().includes(filterText.toLowerCase())
    );
  }, [items, filterText]); // ✅ Hanya re-calculate jika items atau filterText berubah

  return (
    <div>
      <h3>Daftar Item</h3>
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

function App() {
  const [search, setSearch] = useState('');
  const [data] = useState([
    { id: 1, name: 'Apel' },
    { id: 2, name: 'Jeruk' },
    { id: 3, name: 'Pisang' },
    { id: 4, name: 'Mangga' },
  ]);

  return (
    <div>
      <input
        type="text"
        placeholder="Cari item..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />
      <ItemList items={data} filterText={search} />
    </div>
  );
}

export default App;

Dalam contoh di atas, filteredItems hanya akan dihitung ulang jika items atau filterText berubah. Jika ada state lain di App yang berubah dan menyebabkan App re-render (misalnya, sebuah counter), ItemList akan re-render, tetapi fungsi filter tidak akan dijalankan lagi karena items dan filterText belum berubah.

⚠️ Penting: useMemo menerima sebuah fungsi (disebut “memoized function”) dan array dependencies. Fungsi ini akan dijalankan saat render pertama dan setiap kali salah satu dependency berubah.

4. useCallback: Mengoptimalkan Fungsi (Event Handlers)

Mirip dengan useMemo, useCallback digunakan untuk memoize sebuah fungsi. Ini sangat berguna ketika Anda meneruskan fungsi (seperti event handler) sebagai props ke child component.

Kapan menggunakan useCallback?

Contoh Praktis:

import React, { useState, useCallback, memo } from 'react';

// ✅ Komponen Child yang di-memoize
const ButtonSaya = memo(({ onClick, label }) => {
  console.log(`ButtonSaya "${label}" di-render`);
  return <button onClick={onClick}>{label}</button>;
});

function App() {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('Halo');

  // ❌ Tanpa useCallback, handleIncrement akan dibuat ulang setiap App re-render.
  // Ini akan menyebabkan ButtonSaya re-render meskipun props lain tidak berubah.
  // const handleIncrement = () => {
  //   setCount(prevCount => prevCount + 1);
  // };

  // ✅ Dengan useCallback, handleIncrement hanya dibuat ulang jika 'count' berubah
  // (atau jika tidak ada dependencies, maka hanya sekali saat mount).
  // Dalam kasus ini, kita tidak ingin setCount membuat re-render Button,
  // jadi kita bisa pakai functional update untuk setCount.
  const handleIncrement = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []); // Dependencies kosong berarti fungsi ini hanya dibuat sekali

  const handleChangeMessage = useCallback(() => {
    setMessage('Pesan baru!');
  }, []); // Dependencies kosong

  console.log('App di-render');

  return (
    <div>
      <h1>Counter: {count}</h1>
      <p>Pesan: {message}</p>
      <ButtonSaya onClick={handleIncrement} label="Tambah Counter" />
      <ButtonSaya onClick={handleChangeMessage} label="Ubah Pesan" />
      <button onClick={() => console.log('Klik saya!')}>Log Saja</button>
    </div>
  );
}

export default App;

Dalam contoh ini, ButtonSaya di-memoize dengan React.memo. Jika handleIncrement dan handleChangeMessage tidak di-useCallback, setiap kali App re-render (misalnya karena count berubah), fungsi-fungsi tersebut akan dibuat ulang. Ini akan menyebabkan ButtonSaya re-render karena props onClick dianggap berubah, padahal ButtonSaya tidak peduli dengan perubahan count atau message. Dengan useCallback, fungsi handleIncrement dan handleChangeMessage hanya dibuat sekali dan tetap sama di setiap re-render App, sehingga ButtonSaya tidak perlu re-render.

💡 Tips: Untuk setCount di dalam useCallback, gunakan functional update (setCount(prevCount => prevCount + 1)) agar tidak perlu memasukkan count ke dalam array dependencies useCallback. Ini membuat fungsi tersebut tetap stabil dan tidak sering dibuat ulang.

5. React.memo: Mengoptimalkan Komponen secara Keseluruhan

React.memo adalah Higher-Order Component (HOC) yang membungkus komponen fungsional Anda. Ini adalah cara React untuk melakukan shallow comparison pada props komponen. Jika props yang diterima komponen tidak berubah, React.memo akan mencegah komponen tersebut untuk re-render.

Kapan menggunakan React.memo?

Contoh Praktis:

Lihat kembali contoh ButtonSaya di bagian useCallback. Kita membungkusnya dengan memo:

// ✅ Komponen Child yang di-memoize
const ButtonSaya = memo(({ onClick, label }) => {
  console.log(`ButtonSaya "${label}" di-render`);
  return <button onClick={onClick}>{label}</button>;
});

Tanpa memo, ButtonSaya akan re-render setiap kali App re-render. Dengan memo, ButtonSaya hanya akan re-render jika props onClick atau label benar-benar berubah (secara shallow comparison). Karena onClick sekarang di-useCallback dengan dependencies kosong, ButtonSaya tidak akan re-render kecuali label berubah.

Custom Comparison dengan arePropsEqual

Secara default, React.memo melakukan shallow comparison pada props. Jika Anda membutuhkan logika perbandingan yang lebih kompleks, Anda bisa menyediakan fungsi arePropsEqual sebagai argumen kedua:

const MyComplexComponent = memo((props) => {
  // ... render logic
}, (prevProps, nextProps) => {
  // ✅ Kembalikan true jika props sama (tidak perlu re-render)
  // ✅ Kembalikan false jika props berbeda (perlu re-render)
  return prevProps.value.id === nextProps.value.id &&
         prevProps.data.length === nextProps.data.length;
});

Ini sangat berguna untuk props yang berupa objek atau array yang referensinya selalu baru, tetapi Anda hanya peduli pada beberapa properti di dalamnya.

6. Kapan Tidak Menggunakan Optimasi Ini (Hindari Premature Optimization)

Meskipun useMemo, useCallback, dan React.memo adalah alat yang ampuh, penggunaannya juga memiliki biaya:

Kapan sebaiknya TIDAK menggunakannya:

📌 Aturan Emas: Jangan optimasi sebelum Anda tahu ada masalah. Gunakan React DevTools Profiler untuk mengidentifikasi komponen mana yang paling sering re-render atau paling lambat. Baru setelah itu, terapkan optimasi secara selektif.

Kesimpulan

useMemo, useCallback, dan React.memo adalah teknik memoization yang sangat berharga dalam pengembangan aplikasi React. Mereka memungkinkan Anda untuk mengontrol kapan komponen re-render, mengurangi beban kerja React, dan pada akhirnya meningkatkan performa dan responsivitas aplikasi Anda.

Ingatlah untuk selalu mengoptimasi dengan bijak. Gunakan alat profiler untuk menemukan bottleneck nyata, dan terapkan teknik ini secara strategis, bukan pada setiap komponen. Dengan pendekatan yang tepat, Anda bisa membangun aplikasi React yang tidak hanya fungsional, tetapi juga cepat dan menyenangkan bagi pengguna!

🔗 Baca Juga