Menu

Hook useCallback di React: Kapan Benar-Benar Membantu

useCallback mengembalikan fungsi yang sama di antara render sampai dependensinya berubah. Hook ini hanya membantu saat fungsi itu diberikan ke child yang memakai memo atau dipakai sebagai dependensi effect. Pelajari sintaksnya, bedanya dengan useMemo, dan kesalahan yang perlu dihindari.

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

useCallback menyimpan sebuah fungsi di cache di antara render: hook ini mengembalikan object fungsi yang sama sampai salah satu dependensinya berubah. Itu hanya penting saat ada sesuatu yang membandingkan fungsi berdasarkan referensi, yang dalam praktiknya berarti child yang dibungkus memo atau effect yang mencantumkan fungsi itu sebagai dependensi.

Kedua tombol di-memoize dan keduanya melakukan hal yang sama. Toggle tema lalu perhatikan Console: hanya render Inline function yang muncul, karena saveInline adalah fungsi baru di setiap render, jadi memo melihat prop yang berubah. saveCached adalah fungsi yang sama setiap kali, jadi tombol itu dilewati.

Apa yang disimpan di cache

const cachedFn = useCallback(fn, dependencies);
  • fn adalah fungsi yang ingin kamu pertahankan. React tidak memanggilnya; React mengembalikannya kepadamu.
  • dependencies mendaftar setiap nilai dari komponen yang dibaca fn. Di setiap render, React membandingkannya dengan nilai sebelumnya memakai Object.is.
  • Kalau tidak ada yang berubah, kamu mendapat fungsi dari render sebelumnya. Kalau ada yang berubah, kamu mendapat fungsi yang kamu berikan kali ini, dan React menyimpan fungsi itu untuk berikutnya.

Perhatikan artinya: arrow function di dalam useCallback tetap dibuat di setiap render, karena ia hanyalah sebuah argumen. useCallback tidak menghemat pekerjaan itu. Satu-satunya tugasnya adalah mengembalikan fungsi lama yang identik agar referensinya tetap sama.

useCallback vs useMemo

Keduanya mekanisme yang sama. useMemo menyimpan hasil pemanggilan sebuah fungsi, useCallback menyimpan fungsinya sendiri:

// These two lines are equivalent
const handleSave = useCallback(() => save(draft), [draft]);
const handleSave = useMemo(() => () => save(draft), [draft]);

Pakai useCallback untuk fungsi karena lebih mudah dibaca, dan useMemo untuk nilai yang dihitung seperti daftar yang difilter atau object opsi.

Mengurangi dependensi dengan fungsi updater

Fungsi yang di-cache harus mencantumkan setiap nilai state yang dibacanya, dan setiap kali state itu berubah, fungsinya ikut berubah. Untuk daftar baris yang di-memoize, ini menjadi masalah: fungsi remove yang membaca todos berubah setiap kali todos berubah, jadi setiap baris yang tersisa dirender lagi setelah setiap penghapusan. Pakai bentuk updater dari setter agar fungsi itu sama sekali tidak lagi membaca todos.

Hapus satu baris: Console tetap sepi, karena baris lain mendapat object todo yang sama dan fungsi remove yang sama seperti sebelumnya. Ubah fungsinya menjadi useCallback((id) => setTodos(todos.filter((t) => t.id !== id)), [todos]) lalu coba lagi: sekarang setiap baris yang tersisa mencatat render setelah setiap penghapusan.

Fungsi sebagai dependensi effect

Effect yang memanggil fungsi yang dideklarasikan di komponen harus mencantumkan fungsi itu sebagai dependensi. Kalau fungsinya baru di setiap render, effect berjalan setelah setiap render. useCallback menjaganya tetap stabil sampai input-nya sendiri berubah:

Mengetik pesan tidak menyambung ulang koneksi. Berpindah room mencatat disconnect dari room lama dan connect ke room baru. Hapus pembungkus useCallback (pertahankan arrow function-nya) dan setiap ketukan tombol akan memutus lalu menyambung ulang.

Tapi ada perbaikan yang lebih sederhana di sini: pindahkan fungsinya ke dalam effect. Dengan begitu effect bergantung langsung pada roomId dan tidak ada fungsi yang perlu stabil.

useEffect(() => {
    function createOptions() {
        return { serverUrl: 'wss://chat.example', roomId };
    }
    const options = createOptions();
    // ...
}, [roomId]);

Utamakan cara itu saat fungsinya hanya dipakai oleh effect. Simpan useCallback untuk fungsi yang juga kamu kirim ke bawah atau kembalikan dari custom hook, tempat pemanggilnya mungkin memasukkannya ke array dependensi mereka sendiri.

Memeriksa apakah sebuah fungsi berubah

Saat child yang memakai memo terus dirender dan kamu tidak yakin penyebabnya, periksa apakah prop fungsi itu benar-benar object yang sama di antara render. Simpan yang sebelumnya di ref lalu bandingkan:

Klik Count: Console menampilkan increment changed: false, karena step tidak berubah. Klik Step dan Console menampilkan true: step adalah dependensi, jadi React mengembalikan fungsi yang baru. Fungsi yang berubah di setiap render biasanya berarti ada dependensi yang juga baru di setiap render, seperti object atau fungsi lain yang dideklarasikan di komponen.

useCallback di custom hook

Custom hook yang mengembalikan fungsi adalah tempat yang bagus untuk useCallback, bahkan saat kamu tidak tahu siapa yang akan memanggilnya. Komponen yang memakai hook itu mungkin memberikan fungsi-fungsi tersebut ke child yang memakai memo atau mencantumkannya di effect, dan komponen itu tidak bisa membuatnya stabil dari luar. Membungkusnya di dalam hook memberi setiap pemanggil referensi yang stabil secara default:

function useCounter(initial = 0) {
    const [count, setCount] = useState(initial);
    const increment = useCallback(() => setCount((c) => c + 1), []);
    const reset = useCallback(() => setCount(initial), [initial]);
    return { count, increment, reset };
}

Kesalahan umum: membungkus semuanya

useCallback di sekitar handler yang langsung diberikan ke <button> atau <input> tidak berguna. React tidak melewatkan pekerjaan apa pun untuk elemen DOM hanya karena handler-nya tetap sama, dan komponen yang tidak dibungkus memo tetap dirender saat parent-nya dirender.

// Pointless: <button> does not care that the function is the same
const handleClick = useCallback(() => setOpen(true), []);
return <button onClick={handleClick}>Open</button>;

Setiap useCallback tambahan menambah array dependensi yang bisa salah ditulis seseorang, dan dependensi yang basi adalah bug sungguhan (fungsinya terus membaca state lama). Tambahkan saat ada child memo atau array dependensi di sisi penerima, bukan secara default.

React Compiler 1.0, saat build-mu memakainya, me-memoize fungsi dan nilai secara otomatis, sehingga sebagian besar pemanggilan useCallback yang ditulis manual tidak lagi diperlukan. Tanpa compiler, aturan di atas tetap berlaku, dan useCallback manual masih umum di kode yang sudah ada.

Pertanyaan yang sering diajukan

Apa fungsi useCallback?

useCallback mengembalikan object fungsi yang sama di setiap render sampai salah satu dependensinya berubah. Hook ini tidak mencegah fungsi itu dibuat; ia mengembalikan fungsi dari render sebelumnya alih-alih fungsi yang baru.

Kapan sebaiknya memakai useCallback?

Saat kamu memberikan fungsi ke child yang dibungkus memo, atau saat fungsi itu menjadi dependensi effect atau hook lain. Di kedua kasus, fungsi baru di setiap render akan menggagalkan optimasinya atau menjalankan ulang effect-nya.

Apa bedanya useCallback dan useMemo?

useCallback(fn, deps) menyimpan fn itu sendiri di cache. useMemo(() => value, deps) menyimpan nilai yang dikembalikan sebuah fungsi. useCallback(fn, deps) persis sama dengan useMemo(() => fn, deps).

Apakah semua fungsi sebaiknya dibungkus useCallback?

Tidak. Kalau fungsi itu diberikan ke elemen DOM biasa atau ke komponen yang tidak di-memoize, referensi yang stabil tidak mengubah apa pun, dan array dependensi tambahan membuat kode lebih sulit dibaca dan lebih mudah salah.

Apakah useCallback membuat komponen saya lebih cepat?

Tidak dengan sendirinya. Hook ini hanya membantu saat ada sesuatu di bawahnya yang membandingkan fungsi berdasarkan referensi, seperti memo atau array dependensi milik effect.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai