JAVASCRIPT // REACT + TSD::03 ORTA
20m READCOMPLETION: 84%ID::TS-201

TYPESCRIPT İLE REACT: TİP GÜVENLİ BİLEŞENLER

Generic bileşenler, custom hook tipleri ve event tipleri

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.