React Hooks, fonksiyon bileşenlerine durum ve yaşam döngüsü yönetimi kazandırır. Bu derste useState, useReducer, useContext ve özel hook'ları derinlemesine inceliyoruz; ardından performans hook'larıyla optimizasyon yapıyoruz.
useState vs useReducer
// TSX //
// useState — basit durum
const [sayac, setSayac] = useState(0);
// useReducer — karmaşık durum mantığı için
type Aksiyon =
| { tur: 'ARTTIR' }
| { tur: 'AZALT' }
| { tur: 'SIFIRLA' }
| { tur: 'DEGER_ATA'; deger: number };
function sayacAzaltici(durum: number, aksiyon: Aksiyon): number {
switch (aksiyon.tur) {
case 'ARTTIR': return durum + 1;
case 'AZALT': return Math.max(0, durum - 1);
case 'SIFIRLA': return 0;
case 'DEGER_ATA': return aksiyon.deger;
default: return durum;
}
}
function Sayac() {
const [sayac, dispatch] = useReducer(sayacAzaltici, 0);
return (
<div>
<p>Sayaç: {sayac}</p>
<button onClick={() => dispatch({ tur: 'ARTTIR' })}>+1</button>
<button onClick={() => dispatch({ tur: 'AZALT' })}>-1</button>
<button onClick={() => dispatch({ tur: 'SIFIRLA' })}>Sıfırla</button>
</div>
);
}useContext — Global Durum
// TSX //
// context/AuthContext.tsx
import { createContext, useContext, useReducer, useCallback } from 'react';
interface KullaniciDurumu {
kullanici: Kullanici | null;
yukleniyor: boolean;
}
type AuthAksiyon =
| { tur: 'GIRIS_BASARILI'; kullanici: Kullanici }
| { tur: 'CIKIS' }
| { tur: 'YUKLENIYOR' };
function authAzaltici(durum: KullaniciDurumu, aksiyon: AuthAksiyon): KullaniciDurumu {
switch (aksiyon.tur) {
case 'GIRIS_BASARILI': return { kullanici: aksiyon.kullanici, yukleniyor: false };
case 'CIKIS': return { kullanici: null, yukleniyor: false };
case 'YUKLENIYOR': return { ...durum, yukleniyor: true };
}
}
interface AuthContext {
durum: KullaniciDurumu;
girisYap: (email: string, sifre: string) => Promise<void>;
cikisYap: () => void;
}
const AuthCtx = createContext<AuthContext | null>(null);
export function AuthSaglayici({ children }: { children: React.ReactNode }) {
const [durum, dispatch] = useReducer(authAzaltici, {
kullanici: null,
yukleniyor: false,
});
const girisYap = useCallback(async (email: string, sifre: string) => {
dispatch({ tur: 'YUKLENIYOR' });
const res = await fetch('/api/auth/giris', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, sifre }),
});
const { kullanici } = await res.json();
dispatch({ tur: 'GIRIS_BASARILI', kullanici });
}, []);
const cikisYap = useCallback(() => {
dispatch({ tur: 'CIKIS' });
}, []);
return (
<AuthCtx.Provider value={{ durum, girisYap, cikisYap }}>
{children}
</AuthCtx.Provider>
);
}
// Custom hook — null güvenliği
export function useAuth() {
const ctx = useContext(AuthCtx);
if (!ctx) throw new Error('useAuth, AuthSaglayici içinde kullanılmalıdır');
return ctx;
}Özel Hooks (Custom Hooks)
// TSX //
// hooks/useLocalStorage.ts
function useLocalStorage<T>(anahtar: string, ilkDeger: T) {
const [deger, setDeger] = useState<T>(() => {
try {
const kayit = window.localStorage.getItem(anahtar);
return kayit ? JSON.parse(kayit) : ilkDeger;
} catch {
return ilkDeger;
}
});
const kaydet = useCallback((yeniDeger: T | ((prev: T) => T)) => {
setDeger(prev => {
const hesaplanan = typeof yeniDeger === 'function'
? (yeniDeger as (prev: T) => T)(prev)
: yeniDeger;
window.localStorage.setItem(anahtar, JSON.stringify(hesaplanan));
return hesaplanan;
});
}, [anahtar]);
return [deger, kaydet] as const;
}
// hooks/useFetch.ts
interface FetchDurumu<T> {
veri: T | null;
yukleniyor: boolean;
hata: string | null;
}
function useFetch<T>(url: string) {
const [durum, setDurum] = useState<FetchDurumu<T>>({
veri: null, yukleniyor: true, hata: null,
});
useEffect(() => {
let iptal = false;
setDurum({ veri: null, yukleniyor: true, hata: null });
fetch(url)
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json() as Promise<T>;
})
.then(veri => {
if (!iptal) setDurum({ veri, yukleniyor: false, hata: null });
})
.catch(e => {
if (!iptal) setDurum({ veri: null, yukleniyor: false, hata: e.message });
});
return () => { iptal = true; }; // Cleanup — memory leak önle
}, [url]);
return durum;
}
// hooks/useDebounce.ts
function useDebounce<T>(deger: T, gecikmems: number): T {
const [geciktirilmis, setGeciktirilmis] = useState(deger);
useEffect(() => {
const zamanlayici = setTimeout(() => setGeciktirilmis(deger), gecikmems);
return () => clearTimeout(zamanlayici);
}, [deger, gecikmems]);
return geciktirilmis;
}
// Kullanım — arama kutusu
function AramaKutusu() {
const [aramaMetni, setAramaMetni] = useState('');
const geciktirilmisArama = useDebounce(aramaMetni, 300);
const { veri: sonuclar } = useFetch<Sonuc[]>(
geciktirilmisArama ? `/api/ara?q=${geciktirilmisArama}` : '/api/ara'
);
// ...
}useMemo ve useCallback
// TSX //
// useMemo — pahalı hesaplamayı önbelleğe al
function UrunListesi({ urunler, filtre }: { urunler: Urun[]; filtre: string }) {
// filtre değişmediği sürece tekrar çalışmaz
const filtrelenmisler = useMemo(
() => urunler.filter(u =>
u.isim.toLowerCase().includes(filtre.toLowerCase())
),
[urunler, filtre]
);
const toplamFiyat = useMemo(
() => filtrelenmisler.reduce((acc, u) => acc + u.fiyat, 0),
[filtrelenmisler]
);
return (
<div>
<p>Toplam: ₺{toplamFiyat}</p>
{filtrelenmisler.map(u => <UrunKart key={u.id} urun={u} />)}
</div>
);
}
// useCallback — fonksiyon referansını sabitle
const UrunKart = React.memo(({ urun, onSil }: { urun: Urun; onSil: (id: number) => void }) => {
console.log('UrunKart render:', urun.id);
return (
<div>
<span>{urun.isim}</span>
<button onClick={() => onSil(urun.id)}>Sil</button>
</div>
);
});
function Sepet() {
const [urunler, setUrunler] = useState<Urun[]>([]);
// useCallback olmadan her render'da yeni fonksiyon → React.memo etkisiz
const urununuSil = useCallback((id: number) => {
setUrunler(prev => prev.filter(u => u.id !== id));
}, []); // Bağımlılık yok — stale closure riski için setUrunler(prev=>) kullan
return urunler.map(u => <UrunKart key={u.id} urun={u} onSil={urununuSil} />);
}useRef — DOM ve Değişken Değerler
// TSX //
function VideoOynatici() {
const videoRef = useRef<HTMLVideoElement>(null);
const oynatiliyorRef = useRef(false); // render tetiklemez
function oynat() {
videoRef.current?.play();
oynatiliyorRef.current = true;
}
function durdur() {
videoRef.current?.pause();
oynatiliyorRef.current = false;
}
// Önceki değeri sakla
function usePrevious<T>(deger: T): T | undefined {
const ref = useRef<T>();
useEffect(() => { ref.current = deger; });
return ref.current;
}
return (
<div>
<video ref={videoRef} src="/video.mp4" />
<button onClick={oynat}>Oynat</button>
<button onClick={durdur}>Durdur</button>
</div>
);
}Compound Components Deseni
// TSX //
// Bileşen API'sini temiz tutan desen
interface TabDurumu { aktif: string; setAktif: (id: string) => void; }
const TabCtx = createContext<TabDurumu | null>(null);
function Tabs({ varsayilan, children }: { varsayilan: string; children: React.ReactNode }) {
const [aktif, setAktif] = useState(varsayilan);
return <TabCtx.Provider value={{ aktif, setAktif }}>{children}</TabCtx.Provider>;
}
function TabListesi({ children }: { children: React.ReactNode }) {
return <div role="tablist" className="flex border-b">{children}</div>;
}
function Tab({ id, children }: { id: string; children: React.ReactNode }) {
const { aktif, setAktif } = useContext(TabCtx)!;
return (
<button
role="tab"
aria-selected={aktif === id}
onClick={() => setAktif(id)}
className={aktif === id ? 'border-b-2 border-blue-500' : ''}
>
{children}
</button>
);
}
function TabPanel({ id, children }: { id: string; children: React.ReactNode }) {
const { aktif } = useContext(TabCtx)!;
return aktif === id ? <div role="tabpanel">{children}</div> : null;
}
// Alt bileşenleri ana bileşene ekle
Tabs.Listesi = TabListesi;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;
// Kullanım — temiz API
<Tabs varsayilan="genel">
<Tabs.Listesi>
<Tabs.Tab id="genel">Genel</Tabs.Tab>
<Tabs.Tab id="guvenlik">Güvenlik</Tabs.Tab>
</Tabs.Listesi>
<Tabs.Panel id="genel"><GenelAyarlar /></Tabs.Panel>
<Tabs.Panel id="guvenlik"><GuvenlikAyarlar /></Tabs.Panel>
</Tabs>Sonuç
React Hooks ekosistemi; useReducer ile öngörülebilir durum, useContext ile bileşenler arası veri paylaşımı, özel hook'lar ile mantık yeniden kullanımı ve useMemo/useCallback ile performans optimizasyonu sağlar. Compound Components deseni ise esnek ve kullanımı kolay bileşen API'leri oluşturmanıza olanak tanır. Bir sonraki derste Next.js ile full-stack uygulama geliştirmeyi inceleyeceğiz.