React 18 telah menambah useSyncExternalStore, sebuah hook yang membolehkan komponen membaca data daripada mana-mana sumber yang berada di luar pokok (tree) React—seperti API pelayar, WebSocket, atau stor pihak ketiga—tanpa gangguan visual yang sering berlaku pada corak lama yang mencerminkan data tersebut menggunakan useState dan useEffect. Jika anda pernah melihat UI berkelip (flicker) kerana sebahagian daripadanya menunjukkan saiz tetingkap lama manakala bahagian lain sudah menunjukkan saiz baharu, hook ini adalah penyelesaian yang anda nantikan.
Mengapa keadaan (state) luaran memerlukan hook baharu
Komponen React sentiasa boleh melanggan (subscribe) kepada perkara yang wujud di luar skopnya sendiri: objek window, stor Redux, atau sambungan WebSocket. Pendekatan konvensional adalah dengan menyediakan langganan di dalam useEffect, memasukkan nilai yang diterima ke dalam keadaan tempatan (local state) melalui setState, dan membiarkan React melakukan render semula. Ini berfungsi dengan baik apabila React melakukan render secara sinkronus, tetapi React 18 memperkenalkan rendering serentak (concurrent rendering), di mana sesuatu komponen mungkin di-render berkali-kali sebelum pelayar benar-benar melukis (paint) paparan tersebut. Dalam mod tersebut, corak “mencerminkan-ke-keadaan-tempatan” boleh menghasilkan tearing—bahagian UI yang berbeza membaca snapshot nilai luaran yang sama yang berbeza dalam satu kitaran render.
useSyncExternalStore dibina untuk mencegah tearing. Ia meminta snapshot (nilai semasa) daripada sumber luaran dan fungsi subscribe (cara untuk dimaklumkan tentang perubahan). React memanggil subscribe apabila komponen dipasang (mount) dan secara automatik melakukan unsubscribe apabila komponen dilepaskan (unmount). Setiap kali snapshot yang dikembalikan oleh getSnapshot berubah, React akan menjadualkan render yang membaca snapshot yang sama untuk keseluruhan pokok komponen, sekali gus menjamin ketekalan (consistency).
Dua fungsi yang diperlukan
| Fungsi | Fungsinya |
|---|---|
| subscribe | Menerima callback yang mesti dipanggil setiap kali nilai luaran berubah. Ia mengembalikan fungsi unsubscribe yang akan dipanggil oleh React semasa pembersihan (cleanup). |
| getSnapshot | Mengembalikan nilai semasa daripada sumber luaran. Ia mestilah stabil secara rujukan (referentially stable)—objek fungsi yang sama pada setiap render—supaya React dapat membandingkan snapshot dengan betul. |
Implementasi minimal untuk status online pelayar adalah seperti berikut:
function useOnlineStatus() {
return useSyncExternalStore(
(onChange) => {
window.addEventListener('online', onChange);
window.addEventListener('offline', onChange);
return () => {
window.removeEventListener('online', onChange);
window.removeEventListener('offline', onChange);
};
},
() => navigator.onLine
);
}
Apabila mana-mana komponen memanggil useOnlineStatus(), React hanya akan melakukan render semula apabila navigator.onLine benar-benar berubah, dan setiap render akan melihat nilai yang sama.
Peraturan untuk mengelakkan hook daripada bermasalah
Kembalikan bahagian data terkecil yang diperlukan oleh komponen.
Jika stor luaran memegang objek yang besar tetapi komponen hanya memerlukan satu medan, kembalikan medan tersebut sahaja. Pemulangan data yang lebih besar akan menyebabkan render yang tidak perlu.Gunakan memoize untuk
getSnapshot.
Jika anda mencipta semula fungsi tersebut pada setiap render, React akan menganggapnya sebagai sumber baharu dan mungkin akan menyebabkan gelung (loop) tanpa henti. Bungkus ia dalamuseCallbackatau takrifkannya di luar komponen.Elakkan mengembalikan objek baharu pada setiap panggilan.
Mengembalikan objek baharu (contohnya,{ count: store.getCount() }) mencipta rujukan baharu pada setiap render, menyebabkan React menyangka snapshot telah berubah dan mencetuskan gelung render tanpa henti. Gunakan jenis primitif, string, nombor, atau objek yang telah di-memoize.Jangan gunakannya untuk keadaan (state) komponen dalaman.
useStatemasih merupakan alat yang betul untuk data yang hanya wujud di dalam komponen tersebut.useSyncExternalStoremenambah beban (overhead) yang tidak diperlukan untuk keadaan tempatan.
Bila perlu menggunakannya
- API Pelayar – saiz tetingkap, pertanyaan media (media queries),
navigator.onLine, status bateri. - Stor bebas kerangka kerja (framework-agnostic) – Zustand, Redux, MobX, atau mana-mana stor tersuai yang mendedahkan API langganan.
- Sumber boleh ubah (mutable) yang dikemas kini di luar React – mesej WebSocket, acara perubahan IndexedDB, pemberitahuan Service Worker.
Jika sumber data anda sudah berada di dalam React (contohnya, keadaan komponen induk), teruskan menggunakan useState atau context.
Apa yang dikatakan oleh komuniti
Pengguna awal melaporkan bahawa useSyncExternalStore menghapuskan kesan berkelip yang mereka lihat semasa mengubah saiz tetingkap dalam tetapan rendering serentak. Beberapa perpustakaan (library) telah pun menukar hook dalaman mereka kepada API ini, menjanjikan tingkah laku yang lebih boleh diramal merentasi versi React. Imbalannya adalah beban mental yang sedikit tinggi: pembangun mesti memikirkan tentang kestabilan kedua-dua fungsi tersebut dan mengelakkan daripada mengembalikan objek baharu pada setiap panggilan.
Apa yang perlu diperhatikan seterusnya
Versi React yang akan datang mungkin akan memperketat kontrak di sekeliling useSyncExternalStore, berkemungkinan menambah pembantu terbina dalam untuk API pelayar yang biasa digunakan. Pantau blog rasmi React untuk sebarang pengisytiharan usang (deprecation) terhadap corak lama yang meniru useEffect. Sementara itu, hook tersebut adalah stabil dan merupakan sebahagian daripada API awam, jadi anda boleh melakukan refaktor pada kod keadaan luaran yang sedia ada dengan selamat.
Ujian pantas
Demo langsung yang memaparkan penyinkronan saiz tetingkap, pengesanan dalam talian/luar talian, dan stor Zustand yang ringkas boleh didapati di https://usesyncexternalstore.vercel.app/. Kod sumber, lengkap dengan komen, tersedia di https://github.com/dev48v/usesyncexternalstore.
Rumusan: useSyncExternalStore memberikan React jambatan yang boleh dipercayai kepada sebarang data boleh ubah yang berada di luar kitaran rendernya, sekali gus mengelakkan isu 'tearing' dan render yang tidak perlu. Gunakannya pada sumber luaran, pastikan snapshot adalah minimal dan stabil, dan biarkan React mengendalikan tugas berat untuk mengekalkan konsistensi.
