React 18, bileşenlerin React ağacının dışında yaşayan herhangi bir kaynaktan —tarayıcı API'leri, WebSockets veya üçüncü taraf mağazalar— veri okumasına olanak tanıyan useSyncExternalStore adlı bir hook ekledi. Bu hook, verileri useState ve useEffect ile aynalama şeklindeki eski yöntemin neden olduğu görsel aksaklıkları ortadan kaldırır. Eğer bir arayüzün bir kısmı eski pencere boyutunu gösterirken diğer kısmının yeni boyutu yansıttığı nedeniyle bir UI titremesi (flicker) gördüyseniz, bu hook beklediğiniz çözüm olacaktır.
Harici durum (external state) neden yeni bir hook'a ihtiyaç duyar?
React bileşenleri her zaman kendi kapsamlarının ötesinde var olan şeylere abone olabilmiştir: window nesnesi, bir Redux mağazası veya bir WebSocket bağlantısı. Geleneksel yaklaşım, bir useEffect içinde abonelik kurmak, gelen değerleri setState aracılığıyla yerel duruma (local state) aktarmak ve React'in yeniden render etmesine izin vermekti. Bu, React senkron bir şekilde render ettiğinde sorunsuz çalışır; ancak React 18, bir bileşenin tarayıcı aslında boyama (paint) yapmadan önce birden fazla kez render edilebildiği eşzamanlı (concurrent) render özelliğini getirdi. Bu modda, "yerel duruma aynalama" (mirror-into-local-state) deseni tearing (yırtılma) sorununa yol açabilir; yani tek bir render döngüsü sırasında UI'ın farklı kısımlarının aynı harici değerin farklı anlık görüntülerini (snapshot) okumasıdır.
useSyncExternalStore, tearing sorununu önlemek için oluşturuldu. Harici kaynaktan bir snapshot (mevcut değer) ve bir subscribe fonksiyonu (değişikliklerden nasıl haberdar olunacağı) ister. React, bileşen bağlandığında (mount) subscribe fonksiyonunu çağırır ve bileşen ayrıldığında (unmount) otomatik olarak abonelikten çıkarır. getSnapshot tarafından döndürülen snapshot her değiştiğinde, React tüm bileşen ağacı için aynı snapshot'ı okuyan bir render planlar ve böylece tutarlılığı garanti eder.
Gereken iki fonksiyon
| Fonksiyon | Ne yapar |
|---|---|
| subscribe | Harici değer her değiştiğinde çağrılması gereken bir callback alır. React'in temizleme (cleanup) sırasında çağıracağı bir unsubscribe fonksiyonu döndürür. |
| getSnapshot | Harici kaynaktan mevcut değeri döndürür. React'in snapshot'ları doğru bir şekilde karşılaştırabilmesi için referans açısından kararlı (her render'da aynı fonksiyon nesnesi) olmalıdır. |
Tarayıcının online durumu için minimal bir uygulama şuna benzer:
function useOnlineStatus() {
return useSyncExternalStore(
(onChange) => {
window.addEventListener('online', onChange);
window.addEventListener('offline', onChange);
return () => {
window.removeEventListener('online', onChange);
window.removeEventListener('offline', onChange);
};
},
() => navigator.onLine
);
}
Herhangi bir bileşen useOnlineStatus() çağırdığında, React onu yalnızca navigator.onLine değeri gerçekten değiştiğinde yeniden render edecektir ve her render aynı değeri görecektir.
Hook'un kontrolden çıkmasını engelleyen kurallar
Bir bileşenin ihtiyaç duyduğu en küçük veri parçasını döndürün.
Eğer harici mağaza büyük bir nesne tutuyorsa ancak bir bileşen yalnızca tek bir alanla ilgileniyorsa, sadece o alanı döndürün. Daha büyük dönüşler gereksiz render'lara neden olur.getSnapshotfonksiyonunu memoize edin.
Eğer fonksiyonu her render'da yeniden oluşturursanız, React bunu yeni bir kaynak olarak algılar ve sonsuz döngüye girebilir. OnuuseCallbackile sarmalayın veya bileşenin dışında tanımlayın.Her çağrıda yeni nesneler döndürmekten kaçının.
Yeni bir nesne döndürmek (örneğin,{ count: store.getCount() }), her render'da yeni bir referans oluşturur; bu da React'in snapshot'ın değiştiğini düşünmesine ve sonsuz bir render döngüsünü tetiklemesine neden olur. İlkel türler (primitives), stringler, sayılar veya memoize edilmiş nesneler kullanın.Bileşenin dahili durumu (internal state) için kullanmayın.
Sadece bileşen içinde yaşayan veriler içinuseStatehala doğru araçtır.useSyncExternalStore, yerel durum için gerekli olmayan bir ek yük (overhead) getirir.
Ne zaman tercih edilmeli?
- Tarayıcı API'leri – pencere boyutu, medya sorguları,
navigator.onLine, pil durumu. - Framework bağımsız mağazalar (stores) – Zustand, Redux, MobX veya bir abonelik API'si sunan herhangi bir özel mağaza.
- React dışında güncellenen değiştirilebilir kaynaklar – WebSocket mesajları, IndexedDB değişiklik olayları, Service Worker bildirimleri.
Eğer veri kaynağınız zaten React içinde yaşıyorsa (örneğin, bir üst bileşenin durumu), useState veya context kullanmaya devam edin.
Topluluk ne diyor?
İlk kullananlar, useSyncExternalStore'un eşzamanlı render (concurrent rendering) kurulumunda pencereleri yeniden boyutlandırırken gördükleri titremeyi ortadan kaldırdığını belirtiyor. Bazı kütüphaneler, React sürümleri arasında daha öngörülebilir davranış vaat ederek dahili hook'larını şimdiden bu API'ye geçirdi. Bunun karşılığında biraz daha yüksek bir zihinsel yük vardır: Geliştiriciler, iki fonksiyonun kararlılığı hakkında düşünmeli ve her çağrıda yeni nesneler döndürmekten kaçınmalıdır.
Sırada ne var?
React'in gelecek sürümleri, useSyncExternalStore etrafındaki sözleşmeyi sıkılaştırabilir ve muhtemelen yaygın tarayıcı API'leri için yerleşik yardımcılar ekleyebilir. Eski useEffect-mirroring deseninin kullanımdan kaldırılmasına dair herhangi bir gelişme için resmi React blogunu takip edin. Bu süre zarfında, hook kararlıdır ve genel API'nin bir parçasıdır, bu nedenle mevcut harici durum (external-state) kodunu güvenle refactor edebilirsiniz.
Hızlı test sürüşü
Pencere boyutu senkronizasyonu, çevrimiçi/çevrimdışı algılama ve basit bir Zustand store'unu sergileyen canlı bir demo https://usesyncexternalstore.vercel.app/ adresinde bulunmaktadır. Yorumlarla birlikte eksiksiz kaynak koduna https://github.com/dev48v/usesyncexternalstore adresinden ulaşılabilir.
Özet: useSyncExternalStore, React'e render döngüsünün dışında yaşayan her türlü değiştirilebilir (mutable) veri için güvenilir bir köprü sağlayarak tearing ve gereksiz render işlemlerini önler. Onu harici kaynaklara uygulayın, snapshot'ı minimal ve kararlı tutun ve tutarlılık konusundaki ağır işi React'e bırakın.
