Interface ve Type Alias
Giriş — Nesnelerin Şablonu
Bir önceki derste TypeScript'in temel tiplerini — string, number, tuple, enum, union — öğrendik. Ama gerçek dünya uygulamalarında veriler genellikle tek bir primitive değil, karmaşık yapılar (nesneler) halinde gelir: bir kullanıcının adı, e-postası, yaşı, adresi; bir ürünün fiyatı, açıklaması, kategorisi, stok bilgisi. Bu yapıları her yerde tekrar tekrar yazmak yerine, bir kez tanımlayıp tekrar tekrar kullanmak istiyoruz. İşte interface ve type alias tam olarak bunu yapar.
Analoji: Bir inşaat projesini düşünün. Müteahhide "evin 3 odası, 2 banyosu, bir mutfağı olacak" diyorsunuz. Bu teknik şartname (specification), evin nasıl olması gerektiğini tanımlar. İnşaat başlayınca, her odanın gerçekten var olması kontrol edilir.
interface, kodunuzdaki nesneler için bu teknik şartnamedir — bir nesnenin hangi özellikleri taşıması gerektiğini tanımlar ve TypeScript, her nesnenin bu şartnameye uyduğunu derleme zamanında doğrular.
Interface — Nesne Yapısı Tanımlama
Interface, bir nesnenin sahip olması gereken özellikleri ve metotları tanımlayan bir sözleşmedir (contract).
// Interface tanımlama
interface Kullanici {
id: number;
isim: string;
email: string;
yas: number;
aktif: boolean;
}
// Bu interface'e uyan nesne oluşturma
const ali: Kullanici = {
id: 1,
isim: "Ali Yılmaz",
email: "ali@example.com",
yas: 28,
aktif: true
};
// ❌ Eksik özellik → hata
// const eksik: Kullanici = {
// id: 2,
// isim: "Ayşe"
// // email, yas, aktif eksik!
// // Error: Type '{ id: number; isim: string; }' is missing properties...
// };
// ❌ Fazla özellik → doğrudan atamada hata (excess property check)
// const fazla: Kullanici = {
// id: 3,
// isim: "Mehmet",
// email: "m@example.com",
// yas: 30,
// aktif: true,
// adres: "İstanbul" // Error: Object literal may only specify known properties
// };
// Fonksiyon parametresinde interface kullanımı
function kullaniciGoster(kullanici: Kullanici): void {
console.log(`${kullanici.isim} (${kullanici.email})`);
console.log(`Yaş: ${kullanici.yas}, Durum: ${kullanici.aktif ? "Aktif" : "Pasif"}`);
}
kullaniciGoster(ali); // ✅ Tip uyumluOpsiyonel Özellikler (?)
interface Profil {
isim: string;
email: string;
bio?: string; // Opsiyonel — olmayabilir
avatarUrl?: string; // Opsiyonel
telefonNo?: string; // Opsiyonel
}
// Opsiyonel özellikler olmadan da geçerli
const minimumProfil: Profil = {
isim: "Ali",
email: "ali@example.com"
};
// Opsiyonel özelliklerin tipi: T | undefined
function bioGoster(profil: Profil): string {
// profil.bio → string | undefined
if (profil.bio) {
return profil.bio;
}
return "Bio eklenmemiş";
// Veya kısaca:
// return profil.bio ?? "Bio eklenmemiş";
}Readonly (Salt Okunur) Özellikler
interface Yapilandirma {
readonly apiUrl: string;
readonly apiKey: string;
readonly maxRetry: number;
zaman_asimi: number; // Bu değiştirilebilir
}
const config: Yapilandirma = {
apiUrl: "https://api.example.com",
apiKey: "secret-key-123",
maxRetry: 3,
zaman_asimi: 5000
};
config.zaman_asimi = 10000; // ✅ Değiştirilebilir
// config.apiUrl = "https://hack.com"; // ❌ Error: Cannot assign to 'apiUrl' because it is a read-only property
// config.apiKey = "stolen-key"; // ❌ Error!Metot Tanımlama
interface Hesap {
id: number;
bakiye: number;
// Metot tanımlama — iki sözdizimi
paraYatir(miktar: number): void; // Kısa sözdizimi
paraCek: (miktar: number) => boolean; // Arrow function sözdizimi (aynı şey)
}
const hesap: Hesap = {
id: 1,
bakiye: 1000,
paraYatir(miktar) {
this.bakiye += miktar;
console.log(`${miktar}₺ yatırıldı. Bakiye: ${this.bakiye}₺`);
},
paraCek(miktar) {
if (miktar > this.bakiye) {
console.log("Yetersiz bakiye!");
return false;
}
this.bakiye -= miktar;
console.log(`${miktar}₺ çekildi. Bakiye: ${this.bakiye}₺`);
return true;
}
};
hesap.paraYatir(500); // "500₺ yatırıldı. Bakiye: 1500₺"
hesap.paraCek(200); // "200₺ çekildi. Bakiye: 1300₺"Index Signature — Dinamik Özellikler
Bazen bir nesnenin özellik isimlerini önceden bilmezsiniz — sözlükler, çeviri dosyaları, cache gibi yapılarda:
// String anahtarlı, string değerli sözlük
interface CeviriDosyasi {
[anahtar: string]: string;
}
const tr: CeviriDosyasi = {
greeting: "Merhaba",
farewell: "Hoşça kal",
thanks: "Teşekkürler"
};
tr["hello"] = "Selam"; // ✅ Dinamik ekleme
// Sabit + dinamik özellikler bir arada
interface AyarDepolama {
version: number; // Zorunlu, sabit
[anahtar: string]: unknown; // Dinamik özellikler
}
const ayarlar: AyarDepolama = {
version: 2,
tema: "karanlik",
dil: "tr",
bildirimler: true
};Interface Extend (Genişletme)
Interface'ler birbirini genişletebilir — mevcut bir interface'in tüm özelliklerini alıp üzerine yeni özellikler ekleyebilir. Bu, nesne yönelimli programlamadaki kalıtıma benzer.
// Temel interface
interface Canli {
isim: string;
yas: number;
yasiyorMu: boolean;
}
// Canli'yı genişleten interface
interface Hayvan extends Canli {
tur: string;
habitat: string;
}
// Hayvan'ı genişleten interface
interface Evcil extends Hayvan {
sahibi: string;
asiTarihi?: Date;
ciplandiMi: boolean;
}
const kedi: Evcil = {
// Canli'dan
isim: "Pamuk",
yas: 3,
yasiyorMu: true,
// Hayvan'dan
tur: "Kedi",
habitat: "Ev",
// Evcil'den
sahibi: "Ali",
ciplandiMi: true
};
// Birden fazla interface'i genişletme (multiple inheritance)
interface Adresli {
adres: string;
sehir: string;
postaKodu: string;
}
interface Calisan extends Kullanici, Adresli {
departman: string;
maas: number;
iseBaslama: Date;
}
const calisan: Calisan = {
// Kullanici'dan
id: 1,
isim: "Ayşe Kaya",
email: "ayse@firma.com",
yas: 32,
aktif: true,
// Adresli'den
adres: "Atatürk Cad. No:42",
sehir: "İstanbul",
postaKodu: "34000",
// Calisan'dan
departman: "Mühendislik",
maas: 45000,
iseBaslama: new Date("2020-03-15")
};implements — Class'ta Interface Kullanımı
Interface'ler, class'ların uyması gereken sözleşmeleri tanımlamak için kullanılır. implements anahtar kelimesi, bir class'ın belirli bir interface'e uygun olduğunu garanti eder.
interface Logger {
seviye: "debug" | "info" | "warn" | "error";
log(mesaj: string): void;
hata(mesaj: string, detay?: unknown): void;
temizle(): void;
}
// Class bu interface'i implement ediyor
class ConsoleLogger implements Logger {
seviye: "debug" | "info" | "warn" | "error";
constructor(seviye: Logger["seviye"] = "info") {
this.seviye = seviye;
}
log(mesaj: string): void {
console.log(`[${this.seviye.toUpperCase()}] ${mesaj}`);
}
hata(mesaj: string, detay?: unknown): void {
console.error(`[HATA] ${mesaj}`, detay ?? "");
}
temizle(): void {
console.clear();
}
// Ek metot — interface'te zorunlu değil ama eklenebilir
uyari(mesaj: string): void {
console.warn(`[UYARI] ${mesaj}`);
}
}
// Birden fazla interface implement etme
interface Seri_hale_getirilebilir {
toJSON(): string;
}
interface Yazdirabilir {
yazdir(): void;
}
class Rapor implements Seri_hale_getirilebilir, Yazdirabilir {
constructor(
private baslik: string,
private icerik: string
) {}
toJSON(): string {
return JSON.stringify({ baslik: this.baslik, icerik: this.icerik });
}
yazdir(): void {
console.log(`=== ${this.baslik} ===`);
console.log(this.icerik);
}
}
// Interface ile polimorfizm
function logKullan(logger: Logger) {
logger.log("Uygulama başlatıldı");
logger.hata("Bağlantı hatası", { kod: 500 });
}
const consoleLog = new ConsoleLogger("debug");
logKullan(consoleLog); // ✅ ConsoleLogger, Logger interface'ini implement ediyor💡 İpucu: implements sadece derleme zamanı kontrolü yapar — çalışma zamanında hiçbir etkisi yoktur. TypeScript derleyicisi, class'ın interface'teki tüm özellikleri ve metotları doğru tiplerle sağladığını garanti eder.
Type Alias — Tip Takma Adı
type anahtar kelimesi, herhangi bir tipe takma ad verir. Interface gibi nesne yapısı tanımlayabilir, ama bunun ötesinde union, intersection, tuple ve primitive tipler için de kullanılabilir.
// Primitive'lere takma ad
type ID = number;
type Email = string;
type ParaBirimi = "TRY" | "USD" | "EUR";
// Nesne tipi (interface'e benzer)
type Nokta = {
x: number;
y: number;
};
// Union tipler
type StringVeyaSayi = string | number;
type Durum = "yukleniyor" | "basarili" | "hatali";
type Nullable<T> = T | null;
// Tuple
type Koordinat = [number, number];
type KullaniciGirdisi = [string, ...string[]]; // En az bir string
// Fonksiyon tipi
type KarsilastirmaFn = (a: number, b: number) => number;
type OlayDinleyici = (olay: Event) => void;
// Intersection
type Tarihli<T> = T & {
olusturma: Date;
guncelleme: Date;
};
type TarihliKullanici = Tarihli<Kullanici>;
// Kullanici'nin tüm alanları + olusturma + guncelleme
// Koşullu tip (ileri seviye — giriş)
type DiziyeMi<T> = T extends any[] ? "evet" : "hayır";
type Test1 = DiziyeMi<string[]>; // "evet"
type Test2 = DiziyeMi<number>; // "hayır"Nesne Tipi Olarak type vs interface
// type ile nesne tanımı
type UrunType = {
id: number;
isim: string;
fiyat: number;
aciklama?: string;
};
// interface ile nesne tanımı
interface UrunInterface {
id: number;
isim: string;
fiyat: number;
aciklama?: string;
}
// Kullanımda fark YOK — ikisi de aynı şekilde çalışır
const urun1: UrunType = { id: 1, isim: "Laptop", fiyat: 25000 };
const urun2: UrunInterface = { id: 2, isim: "Telefon", fiyat: 15000 };Interface vs Type — Farklar ve Tercih
Bu iki yapı çoğu durumda birbirinin yerine kullanılabilir, ama önemli farkları vardır:
1. Declaration Merging — Sadece Interface
// Interface: Aynı isimle tekrar tanımlanırsa BİRLEŞTİRİLİR
interface Pencere {
genislik: number;
yukseklik: number;
}
interface Pencere {
baslik: string; // Önceki tanıma EKLENİR
}
// Sonuç: Pencere { genislik, yukseklik, baslik }
const pencere: Pencere = {
genislik: 800,
yukseklik: 600,
baslik: "Uygulama"
};
// Type: Aynı isimle tekrar tanımlanamaz → HATA
type Kutu = { genislik: number };
// type Kutu = { yukseklik: number }; // ❌ Error: Duplicate identifier 'Kutu'Declaration merging, özellikle kütüphane genişletme için önemlidir:
// Örnek: Window nesnesine özel özellik ekleme
declare global {
interface Window {
analyticsId: string;
__APP_VERSION__: string;
}
}
window.analyticsId = "UA-12345"; // ✅ TypeScript artık bunu tanır
window.__APP_VERSION__ = "1.0.0"; // ✅2. Extend vs Intersection
// Interface: extends ile genişletme
interface Hayvan {
isim: string;
yas: number;
}
interface Kedi extends Hayvan {
miyavla(): void;
}
// Type: & (intersection) ile birleştirme
type HayvanTip = {
isim: string;
yas: number;
};
type KediTip = HayvanTip & {
miyavla(): void;
};
// İkisi de aynı sonucu verir — ama extends daha okunabilir
// Hata mesajları: interface extends ile daha net hata mesajları alırsınız3. Union ve Tuple — Sadece Type
// ❌ Interface union yapamaz
// interface Sonuc = string | number; // Sözdizimi hatası!
// ✅ Type union yapabilir
type Sonuc = string | number;
type Durum = "aktif" | "pasif" | "askida";
// ❌ Interface tuple yapamaz
// interface Koordinat = [number, number]; // Sözdizimi hatası!
// ✅ Type tuple yapabilir
type Koordinat = [number, number];
// ❌ Interface primitive'e alias veremez
// interface ID = number; // Sözdizimi hatası!
// ✅ Type verebilir
type ID = number;
// ❌ Interface mapped type yapamaz
// ✅ Type yapabilir
type SaltOkuma<T> = {
readonly [K in keyof T]: T[K];
};4. Computed Properties — Sadece Type
// Mapped/Computed tipler sadece type ile mümkün
type Anahtarlar = "isim" | "email" | "yas";
type KullaniciBilgisi = {
[K in Anahtarlar]: string;
};
// { isim: string; email: string; yas: string }
// Interface bunu yapamazKarar Matrisi
| Kullanım | Interface | Type |
|---|---|---|
| Nesne şekli tanımlama | ✅ (önerilen) | ✅ |
| Class implements | ✅ (önerilen) | ✅ |
| Genişletme (extend) | ✅ extends | ✅ & (intersection) |
| Declaration merging | ✅ | ❌ |
| Union tipler | ❌ | ✅ (tek seçenek) |
| Tuple tipler | ❌ | ✅ (tek seçenek) |
| Primitive alias | ❌ | ✅ (tek seçenek) |
| Mapped types | ❌ | ✅ (tek seçenek) |
| Fonksiyon tipi | ✅ (call signature) | ✅ (daha temiz) |
| IDE hata mesajları | Daha net | Bazen karmaşık |
💡 İpucu — Pratik Kural:
Nesne yapıları (özellikle public API ve class sözleşmeleri) için →
interfaceUnion, tuple, mapped type, fonksiyon tipi için →
typeEmin değilseniz →
interfaceile başlayın, gerekincetype'a geçin. Çoğu proje, ikisini karışık kullanır.
Generics'e Giriş
Generics, TypeScript'in en güçlü özelliklerinden biridir. Bir fonksiyon, interface veya class'ın birden fazla tipte çalışmasını sağlar — tip güvenliğini kaybetmeden.
Analoji: Bir kargo kutusu düşünün. Kutu, içine ne konulursa konsun taşıyabilir — kitap, elektronik, yiyecek. Ama kutuya "Kırılacak" etiketi koyarsanız, herkes içinde kırılacak bir şey olduğunu bilir. Generics de böyledir: fonksiyon veya yapı "kutu" rolü oynar, tip parametresi (<T>) ise içindekinin etiketidir. Kutuyu her kullandığınızda etiketi (tipi) siz belirlersiniz.
Generic Fonksiyonlar
// Problem: Farklı tipler için aynı mantığı tekrarlamak
function ilkSayiAl(dizi: number[]): number | undefined {
return dizi[0];
}
function ilkStringAl(dizi: string[]): string | undefined {
return dizi[0];
}
// Her tip için ayrı fonksiyon mu yazacağız? Hayır!
// ✅ Generic fonksiyon — T herhangi bir tip olabilir
function ilkEleman<T>(dizi: T[]): T | undefined {
return dizi[0];
}
// Kullanım — TypeScript tipi çıkarır
const sayi = ilkEleman([10, 20, 30]); // number | undefined
const metin = ilkEleman(["a", "b", "c"]); // string | undefined
const bool = ilkEleman([true, false]); // boolean | undefined
// Açıkça tip belirtme
const x = ilkEleman<number>([1, 2, 3]); // number | undefined
// Birden fazla tip parametresi
function ciftOlustur<A, B>(birinci: A, ikinci: B): [A, B] {
return [birinci, ikinci];
}
const cift = ciftOlustur("Ahmet", 28); // [string, number]
const cift2 = ciftOlustur(true, [1, 2]); // [boolean, number[]]Generic Interface
// Generic interface — veri tipi parametrik
interface ApiYanit<T> {
basarili: boolean;
veri: T;
mesaj: string;
zamanDamgasi: number;
}
// Farklı tiplerle kullanım
type KullaniciYaniti = ApiYanit<Kullanici>;
type UrunListesiYaniti = ApiYanit<Urun[]>;
type SayacYaniti = ApiYanit<number>;
// Kullanım
function kullaniciBul(id: number): ApiYanit<Kullanici | null> {
// ...
return {
basarili: true,
veri: { id: 1, isim: "Ali", email: "ali@test.com", yas: 28, aktif: true },
mesaj: "Kullanıcı bulundu",
zamanDamgasi: Date.now()
};
}
// Generic interface + extends
interface Deposu<T> {
ekle(item: T): void;
sil(id: number): boolean;
bul(id: number): T | undefined;
hepsiniGetir(): T[];
}Generic Type Alias
// Generic type alias
type Nullable<T> = T | null;
type Cevap<T> = { veri: T; hata: string | null };
type KoleksiyonOf<T> = T[];
// Kullanım
let kullaniciAdi: Nullable<string> = "Ali";
kullaniciAdi = null; // ✅
let cevap: Cevap<number[]> = {
veri: [1, 2, 3],
hata: null
};Generic Constraint (Kısıtlama)
Bazen generic tipin her şey olmasını istemezsiniz — belirli bir yapıya sahip olmasını zorunlu kılabilirsiniz:
// T, en az "id" özelliğine sahip olmalı
interface IdSahibi {
id: number;
}
function kayitBul<T extends IdSahibi>(dizi: T[], id: number): T | undefined {
return dizi.find(item => item.id === id);
}
// ✅ id özelliği var
const kullanicilar = [
{ id: 1, isim: "Ali", email: "ali@test.com" },
{ id: 2, isim: "Ayşe", email: "ayse@test.com" }
];
const bulunan = kayitBul(kullanicilar, 1);
// bulunan: { id: number; isim: string; email: string } | undefined
// ❌ id özelliği yok
// kayitBul([{ isim: "Ali" }], 1);
// Error: Property 'id' is missing in type '{ isim: string; }'
// keyof ile constraint
function ozellikAl<T, K extends keyof T>(nesne: T, anahtar: K): T[K] {
return nesne[anahtar];
}
const kullanici = { isim: "Ali", yas: 28, email: "ali@test.com" };
const isim = ozellikAl(kullanici, "isim"); // string
const yas = ozellikAl(kullanici, "yas"); // number
// ozellikAl(kullanici, "adres"); // ❌ Error: "adres" is not assignable to "isim" | "yas" | "email"Gerçek Dünya Örneği: Generic Veri Deposu
// Generic interface — farklı veri tipleri için aynı yapı
interface VeriModeli {
id: number;
olusturma: Date;
}
interface Depo<T extends VeriModeli> {
ekle(item: Omit<T, "id" | "olusturma">): T;
guncelle(id: number, kismi: Partial<Omit<T, "id" | "olusturma">>): T | null;
sil(id: number): boolean;
bul(id: number): T | null;
listele(): T[];
filtrele(kriter: Partial<T>): T[];
}
// Kullanıcı modeli
interface Kullanici extends VeriModeli {
isim: string;
email: string;
rol: "admin" | "kullanici";
}
// Generic depo implementasyonu
class BellekDeposu<T extends VeriModeli> implements Depo<T> {
private veriler: T[] = [];
private sonrakiId = 1;
ekle(item: Omit<T, "id" | "olusturma">): T {
const yeni = {
...item,
id: this.sonrakiId++,
olusturma: new Date()
} as T;
this.veriler.push(yeni);
return yeni;
}
guncelle(id: number, kismi: Partial<Omit<T, "id" | "olusturma">>): T | null {
const index = this.veriler.findIndex(v => v.id === id);
if (index === -1) return null;
this.veriler[index] = { ...this.veriler[index], ...kismi };
return this.veriler[index];
}
sil(id: number): boolean {
const oncekiUzunluk = this.veriler.length;
this.veriler = this.veriler.filter(v => v.id !== id);
return this.veriler.length < oncekiUzunluk;
}
bul(id: number): T | null {
return this.veriler.find(v => v.id === id) ?? null;
}
listele(): T[] {
return [...this.veriler];
}
filtrele(kriter: Partial<T>): T[] {
return this.veriler.filter(item => {
return Object.entries(kriter).every(
([anahtar, deger]) => item[anahtar as keyof T] === deger
);
});
}
}
// Kullanım — farklı modeller, aynı depo yapısı
const kullaniciDepo = new BellekDeposu<Kullanici>();
const ali = kullaniciDepo.ekle({
isim: "Ali Yılmaz",
email: "ali@test.com",
rol: "admin"
});
console.log(ali.id); // 1 — otomatik atandı
console.log(ali.isim); // "Ali Yılmaz"
kullaniciDepo.ekle({ isim: "Ayşe Kaya", email: "ayse@test.com", rol: "kullanici" });
// Tip güvenli filtreleme
const adminler = kullaniciDepo.filtrele({ rol: "admin" });
console.log(adminler.length); // 1
// Güncelleme
kullaniciDepo.guncelle(1, { email: "ali.yilmaz@test.com" });
// Ürün modeli — aynı depo, farklı tip
interface Urun extends VeriModeli {
isim: string;
fiyat: number;
kategori: string;
stok: number;
}
const urunDepo = new BellekDeposu<Urun>();
urunDepo.ekle({ isim: "Laptop", fiyat: 25000, kategori: "Elektronik", stok: 10 });
urunDepo.ekle({ isim: "Kulaklık", fiyat: 500, kategori: "Elektronik", stok: 50 });
const elektronikler = urunDepo.filtrele({ kategori: "Elektronik" });
console.log(`${elektronikler.length} elektronik ürün`); // 2Bu örnekte:
interface extendsile model hiyerarşisiimplementsile class'ın interface'e uyumuGenerics(<T extends VeriModeli>) ile tip-güvenli, yeniden kullanılabilir depoOmit,Partialgibi utility tipler (sonraki bölümde detaylandırılacak)Tek bir class, farklı veri modelleriyle çalışıyor
Özet
🔹 Interface nesne yapılarını tanımlar: opsiyonel (
?), readonly, metotlar ve index signature destekler. Class'lar içinimplementsile sözleşme zorunlu kılınır.🔹 `extends` ile interface'ler genişletilir — mevcut yapıya yeni özellikler eklenir. Birden fazla interface genişletilebilir (multiple extends).
🔹 Type alias (
type) her tür tipe takma ad verir: nesne, union, tuple, primitive, fonksiyon, mapped type. Interface'in yapamadığı union ve tuple tanımları type ile yapılır.🔹 interface vs type: Nesne yapıları ve class sözleşmeleri için
interface, union/tuple/mapped type içintypetercih edin. Declaration merging sadece interface'te çalışır.🔹 Generics (
<T>) tip-güvenli ve yeniden kullanılabilir yapılar sağlar.extendsile kısıtlama,keyofile anahtar güvenliği eklenir.🔹 Interface + Generics birlikte kullanıldığında, tek bir tanımla farklı veri tipleri için tip-güvenli yapılar oluşturulabilir —
ApiYanit<T>,Depo<T>gibi.
AI Asistan
Sorularını yanıtlamaya hazır