<Suspense>
<Suspense>
alt elemanları yüklenene kadar bir alternatif (fallback) göstermenize olanak sağlar.
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
- Referans
- Kullanım
- İçerik yüklenirken bir fallback gösterme
- İçeriği tek seferde birlikte gösterme
- İç içe içeriği yüklendikçe açığa çıkarma
- Yeni içerik yüklenirken eski içeriği gösterme
- Zaten açığa çıkmış içeriğin gizlenmesini önleme
- Transition’ın gerçekleştiğini gösterme
- Navigasyon sırasında Suspense sınırlarını sıfırlama
- Sunucu hataları ve sadece istemcide olan içerik için bir fallback sağlama
- Hata ayıklama
Referans
<Suspense>
Prop’lar
children
: Render etmek istediğiniz asıl kullanıcı arayüzüdür. Eğerchildren
render edilirken askıya alınırsa, Suspense sınırıfallback
’i render etmeye geçer.fallback
: Eğer asıl kullanıcı arayüzünün yüklemesi tamamlanmamışsa, onun yerine render edilecek alternatif bir kullanıcı arayüzüdür. Herhangi geçerli React düğümü kabul edilir, ancak pratikte, bir fallback hafif bir yer tutucu görünümdür, örneğin bir yükleniyor göstergesi ya da iskelet. Suspense,children
askıya alındığında otomatik olarakfallback
’e geçer ve veri hazır olduğundachildren
’a geri döner. Eğerfallback
render edilirken askıya alınırsa, en yakın üst Suspense sınırını etkinleştirir.
Uyarılar
- React ilk kez yüklenemeden önce askıya alınan renderlar için herhangi bir state saklamaz. Bileşen yüklendikten sonra, React askıya alınmış ağacı sıfırdan yeniden render etmeye çalışacaktır.
- Eğer suspense ağaç için içerik gösteriyorduysa, ama sonrasında tekrar askıya alındıysa, askıya alınmayı tetikleyen güncelleme
startTransition
veyauseDeferredValue
tarafından tetiklenmediyse,fallback
tekrar gösterilecektir. - Eğer React halihazırda gösterilen bir içeriği tekrar askıya alındığı için gizlemek zorunda kalırsa, içerik ağacındaki layout Effect’lerini temizleyecektir. İçerik tekrar gösterilmeye hazır olduğunda, React layout Effect’leri tekrar tetikleyecektir. Bu, DOM layout’unu ölçen Effect’lerin içerik gizliyken bunu yapmaya çalışmamasını sağlar.
- React includes under-the-hood optimizations like Streaming Server Rendering and Selective Hydration that are integrated with Suspense. Read an architectural overview and watch a technical talk to learn more.
- React Server Render’ını Stream etme ve Selektif Hydrate Etme gibi Suspense ile entegre olan altta yatan optimizasyonlar içerir. Daha fazla bilgi almak için mimari bir bakışı okuyun ve teknik bir konuşmayı izleyin.
Kullanım
İçerik yüklenirken bir fallback gösterme
Uygulamanızın herhangi bir parçasını bir Suspense sınırıyla sarabilirsiniz:
<Suspense fallback={<Loading />}>
<Albums />
</Suspense>
React yükleniyor fallback’inizi alt elemanların ihtiyaç duyduğu tüm kod ve veriler yüklenene kadar gösterecektir.
Aşağıdaki örnekte, Albums
bileşeni albümler listesini fetch ederken askıya alınır. Render etmeye hazır olana kadar, React fallback’i —sizin Loading
bileşeniniz— göstermek için en yakın Suspense sınırını etkinleştirir. Sonra, veri yüklendiğinde, React Loading
fallback’ini gizler ve Albums
bileşenini verilerle render eder.
import { Suspense } from 'react'; import Albums from './Albums.js'; export default function ArtistPage({ artist }) { return ( <> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Albums artistId={artist.id} /> </Suspense> </> ); } function Loading() { return <h2>🌀 Yükleniyor...</h2>; }
İçeriği tek seferde birlikte gösterme
Varsayılan olarak, Suspense içindeki tüm ağaç tek bir birim olarak ele alınır. Örneğin, eğer bu bileşenlerden sadece biri veri beklemek için askıya alınırsa, tümü birlikte yükleniyor göstergesiyle değiştirilecektir:
<Suspense fallback={<Loading />}>
<Biography />
<Panel>
<Albums />
</Panel>
</Suspense>
Sonrasında, hepsi görüntülenmeye hazır olduğunda, hepsi birlikte tek seferde açığa çıkacaktır.
Aşağıdaki örnekte, hem Biography
hem Albums
veri fetch etmekte. Ancak, tek bir Suspense sınırı altında gruplandıkları için, bu bileşenler her zaman aynı anda “açığa çıkıyor”.
import { Suspense } from 'react'; import Albums from './Albums.js'; import Biography from './Biography.js'; import Panel from './Panel.js'; export default function ArtistPage({ artist }) { return ( <> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Biography artistId={artist.id} /> <Panel> <Albums artistId={artist.id} /> </Panel> </Suspense> </> ); } function Loading() { return <h2>🌀 Yükleniyor...</h2>; }
Veri yükleyen bileşenler Suspense sınırının doğrudan alt elemanı olmak zorunda değildir. Örneğin, Biography
ve Albums
’ü yeni bir Details
bileşenine taşıyabilirsiniz. Bu davranışı değiştirmez. Biography
ve Albums
en yakın ebeveyn Suspense sınırını paylaştığı için, açığa çıkışları birlikte koordine edilir.
<Suspense fallback={<Loading />}>
<Details artistId={artist.id} />
</Suspense>
function Details({ artistId }) {
return (
<>
<Biography artistId={artistId} />
<Panel>
<Albums artistId={artistId} />
</Panel>
</>
);
}
İç içe içeriği yüklendikçe açığa çıkarma
Bir bileşen askıya alındığında, en yakın üst Suspense sınırı fallback’i gösterir. Bu, bir yükleme sekansı oluşturmak için birden fazla Suspense sınırını iç içe geçirebilmenizi sağlar. Her Suspense sınırının fallback’i, bir sonraki içerik seviyesi kullanılabilir hale geldikçe doldurulur. Örneğin, albüm listesine kendi fallback’ini verebilirsiniz:
<Suspense fallback={<BigSpinner />}>
<Biography />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>
Bu değişiklikle birlikte, Biography
’i göstermek Albums
’ün yüklenmesini “beklemek” zorunda değildir.
Sekans şu şekilde olacaktır:
- Eğer
Biography
henüz yüklenmediyse,BigSpinner
tüm içerik alanının yerine gösterilir. Biography
yüklenmeyi tamamladığında,BigSpinner
içerik ile değiştirilir.- Eğer
Albums
henüz yüklenmediyse,AlbumsGlimmer
Albums
ve üst elemanıPanel
’in yerine gösterilir. - Son olarak,
Albums
yüklenmeyi tamamladığında,AlbumsGlimmer
’ın yerine geçer.
import { Suspense } from 'react'; import Albums from './Albums.js'; import Biography from './Biography.js'; import Panel from './Panel.js'; export default function ArtistPage({ artist }) { return ( <> <h1>{artist.name}</h1> <Suspense fallback={<BigSpinner />}> <Biography artistId={artist.id} /> <Suspense fallback={<AlbumsGlimmer />}> <Panel> <Albums artistId={artist.id} /> </Panel> </Suspense> </Suspense> </> ); } function BigSpinner() { return <h2>🌀 Yükleniyor...</h2>; } function AlbumsGlimmer() { return ( <div className="glimmer-panel"> <div className="glimmer-line" /> <div className="glimmer-line" /> <div className="glimmer-line" /> </div> ); }
Suspense sınırları kullanıcı arayüzünüzün hangi parçalarının her zaman birlikte “açığa çıkması” gerektiğini ve hangi parçaların yükleme durumları sekansı içerisinde progresif olarak daha fazla içerik açığa çıkarması gerektiğini koordine etmenizi sağlar. Suspense sınırlarını uygulamanızın geri kalanını etkilemeden ağaç içerisinde herhangi bir yere ekleyebilir, taşıyabilir ya da silebilirsiniz.
Her bileşenin etrafına bir Suspense sınırı koymayın. Suspense sınırları kullanıcıların deneyimlemesini istediğiniz yükleme sekansından daha tanecikli olmamalıdır. Eğer bir tasarımcı ile çalışıyorsanız, yükleme durumlarının nereye konulması gerektiğini sorun—muhtemelen zaten tasarım wireframe’lerine dahil etmişlerdir.
Yeni içerik yüklenirken eski içeriği gösterme
Bu örnekte, SearchResults
bileşeni arama sonuçlarını fetch ederken askıya alınır. "a"
Yazın, sonuçları bekleyin ve daha sonra yazıyı "ab"
olarak düzenleyin. "a"
için gelen sonuçlar yükleme fallback’i ile değiştirilecektir.
import { Suspense, useState } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); return ( <> <label> Albümleri ara: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Yükleniyor...</h2>}> <SearchResults query={query} /> </Suspense> </> ); }
Yaygın bir alternatif kullanıcı arayüzü modeli listeyi güncellemeyi ertelemek ve yeni sonuçlar hazır olana kadar önceki sonuçları göstermeye devam etmektir. useDeferredValue
Hook’u sorgunun ertelenmiş bir sürümünü aşağıya geçirmenizi sağlar:
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Albümleri ara:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Yükleniyor...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
sorgu
(query) hemen güncellenecektir, bu yüzden girdi yeni değeri gösterecektir. Ancak, deferredQuery
veri yüklenene kadar önceki değerini koruyacaktır, bu yüzden SearchResults
bir süreliğine eski sonuçları gösterecektir.
Kullanıcıya daha belli etmek için, eski sonuç listesinin gösterildiği zamanlarda görsel bir gösterge ekleyebilirsiniz:
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1
}}>
<SearchResults query={deferredQuery} />
</div>
Aşağıdaki örneğe "a"
yazın, sonuçların yüklenmesini bekleyin, sonrasında girdiyi "ab"
olarak değiştirin. Yeni sonuçlar yüklenene kadar Suspense fallback’i yerine soluklaşmış eski sonuç listesini gördüğünüze dikkat edin:
import { Suspense, useState, useDeferredValue } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; return ( <> <label> Albümleri ara: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Yükleniyor...</h2>}> <div style={{ opacity: isStale ? 0.5 : 1 }}> <SearchResults query={deferredQuery} /> </div> </Suspense> </> ); }
Zaten açığa çıkmış içeriğin gizlenmesini önleme
Bir bileşen askıya alındığında, en yakın Suspense sınırı fallback’i göstermeye geçer. Bu, zaten bir içerik gösteriliyorsa uyumsuz bir kullanıcı deneyimine yol açabilir. Bu düğmeye basmayı deneyin:
import { Suspense, useState } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); function navigate(url) { setPage(url); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Yükleniyor...</h2>; }
Butona bastığınızda Router
bileşeni ArtistPage
sayfası yerine IndexPage
sayfasını render etti. ArtistPage
içerisindeki bir bileşen askıya alındı, bu yüzden en yakın Suspense sınırı fallback’i göstermeye başladı. En yakın Suspense sınırı köke yakındı, bu yüzden tüm site layout’u BigSpinner
ile değiştirildi.
Bunu engellemek için, navigasyon state güncellemesini bir geçiş (transition) olarak startTransition
: ile işaretleyebilirsiniz:
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
Bu, React’e state transition’ının acil olmadığını, ve zaten açığa çıkmış içeriği gizlemek yerine önceki sayfayı göstermeye devam etmenin daha iyi olduğunu söyler. Şimdi butona basmak Biography
’nin yüklenmesini “bekler”:
import { Suspense, startTransition, useState } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); function navigate(url) { startTransition(() => { setPage(url); }); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Yükleniyor...</h2>; }
Bir transition tüm içeriğin yüklenmesini beklemez. Zaten açığa çıkmış içeriği gizlemekten kaçınmak için ne kadar beklemesi gerekiyorsa o kadar bekler. Örneğin, web sitesinin Layout
’u zaten açığa çıkmıştı, bu yüzden onu bir yükleniyor çarkının arkasına saklamak kötü olurdu. Bununla birlikte, Albums
’ün etrafındaki iç içe geçmiş Suspense
sınırı yeni olduğundan, transition onu beklemiyor.
Transition’ın gerçekleştiğini gösterme
Yukarıdaki örnekte, butona bastığınızda navigasyonun gerçekleştiğini gösteren bir görsel gösterge bulunmamakta. Bir gösterge eklemek için, startTransition
’ı useTransition
ile değiştirebilirsiniz, bu size bir boolean olan isPending
değerini verecektir. Aşağıdaki örnekte, transition’ın gerçekleştiği sırada web sitesi başlığı stilini değiştirmek için useTransition
kullanılmıştır:
import { Suspense, useState, useTransition } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); const [isPending, startTransition] = useTransition(); function navigate(url) { startTransition(() => { setPage(url); }); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout isPending={isPending}> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Yükleniyor...</h2>; }
Navigasyon sırasında Suspense sınırlarını sıfırlama
Bir transition sırasında, React açığa çıkarılmış içeriği gizlemekten kaçınır. Ancak, bir sayfaya farklı parametrelerle giderseniz, React’e bunun farklı bir içerik olduğunu söylemek isteyebilirsiniz. Bunu bir key
ile ifade edebilirsiniz:
<ProfilePage key={queryParams.id} />
Bir kullanıcının profil sayfasına gitmeye çalıştığınızı hayal edin, ve bir şey askıya alınsın. Eğer bu güncelleme bir transition ile sarılırsa, zaten görünen içerik için fallback tetiklenmeyecektir. Bu beklenen davranıştır.
Ancak, şimdi iki farklı kullanıcı profili arasında geçiş yapmaya çalıştığınızı düşünün. Bu durumda, fallback’i göstermek mantıklı olacaktır. Örneğin, bir kullanıcının zaman çizelgesi başka bir kullanıcının zaman çizelgesinden farklı içerik’tir. Bir key
belirterek, React’e farklı kullanıcıların profillerini farklı bileşenler olarak ele almasını ve navigasyon sırasında Suspense sınırlarını sıfırlamasını sağlarsınız. Suspense entegreli router’lar bunu otomatik olarak yapmalıdır.
Sunucu hataları ve sadece istemcide olan içerik için bir fallback sağlama
Eğer stream’leyen sunucu render etme API’lerinden birini (ya da onlara bağlı bir framework) kullanıyorsanız, React sunucuda hataları ele almak için <Suspense>
sınırlarınızı kullanacaktır. Eğer bir bileşen sunucuda bir hata throw ederse, React sunucu render’ını iptal etmeyecektir. Bunun yerine, onun üzerindeki en yakın <Suspense>
bileşenini bulacak ve oluşturulan sunucu HTML’ine bileşenin fallback’ini (örneğin bir yükleniyor çarkı) dahil edecektir. Kullanıcı ilk olarak bir yükleniyor çarkı görecektir.
İstemci tarafında, React aynı bileşeni tekrar render etmeyi deneyecektir. Eğer istemcide de hata verirse, React hatayı throw edip en yakın hata sınırını gösterecektir. Ancak, istemcide hata vermezse, React içeriği nihayetinde başarıyla görüntülediği için hatayı kullanıcıya göstermeyecektir.
Bunu bazı bileşenlerin sunucuda yüklenmemesini sağlamak için kullanabilirsiniz. Bunu yapmak için, sunucu ortamında bir hata throw edin ve ardından HTML’lerini fallback’lerle değiştirmek için <Suspense>
sınırı içine alın:
<Suspense fallback={<Loading />}>
<Chat />
</Suspense>
function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat bileşeni sadece istemcide render edilmelidir.');
}
// ...
}
Sunucu HTML’i yükleniyor çarkını içerecektir. İstemci tarafında yükleniyor çarkı Chat
bileşeni ile değiştirilecektir.
Hata ayıklama
Kullanıcı arayüzünün bir güncelleme sırasında bir fallback ile değiştirilmesini nasıl engellerim?
Görünür bir kullanıcı arayüzünü bir fallback ile değiştirmek, uyumsuz bir kullanıcı deneyimine sebep olur. Bu, bir güncelleme bir bileşenin askıya alınmasına sebep olduğunda ve en yakın Suspense sınırı zaten kullanıcıya içerik gösteriyorsa olabilir.
Bunun olmasını engellemek için, güncellemeyi startTransition
ile acil olmayan olarak işaretleyin. Bir transition sırasında, React istenmeyen bir fallback’in görünmesini engellemek için yeterli veri yüklenene kadar bekleyecektir:
function handleNextPageClick() {
// Eğer bu güncelleme askıya alınırsa, zaten görünen içeriği gizleme
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}
Bu, varolan içeriği gizlemeyi önleyecektir. Ancak, yeni render edilen Suspense
sınırları hala kullanıcı arayüzünü bloke etmemek ve kullanıcının içeriği hazır hale geldikçe görmesini sağlamak için hemen fallback gösterecektir.
React sadece istenmeyen fallback’leri acil olmayan güncellemeler sırasında engeller. Eğer acil bir güncelleme sonucunda gerçekleşiyorsa, bir render’ı geciktirmeyecektir. startTransition
veya useDeferredValue
gibi bir API tercih etmeniz gerekecektir.
Eğer router’ınız Suspense ile entegre ise, güncellemelerini startTransition
’ın içerisine otomatik olarak sarması gerekmektedir.