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 };
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>
);
}
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.
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.