Menu

React useSyncExternalStore: Dış Veriye Abone Olmak

useSyncExternalStore bir bileşeni React dışında yaşayan veriye, örneğin küçük bir store modülüne ya da navigator.onLine gibi bir tarayıcı API'sine abone eder ve bu veri her değiştiğinde yeniden render eder. subscribe ve getSnapshot'ı, snapshot'ın neden önbelleğe alınması gerektiğini ve getServerSnapshot'ı öğren.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

useSyncExternalStore, bir bileşeni React dışında yaşayan veriye abone eder ve bu veri her değiştiğinde onu yeniden render eder. Ona iki fonksiyon verirsin: React'e değişiklikleri nasıl dinleyeceğini söyleyen subscribe ve mevcut değeri döndüren getSnapshot.

İki Display bileşeni hiçbir props ya da context paylaşmaz, ama ikisi birlikte güncellenir, çünkü ikisi de aynı store'a abonedir. Düğme bir React setter'ını değil, düz bir fonksiyonu çağırır. Üçüncü bir <Display name="Sidebar" /> ekle; başka hiçbir değişiklik olmadan katılır.

Sözdizimi

const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);
  • subscribe(callback) dinlemeye başlar, veri değişmiş olabileceğinde callback'i çağırır ve bir abonelikten çıkma fonksiyonu döndürür. React onu bileşen mount olduktan sonra çağırır ve unmount'ta döndürülen fonksiyonu çağırır.
  • getSnapshot() mevcut değeri döndürür. React onu her render sırasında ve her bildirimden sonra çağırır, sonra sonucu sonuncusuyla Object.is kullanarak karşılaştırır. Aynı değer, render yok.
  • getServerSnapshot() (isteğe bağlı) sunucuda ve hydration sırasında kullanılacak değeri döndürür.

subscribe'ı bileşenin dışında tanımla ya da useCallback ile sabit tut. Her render'da yeni bir subscribe fonksiyonu aktarırsan React her seferinde abonelikten çıkar ve yeniden abone olur.

Store olarak tarayıcı API'leri

Mevcut bir değeri olan ve değer değiştiğinde bir olay tetikleyen her şey bu şekle uyar. online ve offline olaylarıyla navigator.onLine klasik örnektir.

Ağını kapat (ya da tarayıcının geliştirici araçlarında Offline'a geç); metin yeniden yükleme olmadan değişir. Üçüncü argüman, navigator'ın olmadığı sunucuda render ederken "çevrimiçi varsay" der. Hook çağrısını useOnlineStatus ile sarmak onu her bileşenin kullanabileceği bir özel hook yapar.

Pencere genişliği de aynı şekilde çalışır:

Birkaç kopya ekle ve pencereyi yeniden boyutlandır: her kopya aynı anda aynı sayıyı gösterir. Her kopyanın kendi dinleyicisi vardır ve her biri genişliği render sırasında okur, bu yüzden hiçbiri asla bir kare geride kalmaz.

getSnapshot önbelleğe alınmış bir değer döndürmeli

React getSnapshot'ı sık çağırır ve sonuçları referansa göre karşılaştırır. Her çağrıda yeni bir nesne ya da dizi oluşturan bir fonksiyon her zaman bir değişiklik gibi görünür:

// Broken: a new object on every call
function getSnapshot() {
    return { count: store.count, user: store.user };
}

// Also broken: filter returns a new array every time
function getSnapshot() {
    return store.todos.filter((t) => !t.done);
}

React render eder, getSnapshot'ı çağırır, "farklı" bir değer alır, yeniden render eder ve böyle devam eder; ta ki "Maximum update depth exceeded" ile durana kadar. Geliştirmede React önce "The result of getSnapshot should be cached to avoid an infinite loop" uyarısını da kaydeder. Buradaki önizleme gibi bir production derlemesi bu uyarıyı atlar ve yalnızca son hatayı kısa bir kod olarak bildirir (Minified React error #185).

Çözüm, veriyi store'da immutable tutmaktır: nesneyi değiştiğinde yenisiyle değiştir ve saklanan referansı olduğu gibi döndür.

getSnapshot, add onu değiştirene kadar aynı state nesnesini döndürür, bu yüzden bileşen her değişiklikte bir kez render olur. Filtreleme, snapshot okunduktan sonra bileşende olur; bu güvenlidir. Bunun yerine store'un içinde filtrelemek için filtrelenmiş diziyi veri değiştiğinde hesapla ve sakla, böylece getSnapshot saklanan kopyayı döndürebilir.

getServerSnapshot ve hydration

Sunucuda pencere, navigator ve abonelik yoktur. Üçüncü argüman React'e orada ne render edeceğini söyler:

const width = useSyncExternalStore(
    subscribe,
    () => window.innerWidth, // in the browser
    () => 1024 // on the server, and during hydration
);

React, sunucu HTML'ini hydrate ederken tarayıcıdaki ilk render için de getServerSnapshot'ı kullanır, böylece ikisi eşleşir. Hydration'dan hemen sonra getSnapshot'ı okur ve gerçek değer farklıysa onunla yeniden render eder. Üçüncü argüman olmadan sunucu render'ı "Missing getServerSnapshot, which is required for server-rendered content. Will revert to client rendering." hatasını fırlatır. Bileşenin üstünde bir <Suspense> sınırı varsa sunucu o sınırın yedek içeriğini gönderir ve tarayıcı bunun yerine içeriğini render eder; sınır yoksa sunucu render'ı başarısız olur.

useSyncExternalStore ile useEffect ve useState

Bir efektle de abone olabilirsin:

function useOnlineStatus() {
    const [online, setOnline] = useState(true);
    useEffect(() => {
        const update = () => setOnline(navigator.onLine);
        update();
        window.addEventListener('online', update);
        window.addEventListener('offline', update);
        return () => {
            window.removeEventListener('online', update);
            window.removeEventListener('offline', update);
        };
    }, []);
    return online;
}

Çalışır, ama iki zayıflığı vardır. İlk render her zaman başlangıç tahminini gösterir ve gerçek değer bir render sonra, efekt çalıştıktan sonra gelir. Ayrıca eşzamanlı render ile (örneğin bir transition sırasında) React bir render'ı yarıda duraklatabilir; store duraklama sırasında değişirse önce ve sonra render edilen bileşenler farklı değerler gösterebilir. Bu tutarsızlığa tearing denir. useSyncExternalStore değeri render sırasında okur ve store değiştiyse React'in render'ı senkron olarak yeniden yapmasını sağlar, böylece her bileşen aynı değeri görür.

Veri React dışında yaşadığında onu kullan: kendi store modülün, bir tarayıcı API'si, üçüncü taraf bir kütüphane. Çoğu state kütüphanesi (Redux, Zustand ve diğerleri) onu kendi hook'larının içinde senin için çağırır. Bileşenlerine ait veri için useState, useReducer ve context doğru araçlar olmaya devam eder.

Sıkça Sorulan Sorular

useSyncExternalStore ne için kullanılır?

React'in sahibi olmadığı ve kendi başına değişebilen veriyi okumak için: React dışında yazılmış bir store, üçüncü taraf bir state kütüphanesi ya da navigator.onLine veya pencere genişliği gibi bir tarayıcı değeri. Store React'e değiştiğini her söylediğinde bileşen yeniden render olur.

subscribe ve getSnapshot ne yapar?

subscribe(callback) store'u dinlemeye başlar, her değişiklikte callback'i çağırır ve dinlemeyi durduran bir fonksiyon döndürür. getSnapshot() mevcut değeri döndürür. React getSnapshot'ı render sırasında ve her bildirimden sonra çağırır ve yalnızca değer Object.is'e göre değiştiyse yeniden render eder.

getSnapshot neden önbelleğe alınmış bir değer döndürmeli?

React her getSnapshot çağrısının sonucunu bir öncekiyle karşılaştırır. Her seferinde yeni bir nesne ya da dizi döndürürse React her zaman bir değişiklik görür, yeniden render eder, getSnapshot'ı yeniden çağırır ve "Maximum update depth exceeded" hatası fırlatana kadar döngüye girer. Veri gerçekten değişene kadar aynı referansı döndür.

getServerSnapshot nedir?

İsteğe bağlı üçüncü argümandır. Sunucu tarafı render sırasında ve tarayıcıdaki hydration sırasında kullanılacak değeri döndürür, böylece ikisi aynı HTML'i üretir. O olmadan bileşen sunucuda "Missing getServerSnapshot" hatası fırlatır ve en yakın <Suspense> sınırının altındaki içerik bunun yerine tarayıcıda render edilir.

useSyncExternalStore mu, yoksa useState ile useEffect mi kullanmalıyım?

Dış veriye abone olmak için useSyncExternalStore'u tercih et. Değeri render sırasında okur, bu yüzden ilk render zaten doğrudur ve eşzamanlı (concurrent) render sırasında bile her bileşen aynı değeri görür. useState ile useEffect bir kez eski bir değerle render eder ve farklı bileşenlerde kısa süreliğine farklı değerler gösterebilir.

Coddy programlama dilleri çizimi

Coddy ile kodlamayı öğren

BAŞLA