Menu

Rendering React: Pemicu Render Ulang dan Penyebabnya

Komponen React dirender lagi saat state-nya berubah, saat parent-nya dirender, atau saat context yang dibacanya berubah. Pelajari bedanya render dan commit, arti sebenarnya dari virtual DOM, dan cara menghentikan render yang tidak kamu butuhkan.

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

Komponen React dirender ulang saat state-nya sendiri berubah, saat parent-nya dirender ulang, atau saat context yang dibacanya berubah. Rendering berarti React memanggil fungsi komponenmu lagi untuk mendapatkan JSX yang baru; setelah itu React hanya memperbarui bagian halaman yang benar-benar berubah.

Buka Console di bawah pratinjau dan klik tombolnya. Ketiga komponen mencatat log, termasuk Title, yang sama sekali tidak menerima props. Komponen itu dirender karena parent-nya dirender.

Apa yang memicu render

React merender sebuah komponen tepat karena alasan-alasan ini:

  1. Render pertama. Aplikasi dimulai, atau komponen muncul di pohon untuk pertama kalinya.
  2. State-nya berubah. Kamu memanggil setter dari useState atau dispatch dari useReducer dengan nilai baru.
  3. Parent-nya dirender. Secara default, saat sebuah komponen dirender, setiap komponen yang dikembalikannya ikut dirender, terus sampai ke bawah.
  4. Context yang dibacanya berubah. Komponen yang memanggil useContext(SomeContext) dirender saat provider terdekat memberikan value baru (lihat useContext).

Banyak yang percaya bahwa sebuah komponen dirender "karena props-nya berubah". Itu membalik sebab dan akibat. Props adalah argumen yang dikirim parent saat parent dirender, jadi props baru hanya bisa datang saat parent dirender. Dan render parent saja sudah cukup: Title di atas tidak punya props dan tetap dirender setiap kali.

Mengatur state ke nilai yang sudah dimilikinya (dibandingkan dengan Object.is) tidak memulai render untuk child-nya. React mungkin tetap memanggil komponen itu sekali sebelum menyadari tidak ada yang berubah, tapi hasilnya dibuang.

Render dan commit

Setiap pembaruan melewati dua fase:

  • Render. React memanggil komponenmu. Komponen mengembalikan JSX, yang hanyalah object yang menjelaskan apa yang harus tampil di layar. Belum ada yang berubah di halaman, itulah sebabnya render harus murni: tidak menulis ke DOM, tidak mengirim request, tidak mengubah variabel di luar komponen.
  • Commit. React membandingkan output baru dengan yang sebelumnya dan menerapkan perbedaannya ke DOM: React hanya menyisipkan, menghapus, atau memperbarui node yang berubah. Lalu browser menggambar layar (paint), dan setelah itu React menjalankan effect-mu.

Contoh di bawah dirender di setiap klik, tapi React tetap memakai elemen <input> yang sama. Sebuah effect berjalan setelah setiap commit dan memeriksanya.

Ketik sesuatu di input lalu klik beberapa kali. Teks yang kamu ketik tetap ada, karena React tidak pernah mengganti input itu: satu-satunya perubahan DOM per klik adalah angka di dalam <p>. Log juga menunjukkan urutannya, render lebih dulu dan effect setelah commit.

Virtual DOM, tanpa basa-basi

"Virtual DOM" adalah nama populer untuk object yang dikembalikan komponenmu. <p>Items: {count}</p> dikompilasi menjadi pemanggilan yang membuat object seperti { type: 'p', props: { children: ['Items: ', 1] } }. Setelah render, React menelusuri pohon baru dari object-object ini di samping pohon sebelumnya, dan di tempat yang tipe serta posisinya cocok, React mempertahankan node DOM yang ada dan hanya memperbarui atribut dan teks yang berubah. Perbandingan ini disebut reconciliation.

Istilah ini longgar karena dua alasan. React tidak menyimpan salinan kedua dari DOM lalu membandingkan dua DOM: React membandingkan object element dengan pohon komponen internalnya sendiri (fiber tree). Dan proses yang sama menggerakkan target yang tidak punya DOM sama sekali, seperti React Native. Dokumentasi React sebagian besar menghindari istilah ini dan berbicara tentang rendering dan commit. Yang penting dalam praktik adalah konsekuensinya: rendering itu murah dibandingkan pekerjaan DOM, karena sebagian besar render berakhir dengan pembaruan DOM kecil atau tanpa pembaruan sama sekali.

Dua aturan muncul dari cara kerja perbandingan ini. Tipe element yang berbeda di tempat yang sama (<div> diganti <section>, atau ComponentA diganti ComponentB) menghancurkan subtree lama beserta state-nya. Dan di dalam daftar, key memberi tahu React item mana yang mana, sehingga React bisa memindahkan node alih-alih membangunnya ulang.

Menghentikan render yang tidak kamu butuhkan

Sebagian besar render ekstra tidak menimbulkan biaya yang bisa kamu rasakan. Saat ada yang benar-benar terasa, misalnya daftar besar yang dirender di setiap ketukan tombol di bagian lain halaman, coba langkah-langkah ini secara berurutan.

Pindahkan state ke bawah

Kalau hanya satu bagian kecil layar yang memakai suatu state, simpan state itu di komponen yang hanya membungkus bagian tersebut. Versi ini merender ulang ProductList di setiap ketukan tombol:

export default function App() {
    const [text, setText] = useState('');
    return (
        <>
            <input value={text} onChange={(e) => setText(e.target.value)} />
            <ProductList />
        </>
    );
}

Pindahkan input dan state-nya ke komponennya sendiri, dan daftar itu tidak lagi berada di dalam komponen yang dirender:

Ketik beberapa huruf: hanya SearchBox yang mencatat log.

Kirim children sebagai gantinya

Kadang state harus tinggal di pembungkus di sekitar bagian yang berat, misalnya panel yang menyalakan dan mematikan highlight. Buat pembungkus itu menerima children. Parent yang membuat element child-nya, dan karena parent tidak dirender saat state pembungkus berubah, element itu tetap object yang sama seperti sebelumnya, jadi React melewatinya.

Klik toggle dan hanya Highlighter yang mencatat log. Sekarang pindahkan <Article /> ke dalam JSX Highlighter di posisi {children}, dan setiap klik juga akan mencatat render Article.

Memoize

Saat kedua cara penyusunan ulang itu tidak cocok, bungkus child dengan memo. React lalu membandingkan props-nya dengan props sebelumnya dan melewati render saat semuanya sama. Object dan fungsi yang dibuat saat render selalu baru setiap kali, jadi memo biasanya datang bersama useMemo atau useCallback untuk props tersebut. Halaman React.memo menunjukkan ini dengan contoh yang bisa dijalankan.

import { memo } from 'react';

const ProductList = memo(function ProductList({ category }) {
    // skipped while category stays the same
});

React Compiler bisa menambahkan memoisasi semacam ini untukmu saat build, tapi menyusun ulang state tetap menjadi hal pertama yang patut dicoba: cara itu menghilangkan pekerjaannya, bukan menyimpannya di cache.

Render biasanya tidak masalah

Render adalah pemanggilan fungsi yang mengembalikan object. React menjalankan ribuan render dengan cepat, dan render yang menghasilkan output sama tidak mengubah DOM. Jangan menambahkan memo di mana-mana "biar aman": setiap perbandingan punya biayanya sendiri dan membuat kode lebih sulit dibaca. Ukur dulu. Profiler di React DevTools menunjukkan komponen mana yang dirender, alasannya, dan berapa lama masing-masing berlangsung.

Pertanyaan yang sering diajukan

Apa yang membuat komponen React dirender ulang?

Tiga hal: state-nya sendiri berubah, parent-nya dirender, atau context yang dibacanya dengan useContext mendapat nilai baru. Props bukan pemicu tersendiri: props baru hanya datang karena parent-nya dirender.

Apakah child ikut dirender ulang saat parent dirender ulang?

Ya, secara default setiap komponen di dalam parent yang sedang dirender ikut dirender, meskipun props-nya tidak berubah. Membungkus child dengan memo membuat React melewatinya saat props-nya sama dengan sebelumnya.

Apa itu virtual DOM di React?

Virtual DOM adalah sebutan longgar untuk pohon object JavaScript biasa (React element) yang dikembalikan komponenmu. React membandingkan pohon baru dengan pohon sebelumnya dan hanya mengubah node DOM asli yang berbeda.

Apakah render ulang buruk untuk performa?

Biasanya tidak. Render adalah pemanggilan fungsi yang menghasilkan object, dan React hanya menyentuh DOM di bagian yang output-nya berubah. Lakukan optimasi saat sebuah render terbukti lambat dari hasil pengukuran, misalnya dengan Profiler di React DevTools.

Apa bedanya render dan commit?

Rendering adalah saat React memanggil komponenmu untuk mencari tahu seperti apa layar seharusnya. Commit adalah saat React menerapkan perbedaannya ke DOM. Render yang menghasilkan output sama tidak meng-commit perubahan DOM apa pun.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai