Minggu, 19 November 2023

Optimalisasi React dengan useCallback: Tutorial Lengkap untuk Peningkatan Performa Aplikasi Anda

Dalam artikel ini, kita akan belajar tentang useCallback di React untuk meningkatkan performa aplikasi. Dapatkan tips dan contoh praktis untuk mengoptimalkan re-render dan kode efisien.

React
React Hooks

useCallback 101

useCallback dalam React adalah hook yang bertujuan untuk mengoptimalkan kinerja dengan mengurangi jumlah re-render yang tidak perlu pada komponen lain di dalamnya. Pada dasarnya, dalam aplikasi React, setiap kali sebuah komponen di-render, semua fungsi di dalam komponen tersebut juga dibuat ulang. Ini berarti, meskipun isi dari fungsi tersebut tetap sama antara satu render ke render berikutnya, React menganggapnya sebagai fungsi baru pada setiap render.

Ketika fungsi ini diteruskan ke komponen lain yang ada di dalamnya, komponen tersebut bisa mengalami re-render yang tidak perlu karena dari sudut pandangnya, ia menerima prop yang 'baru' pada setiap render komponen induk, meski secara fungsional fungsi tersebut tidak berubah.

useCallback membantu dalam situasi ini dengan memungkinkan kita untuk 'mengingat' fungsi yang sama di antara render. Ini dicapai dengan memberikan fungsi tersebut pada useCallback bersama dengan daftar dependensi. Jadi, fungsi yang dibungkus dengan useCallback hanya akan dibuat ulang jika ada perubahan pada nilai di dalam daftar dependensinya.

Berikut adalah contoh penggunaanya:

const memoizedFunction = useCallback(() => {
  //
}, [dependencies]);

Di sini, memoizedFunction akan mempertahankan identitasnya yang sama selama nilai dalam dependencies tidak berubah.

Penggunaan useCallback efektif dalam kasus di mana re-render yang tidak perlu pada komponen lain bisa mempengaruhi performa, atau ketika dibutuhkan referensi yang stabil untuk fungsi tersebut antar render. Namun, penggunaannya harus bijaksana karena kadang-kadang overhead dari proses memoization bisa lebih berat daripada manfaat yang diperoleh, terutama jika fungsi tersebut tidak sering berubah atau tidak berdampak signifikan terhadap performa komponen.

Kapan sebaiknya menggunakan useCallback?

useCallback sebaiknya digunakan dalam kondisi-kondisi tertentu di mana optimasi performa menjadi penting. Salah satu skenario utama adalah ketika fungsi yang didefinisikan di dalam sebuah komponen perlu di-pass sebagai props ke komponen lain yang ada di dalamnya. Terutama jika komponen tersebut dioptimalkan dengan React.memo, penggunaan useCallback akan mencegah komponen anak tersebut dari re-render yang tidak perlu karena fungsi yang diterima sebagai props akan mempertahankan referensinya yang sama di antara render, kecuali jika dependensi yang ditentukan berubah.

Situasi lain di mana useCallback menjadi berguna adalah ketika sebuah fungsi digunakan sebagai dependensi di useEffect atau hook lainnya. Tanpa useCallback, fungsi tersebut akan dibuat ulang pada setiap render, memicu kembali pemanggilan useEffect atau hook lainnya yang tidak perlu. Ini berguna untuk menghindari efek samping yang tidak diinginkan dan menjaga logika aplikasi tetap konsisten.

Pada dasarnya, useCallback membantu dalam menjaga stabilitas referensi fungsi di antara render, yang penting dalam kasus di mana pembuatan ulang fungsi yang sering dapat mempengaruhi performa atau perilaku komponen.

Hindari Jika Memungkinkan

Menghindari pembuatan ulang fungsi yang tidak perlu dalam React, seperti yang dilakukan melalui useCallback, penting karena beberapa alasan:

  1. Performa: Di aplikasi React yang besar dan kompleks, setiap bit performa sangat berharga. Fungsi yang dibuat ulang pada setiap render, terutama jika diteruskan ke komponen lain, dapat menyebabkan re-render yang tidak perlu pada komponen tersebut. Ini memperberat proses pembaruan DOM virtual dan dapat mengurangi kecepatan respons aplikasi, terutama pada aplikasi dengan banyak komponen interaktif.

  2. Penghematan Sumber Daya: Pembuatan ulang fungsi yang tidak perlu memakai sumber daya komputasi. Meskipun pembuatan ulang fungsi sederhana mungkin tidak berdampak signifikan, fungsi yang lebih kompleks atau pembuatan ulang yang terjadi di banyak komponen sekaligus bisa membebani sistem.

  3. Efek Samping dalam Komponen Lain: Saat fungsi yang dibuat ulang diteruskan ke komponen lain sebagai props, dan jika komponen tersebut menggunakan React.memo atau memiliki logika yang bergantung pada referensi yang stabil, pembuatan ulang fungsi dapat memicu re-render yang tidak diinginkan. Ini bisa menyebabkan perilaku yang tidak konsisten atau efek samping yang sulit di-debug.

  4. Konsistensi Referensi: Dalam React, konsistensi referensi sangat penting, terutama saat bekerja dengan hook seperti useEffect dan useMemo. Jika fungsi yang menjadi dependensi di dalam hook tersebut berubah identitasnya pada setiap render, maka akan memicu pemanggilan kembali hook tersebut, yang bisa mengakibatkan perilaku tak terduga atau komputasi yang berlebihan.

  5. Pengalaman Pengguna: Akhirnya, semua poin di atas berkontribusi terhadap pengalaman pengguna. Aplikasi yang responsif dan efisien memberikan pengalaman pengguna yang lebih baik. Re-render yang tidak perlu atau keterlambatan dalam pembaruan UI dapat mengganggu alur kerja pengguna dan menurunkan kesan mereka terhadap aplikasi.

Meskipun begitu, penting untuk diingat bahwa useCallback harus digunakan secara bijaksana. Tidak semua fungsi perlu di-memoize, dan penggunaan useCallback secara berlebihan atau tanpa kebutuhan yang jelas bisa menambah kompleksitas tanpa manfaat yang signifikan. Penggunaan useCallback idealnya diinformasikan oleh pemahaman yang baik tentang bagaimana aplikasi Anda beroperasi dan di mana bottleneck performa terjadi.

Penggunaan useCallback

Menggunakan useCallback dalam situasi nyata, terutama dalam konteks pemanggilan API yang 'expensive' atau berat, memang sangat bermanfaat. Berikut ini adalah contoh komponen React yang melakukan pemanggilan API menggunakan axios dan useCallback.

Fetch Content

Agar lebih jelas, saya akan menggunakan endpoint API publik dari JSONPlaceholder, yang merupakan layanan online gratis untuk pengujian dan prototyping dengan data JSON palsu.

DataFetcher.jsx
import React, { useState, useCallback } from 'react';
import axios from 'axios';

export default function DataFetcher() {
    const [data, setData] = useState(null);
    const [userId, setUserId] = useState(1);

    const fetchData = useCallback(async () => {
        try {
            const response = await axios(
                `https://jsonplaceholder.typicode.com/users/${userId}`
            );
            setData(response.data);
        } catch (error) {
            console.error('Error fetching data:', error);
        }
    }, [userId]);

    return (
        <div>
            <input
                type="number"
                value={userId}
                onChange={(e) => setUserId(e.target.value)}
            />
            <button onClick={fetchData}>Load User Data</button>
            <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>
        </div>
    );
}

Dalam contoh di atas, fungsi fetchData dibungkus dalam useCallback dengan userId sebagai dependensinya. Ini berarti bahwa fetchData hanya akan dibuat ulang jika userId berubah, yang membantu mengoptimalkan kinerja terutama dalam kasus-kasus di mana fungsi tersebut melakukan operasi yang berat seperti pemanggilan API.

Search Component

Kali ini, kita akan membuat komponen yang melakukan pencarian data dari API setiap kali pengguna mengetik kata kunci pencarian. Ini adalah contoh yang bagus karena pencarian biasanya merupakan operasi yang 'expensive', dan kita ingin membatasi jumlah pemanggilan API yang tidak perlu. Kita akan menggunakan useCallback untuk mengoptimalkan fungsi pencarian:

Search.jsx
import React, { useCallback, useEffect, useState } from 'react';
import axios from 'axios';

export default function Search() {
    const [searchTerm, setSearchTerm] = useState('');
    const [results, setResults] = useState([]);

    const search = useCallback(async () => {
        if (searchTerm !== '') {
            try {
                const response = await axios(
                    `https://jsonplaceholder.typicode.com/posts?title_like=${searchTerm}`
                );
                setResults(response.data);
            } catch (error) {
                console.error('Error during search:', error);
            }
        }
    }, [searchTerm]);

    useEffect(() => {
        const timerId = setTimeout(() => search(), 500); // Delay 500 ms

        return () => clearTimeout(timerId);
    }, [search]);

    return (
        <div>
            <input
                type="text"
                value={searchTerm}
                onChange={(e) => setSearchTerm(e.target.value)}
                placeholder="Type to search..."
            />
            {results.length > 0 && (
                <ul>
                    {results.map((result, index) => (
                        <li key={index}>{result?.title}</li>
                    ))}
                </ul>
            )}
        </div>
    );
}

Dalam kode ini, useEffect digunakan untuk menangani logika debounce. Setiap kali search berubah, yang berarti searchTerm berubah, useEffect akan mengatur timeout. Jika searchTerm berubah lagi dalam waktu 500 ms, timeout sebelumnya dibersihkan dan diatur ulang, sehingga pencarian hanya terjadi setelah pengguna berhenti mengetik selama 500 ms. Ini mengurangi jumlah permintaan yang dibuat ke server dan mengoptimalkan kinerja pencarian.

useCallback tidak perlu digunakan jika

Ada skenario tertentu di mana penggunaan useCallback tidak perlu dan bahkan bisa menjadi kontraproduktif. Salah satu contoh umum adalah ketika fungsi yang Anda gunakan tidak diteruskan ke komponen anak atau tidak termasuk dalam dependensi useEffect, useMemo, atau hook lain yang memerlukan referensi stabil. Dalam kasus seperti ini, manfaat memoization dengan useCallback mungkin tidak akan sebanding dengan overhead yang ditambahkannya.

Berikut adalah contoh kode di mana useCallback tidak perlu digunakan:

Counter.jsx
import React, { useState } from 'react';

export default function Counter() {
    const [count, setCount] = useState(0);

    const increment = () => setCount(count + 1);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={increment}>Increment</button>
        </div>
    );
}

Dalam contoh ini, fungsi increment hanya digunakan untuk mengubah state lokal dan tidak diteruskan ke komponen anak atau digunakan sebagai dependensi untuk hook lain. Oleh karena itu, pembuatan ulang fungsi increment pada setiap render tidak akan berdampak negatif pada performa. Penggunaan useCallback di sini akan menambahkan kompleksitas yang tidak perlu dan overhead memori tanpa manfaat performa yang nyata.

Alasan mengapa useCallback tidak perlu dalam situasi ini adalah:

  1. Sederhana dan Tidak Berat: Fungsi increment sangat sederhana dan tidak melakukan komputasi yang berat atau operasi yang mahal.
  2. Tidak Diteruskan ke Komponen Anak: Karena increment tidak diteruskan ke komponen anak atau komponen yang memerlukan referensi stabil, tidak ada risiko re-render yang tidak perlu.
  3. Tidak Digunakan sebagai Dependensi Hook: Fungsi ini tidak digunakan sebagai dependensi dalam useEffect, useMemo, atau hook React lainnya, sehingga pembuatan ulang fungsi ini tidak memicu efek samping tak terduga.

Dalam pengembangan React, memahami kapan harus menggunakan optimasi seperti useCallback adalah kunci. Menghindari penggunaan berlebihan dari hook ini dapat membantu menjaga kode Anda tetap bersih dan efisien.

Kesimpulan

Dalam pengembangan aplikasi React, memahami kapan dan bagaimana menggunakan useCallback merupakan aspek penting untuk mengoptimalkan performa aplikasi. Penggunaannya sangat berguna dalam situasi tertentu seperti mencegah re-render yang tidak perlu pada komponen anak atau dalam kasus fungsi yang berat dan kompleks. Namun, penting juga untuk mengenali situasi di mana penggunaan useCallback tidak memberikan manfaat tambahan dan malah menambahkan overhead yang tidak perlu.

Semoga penjelasan dan contoh-contoh yang telah kita bahas tadi memberikan pemahaman yang lebih baik tentang cara kerja dan kegunaan useCallback dalam React. Jika Anda merasa artikel ini bermanfaat, jangan ragu untuk membagikannya dengan rekan-rekan Anda. Saya Irsyad, dan saya berharap untuk bertemu Anda lagi di pelajaran selanjutnya. Salam coding!