Buka hampir mana-mana kod pangkalan React dan anda akan menyedari refleks yang sama. Seorang pembangun perlu menjejaki sesuatu nilai, jadi mereka menggunakan useState. Perlu pemasa (counter)? useState. Nilai input sementara? useState. Boolean untuk menukar modal? useState. Tidak lama kemudian, satu komponen memegang berpuluh-puluh hook yang berasingan, setiap satunya menguruskan secebis data yang mungkin atau mungkin tidak perlu kekal merentasi render. Hasilnya ialah kod yang berserabut, re-render tambahan, dan state yang bersepah seperti duit syiling di seluruh komponen.

Tabiat ini boleh difahami. useState adalah hook pertama yang dipelajari oleh kebanyakan kita, dan ia berfungsi. Tetapi "berfungsi" tidak sama dengan "sesuai". Menganggap setiap kepingan data sebagai state reaktif mewujudkan masalah yang hanya akan muncul apabila komponen semakin besar.

Hanya Kerana Ia Berubah, Tidak Bermakna Ia Memerlukan State

Bukan setiap pemboleh ubah yang berubah mengikut masa perlu berada dalam useState. Sesetengah nilai hanyalah hasil daripada sesuatu yang lain yang anda sudah miliki. Jika anda menyimpan nama penuh pengguna dalam state hanya kerana anda menggabungkan firstName dan lastName, anda kini mempunyai dua sumber kebenaran (sources of truth). Apabila firstName dikemas kini kerana komponen induk melakukan re-render, state fullName anda akan menjadi lapuk sehingga anda menjalankan kesan (effect) lain untuk menyelaraskannya. Anda tidak memerlukan kesan penyelarasan. Anda memerlukan nilai terbitan (derived value).

const fullName = `${firstName} ${lastName}`;

Kiranya semasa render. Jika pengiraan terbitan itu mahal, gunakan memoize. Tetapi jangan berikan ia hook useState sendiri melainkan pengguna boleh menyunting nama penuh tersebut secara berasingan daripada bahagian-bahagiannya.

Peraturan yang sama terpakai kepada senarai yang ditapis. Jika anda menyimpan kedua-dua allItems dan filteredItems dalam state, anda telah menggandakan beban penyelenggaraan anda. Tapis semasa render. Simpan tatasusunan sumber dan teks penapis dalam state, kemudian terbitkan senarai yang dipaparkan. Ini menjamin bahawa senarai yang ditapis tidak akan pernah terkeluar daripada penyelarasan dengan sumbernya.

Sesetengah Nilai Tidak Sepatutnya Mencetuskan Re-render

useState wujud khusus untuk memberitahu React bahawa sesuatu telah berubah dan DOM mungkin perlu dikemas kini. Jika sesuatu nilai berubah tetapi tiada bahagian UI yang mengambil peduli tentang perubahan itu, useRef adalah alat yang lebih baik.

Pemasa (timers) dan selang masa (intervals) adalah contoh klasik. Menyimpan ID setInterval dalam state menyebabkan re-render setiap kali anda memulakan atau menghentikan pemasa, walaupun pengguna tidak dapat melihat ID selang masa tersebut. Sebuah ref memegang nilai itu tanpa memaklumkan React. Logik yang sama terpakai untuk menjejaki props terdahulu, mengukur nod DOM sebelum lukisan (paint), atau menyimpan callback terbaru untuk hook tersuai. Tanya diri anda: adakah nilai ini perlu dipaparkan di skrin? Jika jawapannya tidak, ia mungkin tidak memerlukan useState.

Nod DOM itu sendiri juga sepatutnya berada dalam ref. Walaupun anda boleh menyimpan elemen DOM dalam state, berbuat demikian akan mencetuskan re-render selepas ref callback dijalankan. Dalam kebanyakan kes, anda hanya memerlukan nod tersebut untuk kaedah imperatif atau pengukuran, bukan untuk memaparkannya secara berbeza.

Perangkap Boolean

Kebimbangan UI yang berkaitan cenderung menjadi tidak terkawal apabila setiap bendera (flag) mendapat hook sendiri. Anda melihat komponen dengan isLoading, isError, dan isSuccess yang ditakrifkan sebagai tiga boolean berasingan. Masalahnya ialah ketiga-tiga state ini tidak bebas. Jika isLoading dan isSuccess kedua-duanya benar, UI anda berada dalam keadaan yang mustahil, namun TypeScript dan React akan membiarkan anda memaparkannya juga.

Mengelompokkan state yang berkaitan dapat mengelakkan kombinasi yang tidak sah ini. Daripada menggunakan tiga boolean, jejak satu string status tunggal: 'idle', 'loading', 'success', atau 'error'. Hanya satu yang boleh aktif pada satu masa, yang menghapuskan keadaan mustahil pada tahap jenis (type level). Jika data lebih kompleks, satu objek dengan discriminated union akan menjadikan keadaan lebih kemas. Apabila anda mendapati diri anda mengemas kini beberapa panggilan useState di dalam pengendali acara (event handler) yang sama, itu adalah isyarat bahawa nilai-nilai tersebut sepatutnya dikumpulkan bersama.

Gunakan useReducer, Bukan Lagi useState

Ada satu tahap di mana kemas kini state menjadi seperti permainan whack-a-mole. Anda memanggil setA, kemudian setB, kemudian secara bersyarat setC, semuanya di dalam satu fungsi. Pembangun seterusnya yang membaca kod tersebut mesti menjejaki urutan tersebut untuk memahami apa yang sebenarnya dilakukan oleh komponen itu.

useReducer sangat berguna di sini. Ia tidak menggantikan useState kerana ia lebih canggih; ia menggantikan useState kerana logiknya menuntut sedemikian. Sebuah reducer memusatkan cara state berubah. Daripada menyebarkan arahan imperatif merentasi pengendali acara, anda menghantar (dispatch) satu niat: dispatch({ type: 'submitted' }). Reducer akan memutuskan bagaimana rupa state seterusnya. Ini menjadikan ujian sangat mudah, kerana logik state anda adalah fungsi tulen (pure function). Ia juga memudahkan penyahpepijatan (debugging), kerana setiap perubahan meninggalkan tindakan yang boleh dijejak.

Anda tidak memerlukan Redux untuk mewajarkan penggunaan reducer. Jika anda mempunyai tiga atau lebih pemboleh ubah state yang dikemas kini secara serentak, atau jika state seterusnya sangat bergantung pada state sebelumnya, reducer akan memudahkan komponen tersebut secara drastik.

Di Mana State Sebenarnya Berada

Kadangkala isunya bukan tentang bagaimana anda menyimpan state, tetapi di mana. Kesilapan biasa adalah melakukan hoisting state ke komponen induk semata-mata kerana ia mungkin diperlukan di tempat lain. Jika hanya satu komponen leaf yang menggunakan satu bahagian state, simpan ia di sana. Ini adalah colocation, dan ia mengurangkan kesan rantaian (blast radius) perubahan. Jangan menyebabkan komponen induk re-render hanya kerana komponen anak dibuka