React'ta Suspense kullanarak paralel veri getirme için gelişmiş teknikleri keşfedin, uygulama performansını ve kullanıcı deneyimini artırın. Çoklu asenkron işlemleri koordine etme stratejilerini ve yükleme durumlarını etkili bir şekilde yönetmeyi öğrenin.
React Suspense Koordinasyonu: Paralel Veri Getirme Stratejilerinde Uzmanlaşmak
React Suspense, özellikle veri getirme olmak üzere, asenkron işlemleri nasıl ele aldığımız konusunda devrim yarattı. Bileşenlerin veri yüklenmesini beklerken işlemeyi "askıya almasına" olanak tanıyarak, yükleme durumlarını yönetmek için bildirimsel bir yol sağlar. Ancak, tek tek veri getirmelerini Suspense ile sarmalamak, bir getirme işleminin bir sonraki başlamadan önce tamamlandığı ve performansı olumsuz etkileyen bir şelale etkisine yol açabilir. Bu blog gönderisi, uygulamanızın yanıt verme hızını optimize etmek ve küresel bir kitle için kullanıcı deneyimini geliştirmek amacıyla, Suspense kullanarak birden çok veri getirme işlemini paralel olarak koordine etmek için gelişmiş stratejileri ele almaktadır.
Veri Getirmede Şelale Problemini Anlamak
Adı, avatarı ve son etkinliğiyle bir kullanıcı profilini görüntülemeniz gereken bir senaryo hayal edin. Her bir veri parçasını sırayla getirirseniz, kullanıcı ad için bir yükleme çarkı, ardından avatar için başka bir yükleme çarkı ve son olarak etkinlik akışı için bir tane daha görür. Bu sıralı yükleme deseni, tam profilin oluşturulmasını geciktiren ve kullanıcıları hayal kırıklığına uğratan bir şelale etkisi yaratır. Değişen ağ hızlarına sahip uluslararası kullanıcılar için bu gecikme daha da belirgin olabilir.
Bu basitleştirilmiş kod parçasını göz önünde bulundurun:
function UserProfile() {
const name = useName(); // Kullanıcı adını getirir
const avatar = useAvatar(name); // Ada göre avatarı getirir
const activity = useActivity(name); // Ada göre etkinliği getirir
return (
<div>
<h2>{name}</h2>
<img src={avatar} alt="User Avatar" />
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
</div>
);
}
Bu örnekte, useAvatar ve useActivity, useName'in sonucuna bağlıdır. Bu, net bir şelale yaratır - useAvatar ve useActivity, useName tamamlanana kadar veri getirmeye başlayamaz. Bu verimsiz ve yaygın bir performans darboğazıdır.
Suspense ile Paralel Veri Getirme Stratejileri
Suspense ile veri getirmeyi optimize etmenin anahtarı, tüm veri isteklerini eşzamanlı olarak başlatmaktır. İşte uygulayabileceğiniz çeşitli stratejiler:
1. `React.preload` ve Kaynaklar ile Verileri Önceden Yükleme
En güçlü tekniklerden biri, bileşen oluşturulmadan önce verileri önceden yüklemektir. Bu, bir "kaynak" (veri getirme sözünü kapsayan bir nesne) oluşturmayı ve verileri önceden getirmeyi içerir. `React.preload` bu konuda yardımcı olur. Bileşenin verilere ihtiyaç duyduğu zamana kadar, veriler zaten kullanılabilir durumdadır ve yükleme durumunu neredeyse tamamen ortadan kaldırır.
Bir ürün getirmek için bir kaynağı düşünün:
const createProductResource = (productId) => {
let promise;
let product;
let error;
const suspender = new Promise((resolve, reject) => {
promise = fetch(`/api/products/${productId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
product = data;
resolve();
})
.catch(e => {
error = e;
reject(e);
});
});
return {
read() {
if (error) {
throw error;
}
if (product) {
return product;
}
throw suspender;
},
};
};
// Kullanım:
const productResource = createProductResource(123);
function ProductDetails() {
const product = productResource.read();
return (<div>{product.name}</div>);
}
Şimdi, bu kaynağı ProductDetails bileşeni oluşturulmadan önce önceden yükleyebilirsiniz. Örneğin, rota geçişleri sırasında veya üzerine gelindiğinde.
React.preload(productResource);
Bu, ProductDetails bileşeninin ihtiyaç duyduğu zamana kadar verilerin büyük olasılıkla kullanılabilir olmasını sağlayarak, yükleme durumunu en aza indirir veya ortadan kaldırır.
2. Eşzamanlı Veri Getirme için `Promise.all` Kullanmak
Başka bir basit ve etkili yaklaşım, tek bir Suspense sınırı içinde tüm veri getirmelerini eşzamanlı olarak başlatmak için Promise.all kullanmaktır. Bu, veri bağımlılıkları önceden bilindiğinde iyi çalışır.
Kullanıcı profili örneğini tekrar ziyaret edelim. Verileri sırayla getirmek yerine, adı, avatarı ve etkinlik akışını eşzamanlı olarak getirebiliriz:
import { useState, useEffect, Suspense } from 'react';
async function fetchName() {
// API çağrısını simüle et
await new Promise(resolve => setTimeout(resolve, 500));
return 'John Doe';
}
async function fetchAvatar(name) {
// API çağrısını simüle et
await new Promise(resolve => setTimeout(resolve, 300));
return `https://example.com/avatars/${name.toLowerCase().replace(' ', '-')}.jpg`;
}
async function fetchActivity(name) {
// API çağrısını simüle et
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, text: 'Bir fotoğraf yayınladı' },
{ id: 2, text: 'Profili güncelledi' },
];
}
function useSuspense(promise) {
const [result, setResult] = useState(null);
useEffect(() => {
let didCancel = false;
promise.then(
(data) => {
if (!didCancel) {
setResult({ status: 'success', value: data });
}
},
(error) => {
if (!didCancel) {
setResult({ status: 'error', value: error });
}
}
);
return () => {
didCancel = true;
};
}, [promise]);
if (result?.status === 'success') {
return result.value;
} else if (result?.status === 'error') {
throw result.value;
} else {
throw promise;
}
}
function Name() {
const name = useSuspense(fetchName());
return <h2>{name}</h2>;
}
function Avatar({ name }) {
const avatar = useSuspense(fetchAvatar(name));
return <img src={avatar} alt="User Avatar" />;
}
function Activity({ name }) {
const activity = useSuspense(fetchActivity(name));
return (
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
function UserProfile() {
const name = useSuspense(fetchName());
return (
<div>
<Suspense fallback=<div>Avatar Yükleniyor...</div>>
<Avatar name={name} />
</Suspense>
<Suspense fallback=<div>Etkinlik Yükleniyor...</div>>
<Activity name={name} />
</Suspense>
</div>
);
}
export default UserProfile;
Ancak, `Avatar` ve `Activity`'nin her biri de `fetchName`'e güveniyorsa, ancak ayrı suspense sınırları içinde oluşturulmuşsa, `fetchName` sözünü ana öğeye yükseltebilir ve React Context aracılığıyla sağlayabilirsiniz.
import React, { createContext, useContext, useState, useEffect, Suspense } from 'react';
async function fetchName() {
// API çağrısını simüle et
await new Promise(resolve => setTimeout(resolve, 500));
return 'John Doe';
}
async function fetchAvatar(name) {
// API çağrısını simüle et
await new Promise(resolve => setTimeout(resolve, 300));
return `https://example.com/avatars/${name.toLowerCase().replace(' ', '-')}.jpg`;
}
async function fetchActivity(name) {
// API çağrısını simüle et
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, text: 'Bir fotoğraf yayınladı' },
{ id: 2, text: 'Profili güncelledi' },
];
}
function useSuspense(promise) {
const [result, setResult] = useState(null);
useEffect(() => {
let didCancel = false;
promise.then(
(data) => {
if (!didCancel) {
setResult({ status: 'success', value: data });
}
},
(error) => {
if (!didCancel) {
setResult({ status: 'error', value: error });
}
}
);
return () => {
didCancel = true;
};
}, [promise]);
if (result?.status === 'success') {
return result.value;
} else if (result?.status === 'error') {
throw result.value;
} else {
throw promise;
}
}
const NamePromiseContext = createContext(null);
function Avatar() {
const namePromise = useContext(NamePromiseContext);
const name = useSuspense(namePromise);
const avatar = useSuspense(fetchAvatar(name));
return <img src={avatar} alt="User Avatar" />;
}
function Activity() {
const namePromise = useContext(NamePromiseContext);
const name = useSuspense(namePromise);
const activity = useSuspense(fetchActivity(name));
return (
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
function UserProfile() {
const namePromise = fetchName();
return (
<NamePromiseContext.Provider value={namePromise}>
<Suspense fallback=<div>Avatar Yükleniyor...</div>>
<Avatar />
</Suspense>
<Suspense fallback=<div>Etkinlik Yükleniyor...</div>>
<Activity />
</Suspense>
</NamePromiseContext.Provider>
);
}
export default UserProfile;
3. Paralel Getirmeleri Yönetmek için Özel Bir Kanca Kullanmak
Olası koşullu veri bağımlılıklarına sahip daha karmaşık senaryolar için, paralel veri getirmeyi yönetmek ve Suspense'in kullanabileceği bir kaynak döndürmek üzere özel bir kanca oluşturabilirsiniz.
import { useState, useEffect, useRef } from 'react';
function useParallelData(fetchFunctions) {
const [resource, setResource] = useState(null);
const mounted = useRef(true);
useEffect(() => {
mounted.current = true;
const promises = fetchFunctions.map(fn => fn());
const suspender = Promise.all(promises).then(
(results) => {
if (mounted.current) {
setResource({ status: 'success', value: results });
}
},
(error) => {
if (mounted.current) {
setResource({ status: 'error', value: error });
}
}
);
setResource({
status: 'pending',
value: suspender,
});
return () => {
mounted.current = false;
};
}, [fetchFunctions]);
const read = () => {
if (!resource) {
throw new Error('Kaynak henüz başlatılmadı');
}
if (resource.status === 'pending') {
throw resource.value;
}
if (resource.status === 'error') {
throw resource.value;
}
return resource.value;
};
return { read };
}
// Örnek kullanım:
async function fetchUserData(userId) {
// API çağrısını simüle et
await new Promise(resolve => setTimeout(resolve, 300));
return { id: userId, name: 'Kullanıcı ' + userId };
}
async function fetchUserPosts(userId) {
// API çağrısını simüle et
await new Promise(resolve => setTimeout(resolve, 500));
return [{ id: 1, title: 'Gönderi 1' }, { id: 2, title: 'Gönderi 2' }];
}
function UserProfile({ userId }) {
const { read } = useParallelData([
() => fetchUserData(userId),
() => fetchUserPosts(userId),
]);
const [userData, userPosts] = read();
return (
<div>
<h2>{userData.name}</h2>
<ul>
{userPosts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
</div>
);
}
function App() {
return (
<Suspense fallback=<div>Kullanıcı verileri yükleniyor...</div>>
<UserProfile userId={123} />
</Suspense>
);
}
export default App;
Bu yaklaşım, sözlerin ve yükleme durumlarının yönetimi karmaşıklığını kancanın içinde kapsülleyerek, bileşen kodunu daha temiz ve verileri işlemeye daha odaklı hale getirir.
4. Akış Sunucu Tarafı İşleme ile Seçici Hidrasyon
Sunucu tarafında işlenen uygulamalar için React 18, akış sunucu tarafı işleme ile seçici hidrasyonu sunar. Bu, sunucuda kullanılabilir hale geldikçe HTML'yi istemciye parçalar halinde göndermenizi sağlar. Yavaş yüklenen bileşenleri <Suspense> sınırlarıyla sararak, yavaş bileşenler sunucuda hala yüklenirken sayfanın geri kalanının etkileşimli hale gelmesine izin verebilirsiniz. Bu, özellikle yavaş ağ bağlantıları veya cihazları olan kullanıcılar için algılanan performansı önemli ölçüde artırır.
Bir haber web sitesinin dünyanın çeşitli bölgelerinden (örneğin, Asya, Avrupa, Amerika) makaleler görüntülemesi gereken bir senaryoyu göz önünde bulundurun. Bazı veri kaynakları diğerlerinden daha yavaş olabilir. Seçici hidrasyon, daha hızlı bölgelerden makalelerin ilk önce görüntülenmesine olanak tanırken, daha yavaş bölgelerden olanlar hala yükleniyor ve tüm sayfanın engellenmesini önlüyor.
Hataları ve Yükleme Durumlarını İşleme
Suspense, yükleme durumu yönetimini basitleştirirken, hata işleme çok önemlidir. Hata sınırları (componentDidCatch yaşam döngüsü yöntemini veya `react-error-boundary` gibi kitaplıklardan useErrorBoundary kancasını kullanarak), veri getirme veya işleme sırasında meydana gelen hataları zarif bir şekilde işlemenize olanak tanır. Bu hata sınırları, uygulamanın tamamının çökmesini önleyerek, belirli Suspense sınırları içindeki hataları yakalamak için stratejik olarak yerleştirilmelidir.
import React, { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
function MyComponent() {
// ... hata verebilecek verileri getirir
}
function App() {
return (
<ErrorBoundary fallback={<div>Bir şeyler ters gitti!</div>}>
<Suspense fallback={<div>Yükleniyor...</div>}>
<MyComponent />
</Suspense>
</ErrorBoundary>
);
}
Hem yükleme hem de hata durumları için bilgilendirici ve kullanıcı dostu geri dönüş kullanıcı arabirimi sağladığınızdan emin olun. Bu, özellikle daha yavaş ağ hızlarıyla veya bölgesel hizmet kesintileriyle karşılaşabilecek uluslararası kullanıcılar için önemlidir.
Suspense ile Veri Getirmeyi Optimize Etmek için En İyi Uygulamalar
- Kritik Verileri Tanımlayın ve Önceliklendirin: Uygulamanızın ilk oluşturulması için hangi verilerin gerekli olduğunu belirleyin ve bu verileri ilk önce getirmeye öncelik verin.
- Mümkün Olduğunda Verileri Önceden Yükleyin: Yükleme durumlarını en aza indirerek, bileşenlerin ihtiyaç duymadan önce verileri önceden yüklemek için `React.preload` ve kaynakları kullanın.
- Verileri Eşzamanlı Olarak Getirin: Birden çok veri getirme işlemini paralel olarak başlatmak için `Promise.all` veya özel kancalar kullanın.
- API Uç Noktalarını Optimize Edin: API uç noktalarınızın performansı optimize edildiğinden, gecikmeyi ve yük boyutunu en aza indirdiğinizden emin olun. Yalnızca ihtiyacınız olan verileri getirmek için GraphQL gibi teknikleri kullanmayı düşünün.
- Önbelleğe Alma Uygulayın: API isteklerinin sayısını azaltmak için sık erişilen verileri önbelleğe alın. Sağlam önbelleğe alma yetenekleri için `swr` veya `react-query` gibi kitaplıkları kullanmayı düşünün.
- Kod Bölmeyi Kullanın: İlk yükleme süresini azaltmak için uygulamanızı daha küçük parçalara bölün. Uygulamanızın farklı bölümlerini aşamalı olarak yüklemek ve işlemek için kod bölmeyi Suspense ile birleştirin.
- Performansı İzleyin: Performans darboğazlarını belirlemek ve ele almak için Lighthouse veya WebPageTest gibi araçları kullanarak uygulamanızın performansını düzenli olarak izleyin.
- Hataları Zarif Bir Şekilde İşleyin: Veri getirme ve işleme sırasında hataları yakalamak için hata sınırları uygulayın ve kullanıcılara bilgilendirici hata mesajları sağlayın.
- Sunucu Tarafı İşlemeyi (SSR) Düşünün: SEO ve performans nedenleriyle, daha hızlı bir ilk deneyim sunmak için akış ve seçici hidrasyon ile SSR kullanmayı düşünün.
Sonuç
React Suspense, paralel veri getirme stratejileriyle birleştirildiğinde, duyarlı ve yüksek performanslı web uygulamaları oluşturmak için güçlü bir araç seti sağlar. Şelale sorununu anlayarak ve önceden yükleme, Promise.all ile eşzamanlı getirme ve özel kancalar gibi teknikler uygulayarak, kullanıcı deneyimini önemli ölçüde iyileştirebilirsiniz. Uygulamanızın dünya çapındaki kullanıcılar için optimize edilmiş kalmasını sağlamak için hataları zarif bir şekilde işlemeyi ve performansı izlemeyi unutmayın. React gelişmeye devam ettikçe, akış sunucu tarafı işleme ile seçici hidrasyon gibi yeni özellikleri keşfetmek, konumdan veya ağ koşullarından bağımsız olarak olağanüstü kullanıcı deneyimleri sunma yeteneğinizi daha da artıracaktır. Bu teknikleri benimseyerek, yalnızca işlevsel değil, aynı zamanda küresel kitleniz için kullanımı keyifli uygulamalar oluşturabilirsiniz.
Bu blog gönderisi, React Suspense ile paralel veri getirme stratejilerine kapsamlı bir genel bakış sağlamayı amaçlamıştır. Bilgilendirici ve yardımcı bulduğunuzu umuyoruz. Kendi projelerinizde bu teknikleri denemenizi ve bulgularınızı toplulukla paylaşmanızı öneririz.