TypeScript ile React'ı birleştirdiğinizde; bileşen prop'ları, hook'lar, context ve event'ler için güçlü tip güvenliği elde edersiniz. Bu derste TypeScript + React kombinasyonunun en iyi pratiklerini öğreniyoruz.
Bileşen Prop Tipleri
// TSX //
// Temel prop tipleri
interface KullaniciBilgisiProps {
isim: string;
yas: number;
email?: string; // Opsiyonel
rol: 'admin' | 'user' | 'moderator'; // Literal union
aktif: boolean;
olusturulduAt: Date;
onGuncelle: (yeniIsim: string) => void; // Callback
onSil?: () => void; // Opsiyonel callback
children?: React.ReactNode; // Alt elementler
style?: React.CSSProperties; // CSS
className?: string;
}
function KullaniciBilgisi({
isim,
yas,
email,
rol,
aktif,
onGuncelle,
onSil,
children,
}: KullaniciBilgisiProps) {
return (
<div>
<h2>{isim}</h2>
<span className={`badge ${rol}`}>{rol}</span>
{email && <a href={`mailto:${email}`}>{email}</a>}
{children}
<button onClick={() => onGuncelle('Yeni İsim')}>Güncelle</button>
{onSil && <button onClick={onSil}>Sil</button>}
</div>
);
}Generic Bileşenler
// TSX //
// Generic liste bileşeni
interface GenelListeProps<T> {
ogeler: T[];
benzersizAnahtar: (oge: T) => string | number;
ogeyiRender: (oge: T, index: number) => React.ReactNode;
bosIcerik?: React.ReactNode;
yukleniyor?: boolean;
}
function GenelListe<T>({
ogeler,
benzersizAnahtar,
ogeyiRender,
bosIcerik = <p>Öge bulunamadı.</p>,
yukleniyor = false,
}: GenelListeProps<T>) {
if (yukleniyor) return <YuklemeSkeleti />;
if (ogeler.length === 0) return <>{bosIcerik}</>;
return (
<ul>
{ogeler.map((oge, i) => (
<li key={benzersizAnahtar(oge)}>
{ogeyiRender(oge, i)}
</li>
))}
</ul>
);
}
// Kullanım — TypeScript her T için tipini çıkarır
<GenelListe
ogeler={kullanicilar}
benzersizAnahtar={k => k.id}
ogeyiRender={k => <span>{k.isim}</span>}
/>
// Generic Select bileşeni
interface SecimProps<T> {
secenekler: T[];
deger: T | null;
onChange: (yeniDeger: T) => void;
etiketi: (secenek: T) => string;
degeri: (secenek: T) => string | number;
}
function Secim<T>({ secenekler, deger, onChange, etiketi, degeri }: SecimProps<T>) {
return (
<select
value={deger ? String(degeri(deger)) : ''}
onChange={e => {
const secilen = secenekler.find(s => String(degeri(s)) === e.target.value);
if (secilen) onChange(secilen);
}}
>
{secenekler.map(s => (
<option key={String(degeri(s))} value={String(degeri(s))}>
{etiketi(s)}
</option>
))}
</select>
);
}TypeScript ile Custom Hooks
// TSX //
// Tip güvenli useFetch
interface FetchSonucu<T> {
veri: T | null;
yukleniyor: boolean;
hata: Error | null;
yenile: () => void;
}
function useFetch<T>(url: string): FetchSonucu<T> {
const [veri, setVeri] = useState<T | null>(null);
const [yukleniyor, setYukleniyor] = useState(true);
const [hata, setHata] = useState<Error | null>(null);
const [sayac, setSayac] = useState(0);
useEffect(() => {
let iptal = false;
setYukleniyor(true);
setHata(null);
fetch(url)
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}: ${r.statusText}`);
return r.json() as Promise<T>;
})
.then(veri => { if (!iptal) { setVeri(veri); setYukleniyor(false); } })
.catch(e => { if (!iptal) { setHata(e); setYukleniyor(false); } });
return () => { iptal = true; };
}, [url, sayac]);
return { veri, yukleniyor, hata, yenile: () => setSayac(s => s + 1) };
}
// Discriminated union sonuç tipi
type AsyncSonuc<T> =
| { durum: 'yukleniyor' }
| { durum: 'hata'; hata: Error }
| { durum: 'basarili'; veri: T };
function useAsync<T>(fn: () => Promise<T>): AsyncSonuc<T> {
const [sonuc, setSonuc] = useState<AsyncSonuc<T>>({ durum: 'yukleniyor' });
useEffect(() => {
setSonuc({ durum: 'yukleniyor' });
fn()
.then(veri => setSonuc({ durum: 'basarili', veri }))
.catch(hata => setSonuc({ durum: 'hata', hata }));
}, [fn]);
return sonuc;
}
// Kullanım — tip daraltma
const sonuc = useAsync(() => kullanicilariGetir());
if (sonuc.durum === 'yukleniyor') return <Yukleniyoer />;
if (sonuc.durum === 'hata') return <Hata mesaj={sonuc.hata.message} />;
// Burada TypeScript sonuc.veri'nin T olduğunu bilir
return <KullaniciListesi kullanicilar={sonuc.veri} />;Event Tipleri
// TSX //
// HTML Event tipleri
function FormBileseni() {
function degisimIsle(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value, e.target.checked);
}
function gonderIsle(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const form = e.currentTarget;
const veri = new FormData(form);
}
function tiklama(e: React.MouseEvent<HTMLButtonElement>) {
console.log(e.clientX, e.clientY);
}
function klavyeIsle(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') kaydet();
if (e.key === 'Escape') iptal();
}
function surukleme(e: React.DragEvent<HTMLDivElement>) {
e.dataTransfer.setData('text/plain', 'veri');
}
return (
<form onSubmit={gonderIsle}>
<input onChange={degisimIsle} onKeyDown={klavyeIsle} />
<button onClick={tiklama}>Gönder</button>
</form>
);
}Context ile Tip Güvenliği
// TSX //
// Tip güvenli context
interface TemaContext {
tema: 'acik' | 'koyu';
renkler: { birincil: string; ikincil: string };
temaDegistir: () => void;
}
const TemaCtx = createContext<TemaContext | undefined>(undefined);
export function TemaProvider({ children }: { children: React.ReactNode }) {
const [tema, setTema] = useState<'acik' | 'koyu'>('koyu');
const renkler = tema === 'koyu'
? { birincil: '#60a5fa', ikincil: '#a78bfa' }
: { birincil: '#2563eb', ikincil: '#7c3aed' };
return (
<TemaCtx.Provider value={{
tema,
renkler,
temaDegistir: () => setTema(t => t === 'acik' ? 'koyu' : 'acik'),
}}>
{children}
</TemaCtx.Provider>
);
}
export function useTema(): TemaContext {
const ctx = useContext(TemaCtx);
if (!ctx) throw new Error('useTema, TemaProvider içinde kullanılmalı');
return ctx;
}
// Kullanım
function NightModeButonu() {
const { tema, temaDegistir } = useTema();
return (
<button onClick={temaDegistir}>
{tema === 'koyu' ? '☀️ Açık' : '🌙 Koyu'}
</button>
);
}Utility Types ile Gelişmiş Tipler
// TYPESCRIPT //
// Temel interface
interface Makale {
id: number;
baslik: string;
icerik: string;
yayinlandi: boolean;
olusturulduAt: Date;
yazarId: number;
}
// Türetilmiş tipler — utility types
type MakaleOlusturDto = Omit<Makale, 'id' | 'olusturulduAt'>;
type MakaleGuncelleDto = Partial<Omit<Makale, 'id' | 'yazarId' | 'olusturulduAt'>>;
type MakaleOnizleme = Pick<Makale, 'id' | 'baslik' | 'olusturulduAt'>;
type MakaleSalt = Readonly<Makale>;
// Conditional types
type VarolamayanOzellik<T, U extends keyof T = keyof T> = Omit<T, U>;
type DurumaGore<T extends boolean> = T extends true
? { yukleniyor: true }
: { yukleniyor: false; veri: unknown };
// Template literal types
type API_ROTASI = `/api/${string}`;
type HTTP_METOD = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
type OLAY_ADI = `kullanici:${'giris' | 'cikis' | 'guncelleme'}`;
// Discriminated union ile form durumu
type FormDurumu<T> =
| { asamasi: 'bos' }
| { asamasi: 'gonderiliyor' }
| { asamasi: 'hata'; hatalar: Partial<Record<keyof T, string[]>> }
| { asamasi: 'basarili'; veri: T };Sonuç
TypeScript + React kombinasyonu; generic bileşenler, tip güvenli hook'lar ve discriminated union'lar ile hataları geliştirme zamanında yakalamanızı sağlar. Utility types ile interface'leri kopyalamak yerine mevcut tiplerden türetilen yeni tipler oluşturabilirsiniz. Bu yaklaşım büyük ekiplerde ve uzun ömürlü projelerde refactor güvenilirliğini büyük ölçüde artırır.