TypeScript'in en güçlü özelliklerinden Generics ve Utility Types, kod tekrarını ortadan kaldırır ve büyük projelerde tip güvenliğini uçtan uca sağlar. Bu makalede teoriden öteye geçerek gerçek proje senaryolarında nasıl kullanıldığını inceliyoruz.
Generics Neden Var?
// TYPESCRIPT //
// Generic olmadan — her tip için ayrı fonksiyon
function stringIlk(dizi: string[]): string | undefined { return dizi[0]; }
function numberIlk(dizi: number[]): number | undefined { return dizi[0]; }
function userIlk(dizi: User[]): User | undefined { return dizi[0]; }
// any ile — tip güvenliği yok
function ilk(dizi: any[]): any { return dizi[0]; }
// Generic ile — tek fonksiyon, tam tip güvenliği
function ilk<T>(dizi: T[]): T | undefined { return dizi[0]; }
const sayi = ilk([1, 2, 3]); // TypeScript: number | undefined
const metin = ilk(['a', 'b']); // TypeScript: string | undefined
// TypeScript çıkarım yapar — <number> yazmaya gerek yokKısıtlı Generics (Constrained Generics)
// TYPESCRIPT //
// extends ile kısıt
function uzunlukAl<T extends { length: number }>(deger: T): number {
return deger.length;
}
uzunlukAl('merhaba'); // 7 — string'in length'i var
uzunlukAl([1, 2, 3]); // 3 — dizinin length'i var
uzunlukAl({ length: 5 }); // 5 — uyumlu nesne
// uzunlukAl(42); // HATA — number'ın length'i yok
// keyof ile anahtar kısıtı
function nesneAl<T, K extends keyof T>(nesne: T, anahtar: K): T[K] {
return nesne[anahtar];
}
const kullanici = { ad: 'Ali', yas: 25, email: 'ali@test.com' };
const ad = nesneAl(kullanici, 'ad'); // string
// nesneAl(kullanici, 'telefon'); // HATA — 'telefon' yok
// Multiple constraints
function birlestir<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
const sonuc = birlestir({ ad: 'Ali' }, { yas: 25 }); // { ad: string; yas: number }Generic Sınıflar
// TYPESCRIPT //
// Tip güvenli depo
class Depo<T extends { id: string }> {
private items = new Map<string, T>();
ekle(item: T): this {
this.items.set(item.id, item);
return this;
}
al(id: string): T | undefined {
return this.items.get(id);
}
guncelle(id: string, degisiklikler: Partial<Omit<T, 'id'>>): T | undefined {
const mevcut = this.items.get(id);
if (!mevcut) return undefined;
const guncellenmis = { ...mevcut, ...degisiklikler };
this.items.set(id, guncellenmis);
return guncellenmis;
}
sil(id: string): boolean {
return this.items.delete(id);
}
hepsiniAl(filtre?: (item: T) => boolean): T[] {
const tumu = [...this.items.values()];
return filtre ? tumu.filter(filtre) : tumu;
}
get boyut(): number { return this.items.size; }
}
interface Kullanici { id: string; ad: string; rol: 'admin' | 'editor' | 'okuyucu'; }
const kullaniciler = new Depo<Kullanici>();
kullaniciler
.ekle({ id: '1', ad: 'Ali', rol: 'admin' })
.ekle({ id: '2', ad: 'Ayşe', rol: 'editor' })
.ekle({ id: '3', ad: 'Mehmet', rol: 'okuyucu' });
const adminler = kullaniciler.hepsiniAl(k => k.rol === 'admin');
kullaniciler.guncelle('2', { rol: 'admin' }); // Partial<Omit<Kullanici, 'id'>>Conditional Types
// TYPESCRIPT //
// T string ise true, değilse false
type StringMi<T> = T extends string ? true : false;
type A = StringMi<string>; // true
type B = StringMi<number>; // false
type C = StringMi<'merhaba'>; // true (literal da string'dir)
// Unwrap — Promise'i açar
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type PromiseSonucu = Unwrap<Promise<string>>; // string
type DuzSonuc = Unwrap<number>; // number
// Deep readonly
type DerinReadonly<T> = T extends object
? { readonly [K in keyof T]: DerinReadonly<T[K]> }
: T;
interface Ayarlar {
tema: string;
bildirimler: {
email: boolean;
sms: boolean;
};
}
type SabitAyarlar = DerinReadonly<Ayarlar>;
// { readonly tema: string; readonly bildirimler: { readonly email: boolean; ... } }Mapped Types
// TYPESCRIPT //
// Tüm alanları nullable yap
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Tüm alanları Promise yap
type AsyncNesne<T> = { [K in keyof T]: Promise<T[K]> };
// Getter'lar oluştur
type Getterlar<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Kullanici { ad: string; yas: number; email: string; }
type KullanicGetterlar = Getterlar<Kullanici>;
// { getAd: () => string; getYas: () => number; getEmail: () => string; }
// Belirli anahtarları dönüştür
type StringleriOpsiyonel<T> = {
[K in keyof T]: T[K] extends string ? T[K] | undefined : T[K];
};
type EsnekKullanici = StringleriOpsiyonel<Kullanici>;
// { ad: string | undefined; yas: number; email: string | undefined; }Template Literal Types
// TYPESCRIPT //
// Olay adı oluşturma
type OlayAdi<T extends string> = `on${Capitalize<T>}`;
type TiklamaOlayi = OlayAdi<'tikla'>; // "onTikla"
type YuklemeOlayi = OlayAdi<'yukle'>; // "onYukle"
// CRUD route oluşturucu
type CrudRotalar<T extends string> =
| `GET /api/${T}`
| `POST /api/${T}`
| `PUT /api/${T}/:id`
| `PATCH /api/${T}/:id`
| `DELETE /api/${T}/:id`;
type MakaleRotalari = CrudRotalar<'makaleler'>;
// "GET /api/makaleler" | "POST /api/makaleler" | ...
// CSS property helpers
type CssRenk = `#${string}` | `rgb(${number}, ${number}, ${number})` | `hsl(${number}, ${number}%, ${number}%)`;
const gecerliRenk: CssRenk = '#0ea5e9'; // OK
const hslRenk: CssRenk = 'hsl(210, 90%, 55%)'; // OK
// const gecersiz: CssRenk = 'mavi'; // HATAInfer ile Tip Çıkarımı
// TYPESCRIPT //
// Fonksiyonun ilk parametresinin tipini çıkar
type IlkParam<T extends (...args: any[]) => any> =
T extends (ilk: infer P, ...rest: any[]) => any ? P : never;
function topla(a: number, b: number) { return a + b; }
type ToplaBirinciParam = IlkParam<typeof topla>; // number
// Array elemanının tipini çıkar
type ArrayEleman<T> = T extends (infer E)[] ? E : never;
type StringDizi = ArrayEleman<string[]>; // string
type NumberDizi = ArrayEleman<number[]>; // number
// Zincirli builder — tip güvenli
class SorguOlusturucu<T extends object> {
private kosullar: Partial<T>[] = [];
private siralamaAlani?: keyof T;
filtrele<K extends keyof T>(alan: K, deger: T[K]): SorguOlusturucu<T> {
this.kosullar.push({ [alan]: deger } as Partial<T>);
return this;
}
sirala(alan: keyof T): SorguOlusturucu<T> {
this.siralamaAlani = alan;
return this;
}
olustur(): { kosullar: Partial<T>[]; siralama?: keyof T } {
return { kosullar: this.kosullar, siralama: this.siralamaAlani };
}
}
interface Makale { baslik: string; kategori: string; yazar: string; gorunumler: number; }
const sorgu = new SorguOlusturucu<Makale>()
.filtrele('kategori', 'backend')
.filtrele('yazar', 'Ali')
.sirala('gorunumler')
.olustur();Gerçek Senaryo: Tip Güvenli Fetch Wrapper
// TYPESCRIPT //
type HttpMetot = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
interface ApiEndpoint<TResponse, TBody = never> {
metot: HttpMetot;
yol: string;
govde?: TBody;
}
type ApiSema = {
'GET /kullanicilar': ApiEndpoint<Kullanici[]>;
'GET /kullanicilar/:id': ApiEndpoint<Kullanici>;
'POST /kullanicilar': ApiEndpoint<Kullanici, { ad: string; email: string }>;
'PUT /kullanicilar/:id': ApiEndpoint<Kullanici, Partial<Kullanici>>;
'DELETE /kullanicilar/:id': ApiEndpoint<void>;
};
type ApiCagri = {
[K in keyof ApiSema]: ApiSema[K] extends ApiEndpoint<infer R, infer B>
? [B] extends [never]
? () => Promise<R>
: (govde: B) => Promise<R>
: never;
};
// Bu çerçeve, hangi endpoint'in hangi body beklediğini ve ne döndürdüğünü
// compile time'da garanti eder.Sonuç
TypeScript'in Generics, Conditional Types ve Mapped Types özellikleri, tekrar eden tip tanımlarını ortadan kaldırır ve büyük kod tabanlarında refactoring'i güvenli kılar. Template Literal Types ile API kontratlarınızı bile tip sistemi içinde tanımlayabilirsiniz. Bu teknikler başlangıçta karmaşık görünse de zamanla en büyük zaman tasarrufçularınız haline gelir.