React Concurrent Features: useTransition ve useDeferredValue

React Concurrent Features: useTransition ve useDeferredValue
React 18 ile gelen Concurrent Features, kullanıcı arayüzünün her zaman duyarlı kalmasını sağlayan devrim niteliğinde özelliklerdir. useTransition ve useDeferredValue hook'ları, ağır render işlemlerini arka plana atarak kullanıcı etkileşiminin kesintisiz devam etmesini mümkün kılar. Bu rehberde, bu iki hook'un çalışma mantığını, farklarını ve gerçek dünya kullanım senaryolarını adım adım inceleyeceğiz.
Concurrent Rendering Nedir?
Geleneksel React rendering'de (senkron mod), bir render işlemi başladığında tamamlanana kadar kesintiye uğratılamaz. Bu, büyük listelerin filtrelenmesi veya ağır hesaplamalar sırasında arayüzün "donmasına" yol açar. Concurrent rendering ile React, render işlemlerini önceliklendirerek acil güncellemeleri (input yazma, tıklama) düşük öncelikli güncellemelerden (liste filtreleme, ağır hesaplama) önce işler.
useTransition Hook
useTransition, bir state güncellemesini "düşük öncelikli" olarak işaretlemenizi sağlar. Bu sayede kullanıcı arayüzü meşgul olsa bile, acil güncellemeler (input değişiklikleri gibi) hemen işlenir.
import { useState, useTransition, useMemo } from 'react';
interface Product {
id: string;
name: string;
category: string;
price: number;
rating: number;
}
// 10.000 ürünlük büyük bir listeyi filtreleyen component
function ProductSearch({ products }: { products: Product[] }) {
const [query, setQuery] = useState('');
const [category, setCategory] = useState('all');
const [sortBy, setSortBy] = useState<'price' | 'rating'>('price');
const [isPending, startTransition] = useTransition();
// Bu state'ler transition içinde güncellenir (düşük öncelikli)
const [deferredQuery, setDeferredQuery] = useState('');
const [deferredCategory, setDeferredCategory] = useState('all');
const [deferredSortBy, setDeferredSortBy] = useState<'price' | 'rating'>('price');
const handleQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// Input güncellemesi acil — hemen görünür
setQuery(e.target.value);
// Filtreleme düşük öncelikli — arka planda işlenir
startTransition(() => {
setDeferredQuery(e.target.value);
});
};
const handleCategoryChange = (newCategory: string) => {
setCategory(newCategory);
startTransition(() => {
setDeferredCategory(newCategory);
});
};
const filteredProducts = useMemo(() => {
let result = products;
if (deferredQuery) {
result = result.filter((p) =>
p.name.toLowerCase().includes(deferredQuery.toLowerCase())
);
}
if (deferredCategory !== 'all') {
result = result.filter((p) => p.category === deferredCategory);
}
result.sort((a, b) =>
deferredSortBy === 'price' ? a.price - b.price : b.rating - a.rating
);
return result;
}, [products, deferredQuery, deferredCategory, deferredSortBy]);
return (
<div>
<input
type="text"
value={query}
onChange={handleQueryChange}
placeholder="Ürün ara..."
className="border rounded px-3 py-2 w-full"
/>
{isPending && (
<div className="text-blue-500 text-sm mt-1">Sonuçlar güncelleniyor...</div>
)}
<div style={{ opacity: isPending ? 0.7 : 1, transition: 'opacity 0.2s' }}>
<p>{filteredProducts.length} ürün bulundu</p>
{filteredProducts.slice(0, 50).map((product) => (
<div key={product.id} className="border-b py-2">
<h3>{product.name}</h3>
<span>{product.price} TL — ⭐ {product.rating}</span>
</div>
))}
</div>
</div>
);
}
isPending boolean değerini kullanarak, arka planda işlem devam ederken kullanıcıya görsel geri bildirim verebilirsiniz. Örneğin listeyi soluklaştırma veya küçük bir spinner gösterme gibi.
useDeferredValue Hook
useDeferredValue, bir değerin "ertelenmiş" bir kopyasını oluşturur. Acil güncellemeler işlenirken, ertelenmiş değer eski halinde kalır ve React uygun olduğunda güncellenir. useTransition'dan farkı, state güncellemesini kontrol edemediğiniz durumlarda (props olarak gelen değerler gibi) kullanılmasıdır.
import { useState, useDeferredValue, useMemo } from 'react';
interface SearchResultsProps {
query: string;
items: string[];
}
function SearchResults({ query, items }: SearchResultsProps) {
// query prop'u dışarıdan geliyor — useTransition kullanamayız
// useDeferredValue ile ertelenmiş bir kopya oluşturuyoruz
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
const filteredItems = useMemo(() => {
if (!deferredQuery) return items;
// Ağır filtreleme simülasyonu
return items.filter((item) => {
// Her item için karmaşık eşleştirme
const words = deferredQuery.toLowerCase().split(' ');
return words.every((word) => item.toLowerCase().includes(word));
});
}, [items, deferredQuery]);
return (
<div style={{ opacity: isStale ? 0.6 : 1, transition: 'opacity 0.15s' }}>
<p className="text-sm text-gray-500">
{filteredItems.length} sonuç {isStale && '(güncelleniyor...)'}
</p>
<ul>
{filteredItems.map((item, index) => (
<li key={index} className="py-1 border-b">{item}</li>
))}
</ul>
</div>
);
}
// Ana component
function SearchPage() {
const [query, setQuery] = useState('');
const allItems = useMemo(() =>
Array.from({ length: 10000 }, (_, i) => `Ürün ${i + 1} - Kategori ${i % 10}`),
[]
);
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Ara..."
/>
<SearchResults query={query} items={allItems} />
</div>
);
}
useTransition vs useDeferredValue: Ne Zaman Hangisi?
| Özellik | useTransition | useDeferredValue |
|---|---|---|
| Kullanım | State güncellemesini siz kontrol ediyorsanız | Props olarak gelen değerler için |
| isPending | Var | Manuel karşılaştırma gerekir |
| Kontrol Seviyesi | Yüksek — hangi güncellemenin erteleneceğini seçersiniz | Düşük — sadece değeri erteler |
| Yaygın Senaryo | Tab değiştirme, filtreleme, navigasyon | Arama sonuçları, büyük listeler |
Gerçek Dünya Senaryosu: Tab Geçişleri
import { useState, useTransition } from 'react';
type Tab = 'posts' | 'comments' | 'photos';
function TabContainer() {
const [activeTab, setActiveTab] = useState<Tab>('posts');
const [isPending, startTransition] = useTransition();
const handleTabChange = (tab: Tab) => {
startTransition(() => {
setActiveTab(tab);
});
};
const tabs: Tab[] = ['posts', 'comments', 'photos'];
return (
<div>
<nav className="flex gap-2 mb-4">
{tabs.map((tab) => (
<button
key={tab}
onClick={() => handleTabChange(tab)}
className={`px-4 py-2 rounded ${
activeTab === tab ? 'bg-blue-600 text-white' : 'bg-gray-100'
}`}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
</button>
))}
</nav>
<div style={{ opacity: isPending ? 0.6 : 1 }}>
{activeTab === 'posts' && <PostsTab />}
{activeTab === 'comments' && <CommentsTab />}
{activeTab === 'photos' && <PhotosTab />}
</div>
</div>
);
}
Sonuç
React Concurrent Features, kullanıcı deneyimini bir üst seviyeye taşıyan güçlü araçlardır. useTransition ile state güncellemelerini önceliklendirerek, useDeferredValue ile ağır hesaplamaları erteleyerek uygulamanızın her zaman duyarlı kalmasını sağlayabilirsiniz. Ancak bu araçları bilinçli kullanmak önemlidir — önce performans sorununu tespit edin, sonra çözüm olarak Concurrent Features'ı değerlendirin.