Takip et

TypeScript’te Arayüzlere Giriş

TypeScript’te Arayüzlere Giriş TypeScript, JavaScript’e statik tip denetimi ekleyerek büyük ölçekli uygulamaların geliştirilmesini kolaylaşt

TypeScript’te Arayüzlere Giriş

TypeScript, JavaScript’e statik tip denetimi ekleyerek büyük ölçekli uygulamaların geliştirilmesini kolaylaştıran güçlü bir araçtır. Bu tip sisteminin temel taşlarından biri de “arayüzler” (interfaces) kavramıdır. Arayüzler, kodumuzdaki nesnelerin, fonksiyonların veya sınıfların belirli bir yapıyı takip etmesini sağlamak için kullanılan güçlü bir sözleşme mekanizmasıdır. Bu makalede, TypeScript arayüzlerinin ne olduğunu, neden önemli olduğunu, nasıl tanımlandığını ve çeşitli kullanım senaryolarını ayrıntılı bir şekilde inceleyeceğiz.

Arayüzler Nedir ve Neden Önemlidir?

Arayüzler, bir nesnenin veya sınıfın sahip olması gereken özellikleri ve metotları tanımlayan soyut yapılar veya “şablonlardır”. TypeScript’te arayüzler, derleme zamanında tip denetimi sağlamak için kullanılır ve JavaScript çıktısına dönüştürüldüğünde tamamen ortadan kalkar. Yani, çalışma zamanında herhangi bir performans maliyeti getirmezler.

Tanım ve Temel Amaç

Bir arayüzü, bir nesnenin “şeklini” veya “sözleşmesini” tanımlayan bir yapı olarak düşünebiliriz. Örneğin, bir Kullanıcı nesnesinin mutlaka bir id (sayı) ve bir ad (metin) özelliğine sahip olması gerektiğini belirtmek isteyebiliriz. Arayüzler tam da bu tür gereksinimleri ifade etmemizi sağlar. Temel amacı, farklı kod parçaları arasında tutarlı bir veri yapısı veya davranış modeli oluşturmaktır. Bu sayede, bir bileşen belirli bir arayüzü uyguladığında, o arayüzün gerektirdiği tüm özelliklere ve metotlara sahip olacağını garanti ederiz.

TypeScript’in Tip Sistemindeki Rolü

TypeScript’in statik tip sistemi, geliştiricilerin kodlarındaki potansiyel hataları çalışma zamanından önce tespit etmelerine yardımcı olur. Arayüzler, bu tip sisteminin gücünü artıran kritik bir rol oynar. Bir fonksiyonun beklediği parametrelerin veya bir API’den dönen verilerin belirli bir arayüze uygun olmasını sağlayarak, veri tutarsızlıklarından kaynaklanan hataları önemli ölçüde azaltırız. Bu, özellikle büyük ve karmaşık projelerde, farklı ekiplerin aynı veri yapıları üzerinde çalışırken uyumu sağlamak için hayati öneme sahiptir.

Kod Kalitesi, Okunabilirlik ve Sürdürülebilirlik Faydaları

Arayüzlerin kullanımı, kod kalitesini, okunabilirliğini ve sürdürülebilirliğini çeşitli yollarla artırır:
* Açıklık ve Niyet: Arayüzler, kodun ne beklediğini ve ne sağladığını açıkça belirtir. Bu, diğer geliştiricilerin (veya gelecekteki sizin) kodu daha hızlı anlamasına yardımcı olur.
* Tutarlılık: Farklı modüller veya bileşenler arasında veri yapılarında tutarlılığı sağlar.
* Hata Yakalama: Derleme zamanında tip uyuşmazlıklarını yakalayarak çalışma zamanı hatalarını önler.
* Yeniden Kullanılabilirlik: Ortak veri yapılarını tanımlayarak kod tekrarını azaltır ve yeniden kullanılabilirliği teşvik eder.
* Geliştirici Deneyimi: IDE’lerde otomatik tamamlama (IntelliSense) ve tip denetimi sağlayarak geliştirme sürecini hızlandırır ve hataları azaltır.

Kısacası, arayüzler, TypeScript’i kullanarak daha sağlam, bakımı kolay ve anlaşılır uygulamalar geliştirmek için vazgeçilmez bir araçtır.

Temel Arayüz Tanımı ve Kullanımı

Arayüzler, interface anahtar kelimesi kullanılarak tanımlanır ve bir nesnenin sahip olması gereken özellikleri listeler.

interface Anahtar Kelimesi

Bir arayüz tanımlamak için interface anahtar kelimesini ve ardından arayüzün adını kullanırız. Genellikle arayüz adları PascalCase (ilk harf büyük) ile başlar.

interface Kullanici {
    id: number;
    ad: string;
    email: string;
}

Yukarıdaki örnekte, Kullanici adında bir arayüz tanımladık. Bu arayüz, her Kullanici nesnesinin bir id (sayı), bir ad (metin) ve bir email (metin) özelliğine sahip olması gerektiğini belirtir.

Özellik Tanımlama (Properties)

Arayüz içinde özellikler, JavaScript nesnelerindeki gibi özellikAdı: tip şeklinde tanımlanır.

interface Urun {
    urunId: number;
    urunAdi: string;
    fiyat: number;
    stokDurumu: boolean;
}

// Bu nesne Urun arayüzüne uygundur
let laptop: Urun = {
    urunId: 101,
    urunAdi: "Dizüstü Bilgisayar",
    fiyat: 12500.00,
    stokDurumu: true
};

// Bu nesne Urun arayüzüne uygun değildir (fiyat eksik)
/*
let telefon: Urun = {
    urunId: 102,
    urunAdi: "Akıllı Telefon",
    stokDurumu: true // Hata: 'fiyat' özelliği eksik
};
*/

TypeScript derleyicisi, telefon nesnesinin Urun arayüzüne uymadığını (çünkü fiyat özelliği eksik) belirten bir hata verecektir.

Opsiyonel Özellikler (?)

Bazı özelliklerin her zaman mevcut olması gerekmeyebilir. Bu tür durumlar için opsiyonel özellikler tanımlayabiliriz. Bir özelliğin adının sonuna ? işareti ekleyerek opsiyonel olduğunu belirtiriz.

interface Ayarlar {
    tema: string;
    dil?: string; // Opsiyonel özellik
    bildirimlerAcikMi?: boolean; // Opsiyonel özellik
}

let varsayilanAyarlar: Ayarlar = {
    tema: "koyu"
};

let kullaniciAyarlari: Ayarlar = {
    tema: "açık",
    dil: "tr",
    bildirimlerAcikMi: true
};

let misafirAyarlari: Ayarlar = {
    tema: "sistem",
    bildirimlerAcikMi: false
};

Yukarıdaki örnekte, dil ve bildirimlerAcikMi özellikleri opsiyoneldir. Bu, Ayarlar arayüzünü uygulayan bir nesnenin bu özellikleri içermeyebileceği anlamına gelir.

Salt Okunur Özellikler (readonly)

Bir nesne oluşturulduktan sonra belirli özelliklerinin değiştirilememesini isteyebiliriz. readonly anahtar kelimesini kullanarak bu tür salt okunur özellikleri tanımlayabiliriz.

interface Nokta {
    readonly x: number;
    readonly y: number;
    z: number; // Bu özellik değiştirilebilir
}

let p1: Nokta = { x: 10, y: 20, z: 30 };
// p1.x = 5; // Hata: 'x' salt okunur bir özelliktir.
// p1.y = 15; // Hata: 'y' salt okunur bir özelliktir.
p1.z = 40; // Geçerli

readonly özelliği, özellikle bir nesnenin ilk oluşturulduğunda aldığı değerlerin sonradan değiştirilmemesi gereken durumlar için kullanışlıdır (örneğin, bir nesnenin ID’si veya bir yapılandırma sabiti).

Arayüzlerde Metot Tanımlama

Arayüzler sadece özelliklerin değil, aynı zamanda nesnelerin sahip olması gereken metotların imzalarını da tanımlayabilir. Bu, belirli bir davranışa sahip olması gereken nesneler için bir sözleşme oluşturmamızı sağlar.

Fonksiyon İmzaları

Bir metot tanımlamak için, metot adını, parantez içinde parametrelerini ve ardından dönüş tipini belirtiriz.

interface HesapMakinesi {
    topla(a: number, b: number): number;
    cikar(a: number, b: number): number;
    carp?(a: number, b: number): number; // Opsiyonel metot
}

Yukarıdaki HesapMakinesi arayüzü, topla ve cikar adında iki metot tanımlar. Her iki metot da iki sayısal parametre alır ve bir sayı döndürür. carp metodu ise opsiyoneldir.

Parametreler ve Dönüş Tipleri

Metot tanımlarken, her parametrenin adını ve tipini belirtmeli, ayrıca metodun dönüş tipini de açıkça ifade etmeliyiz.

let basitHesapMakinesi: HesapMakinesi = {
    topla: function(x: number, y: number): number {
        return x + y;
    },
    cikar: (x: number, y: number): number => {
        return x - y;
    }
};

console.log(basitHesapMakinesi.topla(5, 3)); // Çıktı: 8
console.log(basitHesapMakinesi.cikar(10, 4)); // Çıktı: 6

let gelismisHesapMakinesi: HesapMakinesi = {
    topla: (x, y) => x + y,
    cikar: (x, y) => x - y,
    carp: (x, y) => x * y // Opsiyonel metodu uyguladık
};

console.log(gelismisHesapMakinesi.carp?.(2, 6)); // Çıktı: 12 (opsiyonel zincirleme ile güvenli çağrı)

Metotları uygularken, arayüzde belirtilen parametre tipleri ve dönüş tipleriyle uyumlu olmaları gerekir. TypeScript, bu uyumsuzlukları derleme zamanında tespit edecektir.

Fonksiyon Tiplerini Tanımlayan Arayüzler

Arayüzler, sadece nesne özelliklerini ve metotlarını değil, aynı zamanda doğrudan fonksiyon tiplerini de tanımlamak için kullanılabilir. Bu, özellikle fonksiyon parametreleri olarak beklenen veya fonksiyon döndüren fonksiyonlar için kullanışlıdır.

Arayüzlerin Direkt Olarak Fonksiyon Tipi Olarak Kullanılması

Bir arayüz, sadece bir çağrı imzası (call signature) içeriyorsa, bu arayüzü bir fonksiyon tipi olarak kullanabiliriz.

interface Karsilastirici {
    (kaynak: string, altDize: string): boolean;
}

Bu Karsilastirici arayüzü, iki string parametresi alan ve bir boolean döndüren bir fonksiyon tipini temsil eder.

Fonksiyon Arayüzleri Oluşturma

Bu arayüzü bir değişken veya parametre tipi olarak atayabiliriz:

let stringArama: Karsilastirici;

stringArama = function(s: string, sub: string): boolean {
    let sonuc = s.indexOf(sub);
    return sonuc > -1;
};

console.log(stringArama("Merhaba Dünya", "Dünya")); // Çıktı: true
console.log(stringArama("TypeScript", "Java")); // Çıktı: false

Burada, stringArama değişkeni Karsilastirici arayüzüne uygun bir fonksiyon olarak atanmıştır. TypeScript, atanan fonksiyonun parametre tiplerinin ve dönüş tipinin arayüzdeki çağrı imzasıyla eşleştiğini doğrular. Parametre isimleri farklı olabilir, önemli olan tiplerin sırası ve uyumudur.

// Parametre isimleri farklı olabilir
stringArama = function(sira: string, altMetin: string): boolean {
    let sonuc = sira.indexOf(altMetin);
    return sonuc > -1;
};

Bu kullanım, özellikle callback fonksiyonları veya bir fonksiyonun parametre olarak belirli bir imzaya sahip bir başka fonksiyonu beklediği durumlarda kodun daha okunabilir ve güvenli olmasını sağlar.

İndekslenebilir Tipler İçin Arayüzler

JavaScript’te diziler ve nesneler, sayısal veya string indekslerle erişilebilen koleksiyonlardır. TypeScript’te arayüzler, bu tür indekslenebilir tiplerin yapısını tanımlamak için de kullanılabilir.

Dizi Benzeri veya Obje Benzeri Erişim

İndekslenebilir bir arayüz, bir diziye veya bir objeye arr[index] veya obj["key"] şeklinde erişildiğinde döndürülecek tipi belirtir.

interface StringDizisi {
    [index: number]: string;
}

Bu StringDizisi arayüzü, sayısal bir indeksle erişildiğinde string tipi döndüren bir dizi benzeri yapıyı tanımlar.

let myStringArray: StringDizisi;
myStringArray = ["Elma", "Armut", "Kiraz"];

console.log(myStringArray[0]); // Çıktı: Elma
console.log(myStringArray[1]); // Çıktı: Armut

// myStringArray[0] = 123; // Hata: 'number' tipi 'string' tipine atanamaz.

String ve Number İndeks İmzaları

İndeks imzaları, string veya number tipinde olabilir. Ancak, bir arayüz hem sayısal hem de string indeks imzası tanımlıyorsa, sayısal indeks imzasının dönüş tipi, string indeks imzasının dönüş tipinin bir alt tipi olmalıdır. Bunun nedeni, JavaScript’te sayısal indekslerin dahili olarak string’e dönüştürülmesidir (örn. arr[0] aslında arr["0"] olarak erişilir).

interface Dictionary {
    [key: string]: any; // String indeks imzası
}

let myDictionary: Dictionary = {
    "name": "Alice",
    "age": 30,
    "isActive": true
};

console.log(myDictionary["name"]); // Çıktı: Alice
console.log(myDictionary.age); // Nokta notasyonu da kullanılabilir

interface ReadonlyStringDictionary {
    readonly [key: string]: string; // Salt okunur string indeks imzası
}

let myConfig: ReadonlyStringDictionary = {
    "apiUrl": "https://api.example.com",
    "version": "1.0.0"
};

// myConfig["apiUrl"] = "new-url"; // Hata: İndeks imzası salt okunurdur.

İndeks imzaları, özellikle dinamik özelliklere sahip nesnelerle çalışırken veya bir koleksiyonun belirli bir yapıya sahip olmasını garanti etmek istediğimizde çok kullanışlıdır.

Sınıfları Uygulayan Arayüzler

Arayüzler, sınıfların belirli bir yapıyı veya davranışı uygulamasını zorunlu kılmak için de kullanılabilir. Bu, nesne tabanlı programlamada “sözleşme” kavramının önemli bir parçasıdır.

implements Anahtar Kelimesi

Bir sınıfın bir arayüzü uyguladığını belirtmek için implements anahtar kelimesini kullanırız. Bir sınıf bir arayüzü uyguladığında, arayüzde tanımlanan tüm özellikleri ve metotları kendi içinde bulundurmak zorundadır.

interface Gosterilebilir {
    goster(): string;
}

class KullaniciBilgisi implements Gosterilebilir {
    constructor(public ad: string, public soyad: string) {}

    goster(): string {
        return ${this.ad} ${this.soyad};
    }
}

class UrunBilgisi implements Gosterilebilir {
    constructor(public urunAdi: string, public fiyat: number) {}

    goster(): string {
        return ${this.urunAdi} - ${this.fiyat} TL;
    }
}

let kullanici = new KullaniciBilgisi("Ali", "Can");
let urun = new UrunBilgisi("Kitap", 50);

console.log(kullanici.goster()); // Çıktı: Ali Can
console.log(urun.goster());     // Çıktı: Kitap - 50 TL

// Polimorfizm örneği
function bilgiyiGoster(nesne: Gosterilebilir) {
    console.log(nesne.goster());
}

bilgiyiGoster(kullanici);
bilgiyiGoster(urun);

Yukarıdaki örnekte, Gosterilebilir arayüzü, goster() adında bir metot tanımlar. KullaniciBilgisi ve UrunBilgisi sınıfları bu arayüzü uyguladıkları için goster() metodunu kendi içlerinde barındırmak zorundadırlar. Bu, bilgiyiGoster gibi fonksiyonların, Gosterilebilir arayüzünü uygulayan herhangi bir nesneyle çalışabilmesini sağlar; bu da polimorfizmin güzel bir örneğidir.

Statik ve Örnek Metotlar

Bir sınıf bir arayüzü uyguladığında, yalnızca sınıfın örnek (instance) tarafı denetlenir. Sınıfın statik tarafı (yani sınıfın kendisine ait olan statik özellikler ve metotlar) arayüz tarafından denetlenmez.

interface SaatConstructor {
    new (saat: number, dakika: number): SaatInterface;
}

interface SaatInterface {
    tick(): void;
}

function saatOlustur(ctor: SaatConstructor, saat: number, dakika: number): SaatInterface {
    return new ctor(saat, dakika);
}

class DijitalSaat implements SaatInterface {
    constructor(h: number, m: number) { / ... / }
    tick() {
        console.log("tik tak (dijital)");
    }
}

class AnalogSaat implements SaatInterface {
    constructor(h: number, m: number) { / ... / }
    tick() {
        console.log("tik tak (analog)");
    }
}

let dijital = saatOlustur(DijitalSaat, 12, 17);
let analog = saatOlustur(AnalogSaat, 7, 32);

dijital.tick();
analog.tick();

Bu örnekte, SaatConstructor arayüzü bir sınıfın constructor’ının imzasını tanımlar. Bu biraz daha gelişmiş bir kullanım olup, bir sınıfın kendisini bir arayüz olarak nasıl kullanabileceğini gösterir. Genellikle, sınıflar doğrudan örnek metotlarını ve özelliklerini tanımlayan arayüzleri uygular.

Arayüzlerin Genişletilmesi (Extending Interfaces)

Arayüzler, tıpkı sınıflar gibi birbirlerini genişletebilirler. Bu, mevcut bir arayüze yeni özellikler ekleyerek veya birden fazla arayüzü birleştirerek daha spesifik arayüzler oluşturmamızı sağlar.

extends Anahtar Kelimesi

Bir arayüzü başka bir arayüzden genişletmek için extends anahtar kelimesini kullanırız. Genişleten arayüz, genişletilen arayüzün tüm üyelerine (özellikler ve metotlar) sahip olur ve üzerine yeni üyeler ekleyebilir.

interface Sekil {
    renk: string;
}

interface Kare extends Sekil {
    kenarUzunlugu: number;
}

let kare1: Kare = {
    renk: "mavi",
    kenarUzunlugu: 10
};

// kare1.genislik = 5; // Hata: 'Kare' tipinde 'genislik' özelliği yok.

Kare arayüzü, Sekil arayüzünden renk özelliğini miras alır ve kendi kenarUzunlugu özelliğini ekler.

Birden Fazla Arayüzü Genişletme

Bir arayüz, birden fazla arayüzü de genişletebilir. Bu durumda, genişleten arayüz, tüm genişletilen arayüzlerin üyelerinin birleşimine sahip olur.

interface Kalem {
    marka: string;
}

interface Silgi {
    silmeKapasitesi: number;
}

interface KalemSilgi extends Kalem, Silgi {
    model: string;
}

let kalemSilgi1: KalemSilgi = {
    marka: "Faber-Castell",
    silmeKapasitesi: 100,
    model: "Grippy"
};

console.log(kalemSilgi1.marka);
console.log(kalemSilgi1.silmeKapasitesi);
console.log(kalemSilgi1.model);

KalemSilgi arayüzü, hem Kalem hem de Silgi arayüzlerinin özelliklerini miras alır ve üzerine model özelliğini ekler. Bu, farklı özellik setlerini birleştirerek karmaşık tipler oluşturmak için çok güçlü bir yöntemdir.

Kalıtım ve Yeniden Kullanılabilirlik

Arayüz genişletme, kodda kalıtım ve yeniden kullanılabilirlik ilkelerini destekler. Daha genel arayüzler tanımlayabilir ve bunları daha özel durumlar için genişleterek DRY (Don’t Repeat Yourself) prensibine uyabiliriz. Bu, kodun daha modüler, anlaşılır ve bakımı kolay olmasını sağlar. Örneğin, bir TemelAPIYaniti arayüzü tanımlayıp, tüm API yanıt arayüzlerimizi bundan genişleterek ortak alanları (örn. durumKodu, mesaj) tek bir yerde tanımlayabiliriz.

Hibrit Tipler (Hybrid Types)

JavaScript’in dinamik doğası gereği, bazı nesneler hem fonksiyon olarak çağrılabilir hem de özelliklere sahip olabilir. TypeScript’teki arayüzler, bu tür “hibrit tipleri” tanımlamak için de kullanılabilir.

Hem Fonksiyon Hem de Özelliklere Sahip Arayüzler

Bir arayüz, hem bir çağrı imzası hem de normal özellikler içerebilir.

interface Sayac {
    (baslangic: number): string; // Çağrı imzası (fonksiyon tarafı)
    aralik: number;             // Özellik (nesne tarafı)
    reset(): void;              // Metot (nesne tarafı)
}

function getSayac(): Sayac {
    let sayac = function (baslangic: number) {
        return Sayac ${baslangic}'den başladı.;
    };
    sayac.aralik = 10;
    sayac.reset = function () {
        console.log("Sayaç sıfırlandı.");
    };
    return sayac;
}

let c = getSayac();
console.log(c(5)); // Fonksiyon olarak çağrı: Çıktı: Sayac 5'den başladı.
c.reset();         // Metot çağrısı: Çıktı: Sayaç sıfırlandı.
console.log(c.aralik); // Özellik erişimi: Çıktı: 10

Bu örnekte, Sayac arayüzü hem bir fonksiyon olarak çağrılabilen (bir number alıp string döndüren) hem de aralik adında bir sayısal özelliğe ve reset() adında bir metoda sahip bir yapıyı tanımlar. getSayac fonksiyonu bu arayüze uygun bir nesne döndürür.

Nesne Tabanlı Programlamadaki Kullanım Alanları

Hibrit tipler, özellikle belirli bir global nesnenin hem konfigürasyon özelliklerine sahip olduğu hem de belirli bir eylemi gerçekleştirdiği durumlarda kullanışlıdır. Örneğin, bir logger nesnesi hem log() metotlarına sahip olabilir hem de debugMode gibi konfigürasyon özelliklerini tutabilir. Veya bir fabrika fonksiyonu, hem kendisi bir nesne döndürebilir hem de o nesnenin üzerinde metotlar barındırabilir. Bu, JavaScript’in esnek doğasını TypeScript’in tip güvenliğiyle birleştirmenin bir yoludur.

Arayüzler ve Tip Takma Adları (Type Aliases) Arasındaki Farklar

TypeScript’te arayüzlere benzer bir diğer kavram da tip takma adları (type aliases) veya type anahtar kelimesidir. Her ikisi de yeni tipler tanımlamak için kullanılsa da, aralarında önemli farklar bulunur.

interface vs type

| Özellik | interface | type |
| :——————— | :———————————————- | :————————————————– |
| Genişletilebilirlik | extends ile genişletilebilir. | & (intersection) ile birleştirilebilir. |
| Birleşme (Declaration Merging) | Aynı isimde birden fazla arayüz tanımlandığında otomatik olarak birleşirler. | Birleşme özelliği yoktur; aynı isimde iki type tanımı hata verir. |
| Kullanım Alanları | Genellikle nesnelerin şeklini tanımlamak için kullanılır. | Primitif tipler, birleşim tipleri (union types), tuple’lar, fonksiyon tipleri ve nesne tipleri için kullanılabilir. |
| Sınıflarla Kullanım | implements anahtar kelimesiyle sınıflar tarafından uygulanabilir. | implements ile sınıflar tarafından uygulanamaz (sadece nesne tipleri için). |
| Daha Kompleks Tipler | Basit nesne yapıları için idealdir. | Birleşim tipleri (|), kesişim tipleri (&), koşullu tipler ve tuple’lar gibi daha karmaşık tipler oluşturmak için daha esnektir. |

// Interface Genişletme ve Birleşme
interface Kisi {
    ad: string;
}

interface Kisi { // Aynı isimde tekrar tanımlanabilir ve birleşir
    yas: number;
}

interface Calisan extends Kisi { // Genişletilebilir
    sicilNo: string;
}

let calisan1: Calisan = { ad: "Ayşe", yas: 30, sicilNo: "TS123" };


// Type Takma Adı ile Genişletme (Intersection)
type Personel = {
    ad: string;
};

// type Personel = { // Hata: 'Personel' zaten tanımlanmış.
//     yas: number;
// };

type Yonetici = Personel & { // & ile birleştirme
    departman: string;
};

let yonetici1: Yonetici = { ad: "Mehmet", departman: "IK" };
// yonetici1.yas = 40; // Hata: 'yas' özelliği yok (Personel type'ına eklenmediği için)

// Type takma adları ile birleşim tipleri (union types)
type Durum = "başarılı" | "hata" | "beklemede";
let islemDurumu: Durum = "başarılı";

// Type takma adları ile fonksiyon tipleri
type FonksiyonTipi = (a: number, b: number) => number;
let carpma: FonksiyonTipi = (x, y) => x * y;

Ne Zaman Hangisini Kullanmalı?

* interface kullanın:
* Bir nesnenin yapısını tanımlamak istediğinizde.
* Sınıflar tarafından uygulanacak bir sözleşme oluşturduğunuzda (implements).
* Otomatik birleşme özelliğinden faydalanmak istediğinizde (örneğin, bir kütüphane veya modülün global tiplerini genişletmek için).
* API yanıtları, konfigürasyon nesneleri gibi veri yapılarını tanımlarken.

* type kullanın:
* Birleşim tipleri (union types) veya kesişim tipleri (intersection types) gibi karmaşık tipler oluşturmak istediğinizde.
* Primitif tipler, tuple’lar veya fonksiyon tipleri için takma adlar oluşturduğunuzda.
* typeof veya keyof gibi operatörlerle birlikte tipleri dinamik olarak oluşturduğunuzda.

Genel bir kural olarak, bir nesnenin şeklini tanımlıyorsanız interface kullanın. Diğer tüm tip tanımlama ihtiyaçları için type kullanın. Çoğu durumda, her ikisi de benzer sonuçlar verebilir, ancak yukarıdaki farklar belirli senaryolarda birinin diğerine göre daha uygun olmasını sağlar.

Gelişmiş Arayüz Kullanım Senaryoları

Arayüzler, temel kullanımlarının ötesinde daha karmaşık ve esnek senaryolar için de kullanılabilir.

Generic Arayüzler

Generic’ler, bir bileşenin veya fonksiyonun birden fazla tipte veriyle çalışmasına olanak tanırken tip güvenliğini korur. Arayüzler de generic olabilir, bu da onları daha esnek hale getirir.

interface Kutu {
    icerik: T;
}

let sayiKutusu: Kutu = { icerik: 123 };
let metinKutusu: Kutu = { icerik: "Merhaba" };

// let booleanKutusu: Kutu = { icerik: "true" }; // Hata: 'string' tipi 'boolean' tipine atanamaz.

interface AnahtarDegerCifti {
    anahtar: K;
    deger: V;
}

let ayar: AnahtarDegerCifti = { anahtar: "port", deger: 8080 };
let kullaniciAyari: AnahtarDegerCifti = { anahtar: "darkMode", deger: true };

Generic arayüzler, farklı veri tipleriyle çalışabilen yeniden kullanılabilir veri yapıları oluşturmak için mükemmeldir. Örneğin, bir generic List arayüzü, hem sayı listeleri hem de string listeleri için kullanılabilir.

Koşullu Tipler ile Arayüzler

TypeScript 2.8 ile tanıtılan koşullu tipler (conditional types), bir tipin başka bir tipe atanabilir olup olmadığına bağlı olarak farklı tipler seçmemize olanak tanır. Arayüzler, bu koşullu tipleri doğrudan tanımlayamaz, ancak koşullu tiplerden türetilen tipleri uygulayabilir veya kullanabilirler. Bu, daha dinamik ve esnek arayüzler oluşturmanın dolaylı bir yoludur.

type MesajTipi = T extends string ? { metin: string } : { veri: T };

interface Yanit {
    status: number;
    payload: MesajTipi;
}

let stringYanit: Yanit = {
    status: 200,
    payload: { metin: "İşlem başarılı" }
};

let objeYanit: Yanit = {
    status: 200,
    payload: { veri: { id: 1, name: "Test" } }
};

// stringYanit.payload.veri; // Hata: 'metin' özelliği var, 'veri' yok.

Bu örnekte, Yanit arayüzünün payload özelliği, generic T tipine bağlı olarak farklı bir yapıya sahip olabilir. Bu, arayüzlerin çok çeşitli senaryolara uyum sağlamasına olanak tanır.

Kullanım Alanları (API Sözleşmeleri, Konfigürasyon Nesneleri, Durum Yönetimi)

Arayüzlerin pratik kullanım alanları oldukça geniştir:
* API Sözleşmeleri: Backend API’lerinden beklenen veya gönderilen veri yapılarını tanımlamak için kullanılır. Bu, frontend ve backend ekipleri arasında net bir sözleşme oluşturur ve entegrasyon hatalarını azaltır.
* Konfigürasyon Nesneleri: Uygulama ayarları veya bileşen konfigürasyonları için kullanılan nesnelerin yapısını tanımlar. Bu, geçersiz veya eksik ayarların derleme zamanında yakalanmasını sağlar.
* Durum Yönetimi: Redux, Vuex gibi durum yönetim kütüphanelerinde state’in (durumun) yapısını tanımlamak için arayüzler kullanılır. Bu, state’in tutarlı kalmasını ve beklenmedik mutasyonların önlenmesini sağlar.
* Veri Modelleri: Veritabanı kayıtları, form verileri veya diğer iş nesnelerinin modellerini tanımlayarak uygulamanın veri katmanında tip güvenliği sağlar.
* Mocking ve Test: Test yazarken sahte (mock) nesneler oluşturmak için arayüzler kullanılabilir. Bir arayüzü uygulayan sahte bir sınıf oluşturarak, gerçek bağımlılıklar olmadan bileşenleri test edebiliriz.

Sonuç

TypeScript’teki arayüzler, kodumuzun sağlamlığını, okunabilirliğini ve sürdürülebilirliğini artıran temel bir özelliktir. Nesnelerin, fonksiyonların ve sınıfların belirli bir yapıyı veya davranışı takip etmesini sağlayan güçlü sözleşmeler oluşturmamıza olanak tanır. Temel özellik tanımlamalarından, metot imzalarına, indekslenebilir tiplere, sınıfları uygulamaya ve hatta generic yapılarla karmaşık senaryolara kadar geniş bir kullanım yelpazesine sahiptirler.

Arayüzler ve tip takma adları arasındaki farkları anlamak, doğru aracı doğru senaryoda kullanmak için kritik öneme sahiptir. interface genellikle nesne şekillerini ve sınıflar için sözleşmeleri tanımlamakta tercih edilirken, type daha karmaşık tip manipülasyonları ve birleşim tipleri için daha esnektir.

TypeScript geliştirirken arayüzleri etkin bir şekilde kullanmak, daha az hata, daha iyi geliştirici deneyimi ve uzun vadede daha bakımı kolay uygulamalar anlamına gelir. Bu makale, TypeScript arayüzlerinin temellerini ve çeşitli kullanım alanlarını anlamanız için kapsamlı bir başlangıç noktası sağlamıştır. Artık bu güçlü özelliği kendi projelerinizde kullanarak kod kalitenizi bir üst seviyeye taşıyabilirsiniz.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık