İçeriğe geç
TypeScript

TypeScript + React: İleri Seviye Pattern'ler

Tarık Tunç
TypeScript + React: İleri Seviye Pattern'ler

TypeScript + React: İleri Seviye Pattern'ler

TypeScript ve React birlikte kullanıldığında, bileşenlerinizin tip güvenliğini dramatik şekilde artırır. Props, state, context, hooks ve event handler'lar için güçlü tip tanımları oluşturabilirsiniz. Bu yazıda React projelerinde kullanılan ileri seviye TypeScript pattern'lerini inceleyeceğiz.

Component Props Tipleri

// Temel props tanımlama
interface ButtonProps {
  label: string;
  variant?: "primary" | "secondary" | "danger";
  size?: "sm" | "md" | "lg";
  disabled?: boolean;
  onClick: () => void;
}

const Button: React.FC<ButtonProps> = ({
  label,
  variant = "primary",
  size = "md",
  disabled = false,
  onClick
}) => {
  return (
    <button
      className={`btn btn-${variant} btn-${size}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  );
};

// Children prop
interface CardProps {
  title: string;
  children: React.ReactNode;
  footer?: React.ReactElement;
}

// Polymorphic component (as prop)
type PolymorphicProps<E extends React.ElementType> = {
  as?: E;
  children: React.ReactNode;
} & Omit<React.ComponentPropsWithoutRef<E>, "as" | "children">;

function Box<E extends React.ElementType = "div">({
  as,
  children,
  ...props
}: PolymorphicProps<E>) {
  const Component = as || "div";
  return <Component {...props}>{children}</Component>;
}

// Kullanım
<Box>Div olarak render edilir</Box>
<Box as="section">Section olarak</Box>
<Box as="a" href="/about">Link olarak</Box>

Discriminated Union Props

// Koşullu prop'lar - variant'a göre farklı prop'lar
type AlertProps =
  | {
      variant: "success";
      message: string;
      onClose?: () => void;
    }
  | {
      variant: "error";
      message: string;
      errorCode: number;
      onRetry: () => void;
    }
  | {
      variant: "loading";
      message?: string;
    };

function Alert(props: AlertProps) {
  switch (props.variant) {
    case "success":
      return <div className="alert-success">{props.message}</div>;
    case "error":
      return (
        <div className="alert-error">
          {props.message} (Kod: {props.errorCode})
          <button onClick={props.onRetry}>Tekrar Dene</button>
        </div>
      );
    case "loading":
      return <div className="alert-loading">{props.message || "Yükleniyor..."}</div>;
  }
}

// Mutually exclusive props
type InputProps =
  | { type: "text"; value: string; onChange: (value: string) => void; checked?: never }
  | { type: "checkbox"; checked: boolean; onChange: (checked: boolean) => void; value?: never };
İpucu: Discriminated union props, bileşenlerinizin API'sini hem tip güvenli hem de kullanıcı dostu yapar. IDE'ler variant'a göre ilgili prop'ları otomatik tamamlar ve yanlış kombinasyonları engeller.

Generic Components

// Generic list component
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
  onItemClick?: (item: T) => void;
  emptyMessage?: string;
}

function List<T>({
  items,
  renderItem,
  keyExtractor,
  onItemClick,
  emptyMessage = "Veri bulunamadı"
}: ListProps<T>) {
  if (items.length === 0) {
    return <p>{emptyMessage}</p>;
  }

  return (
    <ul>
      {items.map((item, index) => (
        <li
          key={keyExtractor(item)}
          onClick={() => onItemClick?.(item)}
        >
          {renderItem(item, index)}
        </li>
      ))}
    </ul>
  );
}

// Kullanım - tip otomatik çıkarılır
interface Kullanici {
  id: number;
  isim: string;
}

<List
  items={kullanicilar}
  renderItem={(kullanici) => <span>{kullanici.isim}</span>}
  keyExtractor={(kullanici) => kullanici.id}
  onItemClick={(kullanici) => console.log(kullanici.isim)}
/>

Custom Hooks ile Tip Güvenliği

// Generic fetch hook
interface UseFetchResult<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
  refetch: () => void;
}

function useFetch<T>(url: string): UseFetchResult<T> {
  const [data, setData] = React.useState<T | null>(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState<Error | null>(null);

  const fetchData = React.useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      const json = await response.json();
      setData(json as T);
    } catch (err) {
      setError(err instanceof Error ? err : new Error(String(err)));
    } finally {
      setLoading(false);
    }
  }, [url]);

  React.useEffect(() => { fetchData(); }, [fetchData]);

  return { data, loading, error, refetch: fetchData };
}

// Kullanım
const { data, loading, error } = useFetch<Kullanici[]>("/api/kullanicilar");

// useReducer ile tip güvenli state yönetimi
type Action =
  | { type: "SET_LOADING" }
  | { type: "SET_DATA"; payload: Kullanici[] }
  | { type: "SET_ERROR"; payload: string }
  | { type: "RESET" };

interface State {
  data: Kullanici[];
  loading: boolean;
  error: string | null;
}

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "SET_LOADING":
      return { ...state, loading: true, error: null };
    case "SET_DATA":
      return { ...state, data: action.payload, loading: false };
    case "SET_ERROR":
      return { ...state, error: action.payload, loading: false };
    case "RESET":
      return { data: [], loading: false, error: null };
  }
}

Context API ile Tip Güvenliği

// Tip güvenli context
interface AuthContextType {
  kullanici: Kullanici | null;
  girisYap: (email: string, sifre: string) => Promise<void>;
  cikisYap: () => void;
  yukleniyor: boolean;
}

const AuthContext = React.createContext<AuthContextType | null>(null);

// Custom hook ile null kontrolü
function useAuth(): AuthContextType {
  const context = React.useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth, AuthProvider içinde kullanılmalıdır");
  }
  return context;
}

// Provider component
function AuthProvider({ children }: { children: React.ReactNode }) {
  const [kullanici, setKullanici] = React.useState<Kullanici | null>(null);
  const [yukleniyor, setYukleniyor] = React.useState(false);

  const girisYap = async (email: string, sifre: string) => {
    setYukleniyor(true);
    const response = await fetch("/api/giris", {
      method: "POST",
      body: JSON.stringify({ email, sifre })
    });
    setKullanici(await response.json());
    setYukleniyor(false);
  };

  const cikisYap = () => setKullanici(null);

  return (
    <AuthContext.Provider value={{ kullanici, girisYap, cikisYap, yukleniyor }}>
      {children}
    </AuthContext.Provider>
  );
}
Uyarı: React.FC kullanımı tartışmalıdır. TypeScript 5.1+ ile React 18'de children prop'u artık otomatik olarak dahil edilmez. Bazı projeler React.FC yerine doğrudan fonksiyon tanımını tercih eder. Takımınızın konvansiyonuna uyun.
Bilgi: React 19 ile birlikte gelen yeni özellikler (Server Components, use() hook, form actions) TypeScript ile güçlü tip desteği sunmaktadır. Projenizin React sürümüne göre en güncel tip kalıplarını kullanın.

Sonuç

TypeScript ve React birlikte kullanıldığında, bileşen API'leriniz kendi kendini belgeleyen, hata yakalamayı derleme zamanına taşıyan güçlü yapılar haline gelir. Polymorphic component'ler, generic component'ler, discriminated union props ve tip güvenli context gibi pattern'ler, profesyonel React uygulamalarının temelini oluşturur.

Kaynaklar

İlgili Yazılar