İçeriğe geç
Teknik YazıTypeScript

TypeScript ile Daha İyi React Bileşenleri

TypeScript kullanarak React bileşenlerinizi tip güvenli, bakımı kolay ve daha güçlü hale getirmenin pratik yolları.

20 Şubat 20248 dk okuma

JavaScript'te bir bileşene yanlış prop geçtiğinizde hatayı çalışma zamanında öğrenirsiniz — muhtemelen production'da. TypeScript ile bu hata derleme anında, kodunuzu yazdığınız anda yakalanır.

Props Tanımlamak: interface mi type mı?

İkisi de çalışır, ancak bileşen props'ları için interface tercih edilir — daha okunabilir hata mesajları üretir ve declaration merging destekler.

tsx
// ✅ Tercih edilen
interface ButtonProps {
  label: string
  variant?: 'primary' | 'outline' | 'ghost'
  size?: 'sm' | 'md' | 'lg'
  disabled?: boolean
  onClick?: () => void
}

export function Button({ label, variant = 'primary', size = 'md', ...rest }: ButtonProps) {
  return <button className={cn(base, variants[variant], sizes[size])} {...rest}>{label}</button>
}

children'ı Doğru Tipler

React.PropsWithChildren kullanmak yerine children'ı açıkça tanımlamak daha esnektir — ne tür children'a izin verdiğinizi net gösterir.

tsx
import { ReactNode } from 'react'

interface CardProps {
  title: string
  children: ReactNode        // herhangi bir render edilebilir içerik
  footer?: ReactNode         // opsiyonel slot
  className?: string
}

// Sadece string kabul eden bileşen için:
interface LabelProps {
  children: string
}

Generic Bileşenler

Bir liste bileşeni düşünün: her veri tipiyle çalışması gerekiyor ama tip güvenliğini kaybetmek istemiyorsunuz. Generics tam bu durumda devreye girer.

tsx
interface ListProps<T> {
  items: T[]
  renderItem: (item: T, index: number) => ReactNode
  keyExtractor: (item: T) => string
  emptyText?: string
}

export function List<T>({ items, renderItem, keyExtractor, emptyText = 'Sonuç yok' }: ListProps<T>) {
  if (items.length === 0) return <p>{emptyText}</p>
  return (
    <ul>
      {items.map((item, i) => (
        <li key={keyExtractor(item)}>{renderItem(item, i)}</li>
      ))}
    </ul>
  )
}

// Kullanım — tip otomatik çıkarılır:
<List
  items={users}
  keyExtractor={(u) => u.id}
  renderItem={(u) => <UserCard user={u} />}
/>

💡 İpucu

as const kullanarak sabit değerleri tuple/literal olarak daraltın. Bu sayede "primary" | "outline" gibi union tipler otomatik oluşur, ayrı tanımlamaya gerek kalmaz.

Custom Hook Tipleri

tsx
function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    if (typeof window === 'undefined') return initial
    try {
      const item = localStorage.getItem(key)
      return item ? (JSON.parse(item) as T) : initial
    } catch {
      return initial
    }
  })

  const set = (v: T | ((prev: T) => T)) => {
    setValue(v)
    localStorage.setItem(key, JSON.stringify(typeof v === 'function' ? (v as (p: T) => T)(value) : v))
  }

  return [value, set] as const
  //                    ^ tuple döndürür, [T, Dispatch<...>] çıkarımı doğru olur
}

Discriminated Union ile Conditional Props

Bir bileşenin farklı modlarda farklı prop seti alması gerektiğinde discriminated union'lar hayat kurtarır.

tsx
type AlertProps =
  | { variant: 'info';    message: string }
  | { variant: 'confirm'; message: string; onConfirm: () => void; onCancel: () => void }
  | { variant: 'error';   message: string; error: Error }

// variant='confirm' seçilince TypeScript onConfirm ve onCancel'ı zorunlu kılar.
// variant='info' seçilince bu prop'lar mevcut bile değildir.