Beberapa fungsi JavaScript bersifat murni, yaitu hanya melakukan kalkulasi dan tidak lebih dari itu. Dengan menulis komponen sebagai fungsi murni, Anda bisa menghindari berbagai macam bug dan tingkah laku yang membingungkan dari aplikasi yang Anda bangun. Namun, ada beberapa aturan yang harus Anda ikuti untuk mencapai keadaan ini.

Anda akan mempelajari

  • Apa itu kemurnian dan bagaimana hal tersebut dapat membantu Anda menghindari bug
  • Bagaimana cara menjaga kemurnian komponen dengan tidak melakukan pengubahan pada fase render
  • Bagaimana cara menggunakan Strict Mode untuk menemukan kesalahan pada komponen Anda

Kemurnian: Komponen sebagai rumus

Dalam ilmu komputer, terutama di dunia pemrograman fungsional, fungsi murni adalah sebuah fungsi yang memenuhi kriteria berikut:

  • Dia hanya mengurus dirinya sendiri. Dia tidak mengubah objek atau variabel yang ada sebelum dia dipanggil.
  • Masukan yang sama, luaran yang sama. Untuk masukan yang sama, fungsi murni akan selalu menghasilkan luaran yang sama.

Anda mungkin sudah akrab dengan salah satu contoh fungsi murni, yaitu rumus-rumus dalam matematika.

Perhatikan rumus ini: y = 2x.

Jika x = 2, y = 4. Selalu.

Jika x = 3, y = 6. Selalu.

Jika x = 3, y tidak mungkin bernilai 9 ataupun –1 ataupun 2.5 hanya karena ada pergantian hari atau pergerakan bursa saham.

Jika y = 2x dan x = 3, y akan selalu bernilai 6.

Jika kita mengonversi rumus ini menjadi fungsi JavaScript, fungsi tersebut akan terlihat seperti ini:

function double(number) {
return 2 * number;
}

Pada contoh di atas, double adalah sebuah fungsi murni. Jika Anda masukkan 3, fungsi itu akan mengembalikan 6. Selalu.

React dibuat berdasarkan konsep ini. React berasumsi kalau setiap komponen yang Anda buat adalah fungsi murni. Ini berarti komponen React yang Anda buat harus selalu menghasilkan JSX yang sama jika diberikan masukan yang sama:

function Recipe({ drinkers }) {
  return (
    <ol>    
      <li>Boil {drinkers} cups of water.</li>
      <li>Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.</li>
      <li>Add {0.5 * drinkers} cups of milk to boil and sugar to taste.</li>
    </ol>
  );
}

export default function App() {
  return (
    <section>
      <h1>Spiced Chai Recipe</h1>
      <h2>For two</h2>
      <Recipe drinkers={2} />
      <h2>For a gathering</h2>
      <Recipe drinkers={4} />
    </section>
  );
}

Jika Anda memberikan drinkers={2} ke Recipe, komponen tersebut akan mengembalikan JSX yang berisi 2 cups of water. Selalu.

Jika Anda memberikan drinkers={4}, komponen tersebut akan mengembalikan JSX yang berisi 4 cups of water. Selalu.

Seperti rumus matematika.

Anda bisa menganggap komponen Anda sebagai resep: jika Anda mengikuti resep tersebut dan tidak menambahkan bahan apa pun dalam proses pemasakan, Anda akan selalu mendapatkan makanan yang sama. “Makanan” itu adalah JSX yang diserahkan sebuah komponen ke React untuk di-render.

Sebuah resep teh untuk x orang: membutuhkan x gelas air, tambahkan teh sebanyak x sendok, tambahkan rempah sebanyak 0,5 sendok, dan 0,5 gelas susu

Ilustrasi oleh Rachel Lee Nabors

Efek Samping: Konsekuensi yang (tidak) diinginkan

Proses render React harus selalu murni. Komponen hanya mengembalikan JSX mereka dan tidak mengubah objek atau variabel apa pun yang telah ada sebelumnya—ini membuat komponen tersebut menjadi tidak murni!

Ini contoh komponen yang tidak mengikuti aturan tersebut:

let guest = 0;

function Cup() {
  // Buruk: mengubah variabel yang sudah ada!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

Komponen ini membaca dan menulis sebuah variabel guest yang telah dideklarasi di luar komponen tersebut. Ini berarti memanggil komponen JSX ini berkali-kali akan menghasilkan JSX yang berbeda pada setiap percobaan! Bukan hanya itu, jika ada komponen *lain yang juga membaca guest, komponen tersebut juga akan menghasilkan JSX yang berbeda, bergantung kepada kapan dia di-render. Hal ini sangat sulit untuk diprediksi.

Kembali ke rumus y = 2x, meskipun x = 2, kita tidak bisa menjamin y = 4. Kasus uji kita akan gagal, pengguna kita menjadi sangat bingung, dan pesawat akan berjatuhan dari langit—Anda bisa melihat bagaimana ini akan berujung kepada bug yang sangat membingungkan!

Anda bisa memperbaiki komponen ini dengan memberikan guest sebagai sebuah prop:

function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup guest={3} />
    </>
  );
}

Sekarang, komponen Anda menjadi murni karena JSX yang dikembalikan hanya bergantung kepada prop guest.

Secara umum, Anda jangan mengharapkan komponen Anda untuk di-render mengikuti suatu urutan yang pasti. Meskipun Anda memanggil y = 2x sebelum atau sesudah y = 5x, kedua rumus tersebut akan berjalan secara independen. Oleh karena itu, setiap komponen sebaiknya hanya “mengurus dirinya sendiri” dan tidak mencoba untuk berkoordinasi atau bergantung kepada komponen lain selama proses render berjalan. Proses render mirip dengan ujian di sekolah, setiap komponen harus mengalkulasi JSX dia sendiri.

Pendalaman

Mendeteksi kalkulasi tidak murni dengan Strict Mode

Walaupun Anda mungkin belum menggunakan semuanya, di React, ada tiga jenis masukan yang bisa dibaca saat proses render, yaitu prop, state, and context. Anda harus selalu menganggap masukan ini sebagai sesuatu yang hanya untuk dibaca (read-only).

Saat Anda ingin mengubah sesuatu sebagai respons dari masukan pengguna, Anda harus mengubah state, bukan menulis ke suatu variabel. Anda tidak boleh mengubah variabel atau objek yang sudah ada sebelumnya saat komponen Anda sedang di-render.

React menawarkan ”Strict Mode” yang memanggil setiap komponen dua kali pada proses pengembangan. Dengan memanggil setiap komponen dua kali, Strict Mode membantu Anda menemukan komponen-komponen yang melanggar aturan ini.

Di contoh pertama, Anda dapat melihat apa yang ditampilkan adalah ”Guest #2”, ”Guest #4”, dan ”Guest #6”, bukan ”Guest #1”, ”Guest #2”, dan ”Guest #3”. Fungsi tersebut tidak murni sehingga saat dipanggil dua kali, dia rusak. Namun, fungsi yang sudah diperbaiki dan menjadi murni dapat bekerja dengan baik meskipun dijalankan dua kali pada setiap pemanggilan. Fungsi murni hanya mengalkulasi sehingga memanggil dia dua kali tidak akan mengubah apa pun—seperti memanggil double(2) dua kali tidak akan mengubah hasilnya dan menyelesaikan y = 2x dua kali tidak akan mengubah nilai y. Masukan yang sama, luaran yang sama. Selalu.

Strict Mode tidak memberikan pengaruh apa pun di tahap produksi sehingga tidak akan memperlambat aplikasi bagi pengguna Anda. Untuk mencoba Strict Mode, Anda bisa membungkus komponen akar Anda ke dalam <React.StrictMode>. Beberapa framework sudah melakukan ini untuk Anda tanpa perlu intervensi dari Anda.

Mutasi lokal: Rahasia kecil bagi komponen Anda

Pada contoh di atas, masalah yang ditemukan adalah komponen tersebut mengubah variabel yang sudah ada sebelumnya saat melakukan proses render. Ini sering disebut mutasi agar terdengar lebih menakutkan. Fungsi murni tidak memutasi variabel yang ada di luar lingkup fungsi tersebut ataupun objek yang dibuat sebelum fungsi tersebut dipanggil—ini membuat fungsi tersebut menjadi tidak murni!

Namun, mengubah variabel atau objek yang baru saja Anda buat saat proses render bukan menjadi masalah. Pada contoh ini, Anda akan membuat sebuah senarai [], memasukkannya ke variabel cups, kemudian menambahkan 1 lusin cup ke dalamnya dengan menggunakan method push():

function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaGathering() {
  let cups = [];
  for (let i = 1; i <= 12; i++) {
    cups.push(<Cup key={i} guest={i} />);
  }
  return cups;
}

Jika variabel cups atau senarai [] dibuat di luar fungsi TeaGathering, ini akan menimbulkan masalah besar! Anda akan mengubah objek yang sudah ada sebelumnya dengan menambahkan benda baru ke dalam senarai tersebut.

Namun, pada kasus ini, itu tidak menjadi masalah karena Anda membuatnya bersamaan dengan proses render yang sama, di dalam TeaGathering. Tidak ada kode di luar TeaGathering akan mengetahui kejadian ini. Inilah yang disebut sebagai “mutasi lokal”—rahasia kecil bagi komponen Anda.

Di mana Anda dapat menimbulkan efek samping

Walaupun pemrograman fungsional sangat bergantung terhadap kemurnian, ada beberapa situasi, sesuatu memang harus diubah. Itulah tujuan dari pemrograman! Perubahan ini-—memperbarui layar, memulai sebuah animasi, mengubah data-—disebut efek samping. Aktivitas tersebut terjadi “di samping”, bukan saat render.

Di React, efek samping biasanya berada di dalam event handlers. Event handler adalah fungsi yang dijalankan React saat Anda melakukan sebuah aksi-—misalnya, saat Anda menekan sebuah tombol. Meskipun event handler berada di dalam komponen Anda, dia tidak berjalan saat render! Dengan demikian, event handler tidak perlu murni.

Jika Anda sudah kehabisan pilihan dan tidak dapat menemukan event handler yang tepat untuk efek samping Anda, Anda masih dapat melampirkannya di JSX yang dikembalikan komponen Anda dengan memanggil useEffect di dalam komponen Anda. Ini memberi tahu React untuk mengeksekusinya nanti, setelah render, saat efek samping sudah diperbolehkan. Namun, metode ini sebaiknya menjadi pilihan terakhir Anda.

Saat memungkinkan, Anda sebaiknya mencoba untuk menuliskan logika tersebut di proses render. Anda akan terkejut seberapa jauh ini dapat membantu Anda!

Pendalaman

Mengapa React peduli terhadap kemurnian?

Menulis fungsi murni membutuhkan waktu dan kedisiplinan, tetapi ini membuka jalan bagi banyak kesempatan menakjubkan:

  • Komponen Anda dapat berjalan di lingkungan yang berbeda-—misalnya, di server! Karena komponen tersebut selalu menghasilkan hal yang sama untuk masukan yang sama, sebuah komponen bisa melayani permintaan dari banyak pengguna.
  • Anda bisa meningkatkan performa dengan melewati proses render dari komponen yang masukannya tidak berubah. Ini aman karena fungsi murni selalu memberikan hasil yang sama sehingga hasilnya bisa disimpan di cache.
  • Jika ada data yang berubah di tengah render dari sebuah pohon komponen yang dalam, React bisa mengulang proses render tanpa perlu menghabiskan waktu menyelesaikan render yang sudah tidak berlaku lagi. Kemurnian menjamin keamanan dari penghentian kalkulasi pada sembarang waktu.

Setiap fitur baru React yang kami bangun memanfaatkan kelebihan dari kemurnian. Dari pengambilan data hingga animasi dan performa. Menjaga kemurnian komponen membuka jalan bagi kemampuan asli dari paradigma React untuk bersinar.

Rekap

  • Sebuah komponen harus murni, berarti:
    • Dia hanya mengurus dirinya sendiri. Dia tidak mengubah objek atau variabel yang ada sebelum dia dipanggil.
    • Masukan yang sama, luaran yang sama. Untuk masukan yang sama, fungsi murni akan selalu menghasilkan JSX yang sama.
  • Render bisa terjadi kapan saja, maka komponen sebaiknya tidak bergantung terhadap proses render satu sama lain.
  • Anda sebaiknya tidak memutasi masukan yang digunakan komponen Anda dalam proses render, termasuk prop, state, dan context. Untuk memperbarui layar, “ubah” state daripada memutasi objek yang sudah ada sebelumnya.
  • Anda sebaiknya berusaha untuk menuliskan logika komponen di JSX yang akan dihasilkan komponen. Saat Anda ingin “mengubah sesuatu”, Anda sebaiknya melakukannya di dalam event handler. Sebagai pilihan terakhir, Anda juga bisa menggunakan useEffect.
  • Menulis fungsi murni akan membutuhkan waktu, tetapi ini membuka jalan untuk memanfaatkan potensi paradigma React secara maksimal.

Tantangan 1 dari 3:
Memperbaiki jam rusak

Komponen ini ingin mengubah kelas CSS <h1> ke "night" dari tengah malam hingga pukul enam pagi dan "day" di waktu lain. Namun, ini tidak bekerja. Apakah Anda bisa memperbaiki jam ini?

Anda bisa memverifikasi solusi Anda dengan mengubah zona waktu komputer Anda. Saat waktu ada di antara tengah malam dan pukul enam pagi, warna jam berubah!

export default function Clock({ time }) {
  let hours = time.getHours();
  if (hours >= 0 && hours <= 6) {
    document.getElementById('time').className = 'night';
  } else {
    document.getElementById('time').className = 'day';
  }
  return (
    <h1 id="time">
      {time.toLocaleTimeString()}
    </h1>
  );
}