TypeScript’te Fonksiyonları Etkin Bir Şekilde Kullanma Rehberi
Giriş
Modern web uygulamalarının karmaşıklığı arttıkça, kod tabanının yönetilebilir, ölçeklenebilir ve hatasız olması kritik bir önem taşımaktadır. JavaScript’in dinamik yapısı esneklik sunsa da, büyük ölçekli projelerde tip güvenliği eksikliği beklenmedik hatalara yol açabilir. İşte bu noktada TypeScript devreye girer. TypeScript, JavaScript’in bir üst kümesidir ve isteğe bağlı statik tipleme ile geliştiricilere daha güvenli ve öngörülebilir kod yazma imkanı sunar. Fonksiyonlar, herhangi bir programlama dilinin temel yapı taşlarından biridir ve TypeScript’te fonksiyonları doğru ve etkin bir şekilde kullanmak, kod kalitesini önemli ölçüde artırır.
TypeScript’in Gücü ve Fonksiyonların Rolü
TypeScript, fonksiyonlara tip bilgisi ekleyerek geliştirme sürecini dönüştürür. Parametrelere ve dönüş değerlerine tip atayarak, derleme zamanında potansiyel hataları yakalamamızı sağlar. Bu, çalışma zamanı hatalarını azaltır, kodun okunabilirliğini artırır ve refaktöring işlemlerini çok daha güvenli hale getirir. Fonksiyonlar, belirli bir görevi yerine getiren yeniden kullanılabilir kod bloklarıdır. TypeScript ile bu blokları daha güçlü, daha anlaşılır ve daha az hataya açık hale getirebiliriz. Bu makale, TypeScript’te fonksiyonları tüm yönleriyle ele alacak, temel tanımlamalardan ileri seviye kullanımlara kadar geniş bir yelpazede bilgi sunacaktır.
Fonksiyonların Temelleri
TypeScript’te fonksiyonlar, JavaScript’teki gibi çeşitli şekillerde tanımlanabilir. Ancak TypeScript, bu tanımlamalara tip güvenliği katmanı ekler. Temel olarak üç ana yolla fonksiyon tanımlayabiliriz: isimli fonksiyonlar, anonim fonksiyonlar ve ok (arrow) fonksiyonlar.
Fonksiyon Tanımlama
İsimli fonksiyonlar, en geleneksel tanımlama yöntemidir. Fonksiyonun bir adı vardır ve bu adla çağrılır. TypeScript’te parametrelere ve dönüş değerine tip ataması yaparız.
function topla(sayi1: number, sayi2: number): number {
return sayi1 + sayi2;
}
let sonuc: number = topla(5, 3); // sonuc: 8
console.log(sonuc);
Yukarıdaki örnekte, topla fonksiyonu iki adet number tipinde parametre alır ve bir number tipi döndürür. TypeScript, bu tiplere uygun olmayan bir çağrı yapıldığında derleme zamanı hatası verecektir.
Anonim Fonksiyonlar
Anonim fonksiyonlar, bir isme sahip olmayan fonksiyonlardır ve genellikle bir değişkene atanır veya başka bir fonksiyona argüman olarak geçirilir. TypeScript’te anonim fonksiyonlar da tip güvenliği sağlar.
let carp: (x: number, y: number) => number = function(x: number, y: number): number {
return x * y;
};
let carpmaSonucu: number = carp(4, 2); // carpmaSonucu: 8
console.log(carpmaSonucu);
Burada, carp değişkenine bir fonksiyon tipi atadık: (x: number, y: number) => number. Bu, değişkenin iki number parametre alan ve bir number döndüren bir fonksiyon olacağını belirtir. Fonksiyonun kendisi de bu tiplere uygun olarak tanımlanmıştır.
Fonksiyon Parametreleri
Fonksiyon parametreleri, TypeScript’in tip güvenliği özelliklerinin en belirgin olduğu alanlardan biridir. Parametreleri zorunlu, opsiyonel, varsayılan değerli veya rest parametreleri olarak tanımlayabiliriz.
Zorunlu Parametreler
Varsayılan olarak, TypeScript’teki tüm fonksiyon parametreleri zorunludur. Yani, fonksiyon çağrılırken tüm tanımlı parametreler için bir değer sağlanmalıdır.
function selamla(isim: string, soyisim: string): string {
return Merhaba, ${isim} ${soyisim}!;
}
console.log(selamla("Ayşe", "Yılmaz")); // Merhaba, Ayşe Yılmaz!
// console.log(selamla("Ahmet")); // Hata: 'soyisim' argümanı eksik
Eğer soyisim parametresi olmadan selamla fonksiyonunu çağırmaya çalışırsak, TypeScript derleme zamanı hatası verir.
Opsiyonel Parametreler
Bazen bir parametrenin sağlanması isteğe bağlı olabilir. Bu durumda, parametre adının sonuna ? işareti ekleyerek opsiyonel olduğunu belirtiriz.
function tamAd(ilkIsim: string, ortaIsim?: string, soyIsim: string): string {
if (ortaIsim) {
return ${ilkIsim} ${ortaIsim} ${soyIsim};
} else {
return ${ilkIsim} ${soyIsim};
}
}
console.log(tamAd("Can", "Demir")); // Hata: 'soyIsim' argümanı eksik. Opsiyonel parametreler zorunlu parametrelerden sonra gelmelidir.
// Doğru kullanım:
function tamAdDuzeltilmis(ilkIsim: string, soyIsim: string, ortaIsim?: string): string {
if (ortaIsim) {
return ${ilkIsim} ${ortaIsim} ${soyIsim};
} else {
return ${ilkIsim} ${soyIsim};
}
}
console.log(tamAdDuzeltilmis("Ayşe", "Yılmaz")); // Ayşe Yılmaz
console.log(tamAdDuzeltilmis("Can", "Kara", "Ali")); // Can Ali Kara
Önemli Not: Opsiyonel parametreler, zorunlu parametrelerden sonra gelmelidir. Aksi takdirde, TypeScript bir hata verir.
Varsayılan Değerli Parametreler
Bir parametreye varsayılan bir değer atayarak, bu parametrenin opsiyonel olmasını sağlayabiliriz. Eğer fonksiyon çağrılırken bu parametre için bir değer sağlanmazsa, varsayılan değeri kullanılır.
function indirimUygula(fiyat: number, indirimOrani: number = 0.10): number {
return fiyat * (1 - indirimOrani);
}
console.log(indirimUygula(100)); // 90 (indirimOrani varsayılan olarak 0.10)
console.log(indirimUygula(200, 0.25)); // 150 (indirimOrani 0.25 olarak verildi)
Varsayılan değerli parametreler de opsiyonel parametreler gibi davranır ve genellikle zorunlu parametrelerden sonra tanımlanmalıdır.
Rest Parametreler
Bazen bir fonksiyona kaç tane argüman geçirileceğini önceden bilemeyiz. Bu durumlarda, “rest parametreler” (...) kullanarak kalan tüm argümanları bir dizi olarak yakalayabiliriz.
function toplamHesapla(...sayilar: number[]): number {
let toplam = 0;
for (let i = 0; i < sayilar.length; i++) {
toplam += sayilar[i];
}
return toplam;
}
console.log(toplamHesapla(1, 2, 3)); // 6
console.log(toplamHesapla(10, 20, 30, 40)); // 100
Rest parametreler her zaman son parametre olmalıdır ve bir dizi tipiyle tanımlanır.
Fonksiyon Dönüş Tipleri
TypeScript, fonksiyonların ne tür bir değer döndüreceğini de belirtmemize olanak tanır. Bu, kodun öngörülebilirliğini artırır ve dönüş değerinin yanlış kullanılmasını engeller.
Belirli Dönüş Tipleri
En yaygın kullanım, fonksiyonun belirli bir tipte değer döndürmesini beklemektir.
function buyukHarfYap(metin: string): string {
return metin.toUpperCase();
}
function kareAl(sayi: number): number {
return sayi * sayi;
}
console.log(buyukHarfYap("typescript")); // TYPESCRIPT
console.log(kareAl(7)); // 49
void Dönüş Tipi
Bir fonksiyon hiçbir değer döndürmüyorsa, dönüş tipi olarak void kullanırız. Bu, fonksiyonun yan etkileri olduğunu ancak bir sonuç üretmediğini belirtir.
function logMesaji(mesaj: string): void {
console.log(mesaj);
}
logMesaji("Merhaba TypeScript!"); // Konsola "Merhaba TypeScript!" yazar, fonksiyon bir değer döndürmez.
never Dönüş Tipi
never tipi, fonksiyonun asla bir değer döndürmeyeceğini veya hiçbir zaman normal bir şekilde tamamlanmayacağını belirtir. Bu genellikle bir hata fırlatan veya sonsuz döngüye giren fonksiyonlar için kullanılır.
function hataFirlat(mesaj: string): never {
throw new Error(mesaj);
}
function sonsuzDongu(): never {
while (true) {
// ...
}
}
// hataFirlat("Kritik bir hata oluştu!"); // Bu satır programı durdurur.
never, void'den farklıdır çünkü void bir fonksiyonun undefined döndürebileceği anlamına gelirken, never fonksiyonun asla bir noktaya ulaşamayacağını (yani, kontrol akışının asla geri dönmeyeceğini) ifade eder.
unknown ve any Dönüş Tipleri
unknown ve any tipleri, fonksiyonların dönüş değeri hakkında çok az veya hiç bilgiye sahip olmadığımız durumlarda kullanılabilir, ancak any kullanmaktan kaçınmak genellikle en iyi uygulamadır. unknown, any'den daha güvenli bir alternatiftir çünkü unknown tipindeki bir değeri kullanmadan önce tip kontrolü yapmanızı zorunlu kılar.
function bilinmeyenVeriGetir(): unknown {
// API'den gelen veya dinamik bir veri
return { id: 1, name: "Ürün A" };
}
let veri: unknown = bilinmeyenVeriGetir();
// console.log(veri.name); // Hata: 'veri' tipi 'unknown'.
// Kullanmadan önce tip kontrolü yapılmalı:
if (typeof veri === 'object' && veri !== null && 'name' in veri) {
console.log((veri as { name: string }).name); // Ürün A
}
any ise tip kontrolünü tamamen devre dışı bırakır ve bu nedenle dikkatli kullanılmalıdır.
Arrow Fonksiyonlar (Ok Fonksiyonlar)
ES6 ile birlikte tanıtılan arrow fonksiyonlar, TypeScript'te de yaygın olarak kullanılır. Daha kısa sözdizimine sahiptirler ve özellikle this bağlamını ele alış biçimleri nedeniyle avantajlıdırlar.
Sözdizimi ve Avantajları
Arrow fonksiyonların sözdizimi daha kompakttır:
// Geleneksel fonksiyon
const toplaGeleneksel = function(a: number, b: number): number {
return a + b;
};
// Arrow fonksiyon
const toplaArrow = (a: number, b: number): number => a + b;
console.log(toplaArrow(10, 20)); // 30
// Tek parametre için parantez isteğe bağlıdır:
const karesi = (x: number): number => x * x;
console.log(karesi(5)); // 25
// Parametresiz fonksiyon:
const merhaba = (): string => "Merhaba Dünya!";
console.log(merhaba()); // Merhaba Dünya!
Tek satırlık fonksiyon gövdelerinde return anahtar kelimesi ve süslü parantezler isteğe bağlıdır. Bu, daha temiz ve okunabilir kod sağlar.
this Bağlamı
Arrow fonksiyonların en önemli özelliklerinden biri, this bağlamını tanımlandıkları yerden (lexical this) almalarıdır. Geleneksel fonksiyonlarda this'in değeri, fonksiyonun nasıl çağrıldığına bağlı olarak değişirken, arrow fonksiyonlarda bu durum sabittir ve çevreleyen kapsamdaki this değerini korur.
class Kullanici {
isim: string;
constructor(isim: string) {
this.isim = isim;
}
// Geleneksel fonksiyon ile 'this' problemi
selamVerGecikmeliGeleneksel() {
setTimeout(function() {
// console.log(Merhaba, ${this.isim}); // Hata: 'this' burada 'Kullanici' değil, global veya undefined
}, 1000);
}
// Arrow fonksiyon ile 'this' bağlamının korunması
selamVerGecikmeliArrow() {
setTimeout(() => {
console.log(Merhaba, ${this.isim}); // 'this', Kullanici örneğine bağlı kalır
}, 1000);
}
}
const user = new Kullanici("Ali");
// user.selamVerGecikmeliGeleneksel(); // Hata verir veya undefined basar
user.selamVerGecikmeliArrow(); // 1 saniye sonra "Merhaba, Ali" yazar
Bu özellik, özellikle olay dinleyicileri (event listeners) ve asenkron işlemlerle çalışırken kodun çok daha basit ve anlaşılır olmasını sağlar.
Fonksiyon Aşırı Yüklemesi (Function Overloading)
TypeScript, aynı isimde ancak farklı parametre tiplerine veya sayılarına sahip fonksiyonlar tanımlamamıza olanak tanır. Bu özelliğe fonksiyon aşırı yüklemesi denir. JavaScript'te doğrudan aşırı yükleme desteği bulunmazken, TypeScript derleme zamanında bu esnekliği sağlar.
Neden Gerekli?
Bir fonksiyonun farklı giriş tiplerine göre farklı davranışlar sergilemesi gerektiğinde aşırı yükleme kullanışlıdır. Örneğin, bir olustur fonksiyonu hem bir sayı alıp bir dizi döndürebilir hem de bir dize alıp bir nesne döndürebilir.
Uygulama Yöntemi
TypeScript'te fonksiyon aşırı yüklemesi, birden fazla "imza" (signature) tanımlayarak ve ardından bu imzaları karşılayan tek bir "implementasyon" (implementation) fonksiyonu yazarak yapılır. İmza fonksiyonları sadece tip tanımlamalarını içerir, implementasyon fonksiyonu ise tüm olası çağrıları kapsayacak şekilde yazılır ve genellikle any veya unknown gibi daha geniş tipler kullanır.
// Aşırı yükleme imzaları
function islemYap(x: number): number[];
function islemYap(x: string): object;
function islemYap(x: boolean): boolean;
// Implementasyon fonksiyonu
function islemYap(x: number | string | boolean): number[] | object | boolean {
if (typeof x === "number") {
return Array(x).fill(0); // Sayı ise, o kadar elemanlı bir dizi döndür
} else if (typeof x === "string") {
return { mesaj: x.toUpperCase() }; // String ise, büyük harfli mesaj içeren bir nesne döndür
} else if (typeof x === "boolean") {
return !x; // Boolean ise, tersini döndür
}
throw new Error("Geçersiz argüman tipi");
}
console.log(islemYap(3)); // [0, 0, 0]
console.log(islemYap("hello")); // { mesaj: "HELLO" }
console.log(islemYap(true)); // false
// console.log(islemYap({})); // Hata: 'islemYap' aşırı yüklemesinin hiçbir imzası bu çağrıya uygun değil.
Implementasyon fonksiyonunun imzası, tüm aşırı yükleme imzalarını kapsayacak şekilde daha geniş tiplere sahip olmalıdır. Kullanıcılar ise sadece aşırı yükleme imzalarını görür ve bu sayede tip güvenliği sağlanır.
Jenerik Fonksiyonlar (Generic Functions)
Jenerikler, bir fonksiyonun veya sınıfın birden fazla tipte veriyle çalışabilmesini sağlayan güçlü bir TypeScript özelliğidir. Bu, kodun yeniden kullanılabilirliğini artırır ve tip güvenliğini korurken esneklik sağlar.
Jeneriklere Giriş
Bir fonksiyonun farklı tiplerle çalışması gerektiğinde, her tip için ayrı bir fonksiyon yazmak yerine jenerik bir fonksiyon tanımlayabiliriz. Örneğin, bir dizideki ilk elemanı döndüren bir fonksiyon düşünelim. Bu fonksiyon hem sayı dizileriyle hem de dize dizileriyle çalışabilmelidir.
Jenerik Fonksiyonların Tanımlanması
Jenerik fonksiyonlar, tip parametreleri (genellikle T harfiyle temsil edilir) kullanılarak tanımlanır. Tip parametresi, fonksiyonun çağrıldığı anda gerçek bir tipe dönüşür.
function ilkElemaniAl(dizi: T[]): T {
return dizi[0];
}
let sayiDizisi = [1, 2, 3];
let ilkSayi = ilkElemaniAl(sayiDizisi); // ilkSayi tipi: number
console.log(ilkSayi); // 1
let metinDizisi = ["elma", "armut", "kiraz"];
let ilkMetin = ilkElemaniAl(metinDizisi); // ilkMetin tipi: string
console.log(ilkMetin); // elma
// TypeScript, tipi otomatik olarak çıkarır.
// İsterseniz açıkça da belirtebilirsiniz:
let ilkBoolean = ilkElemaniAl([true, false]); // ilkBoolean tipi: boolean
console.log(ilkBoolean); // true
Burada ifadesi, T'nin bir tip parametresi olduğunu belirtir. dizi: T[] parametresi, fonksiyonun T tipindeki elemanlardan oluşan bir dizi alacağını, : T ise T tipinde bir değer döndüreceğini ifade eder.
Tip Kısıtlamaları (Type Constraints)
Bazen jenerik bir tip üzerinde belirli işlemler yapmak isteyebiliriz. Örneğin, bir nesnenin bir özelliğine erişmek gibi. Ancak T'nin herhangi bir tip olabileceği için, bu işlem her zaman güvenli olmayabilir. Bu durumda, jenerik tipe kısıtlamalar (extends) ekleyebiliriz.
interface HasLength {
length: number;
}
function uzunluguYazdir(arg: T): void {
console.log(Uzunluk: ${arg.length});
}
uzunluguYazdir("Merhaba"); // Uzunluk: 7 (string'in length özelliği var)
uzunluguYazdir([1, 2, 3]); // Uzunluk: 3 (array'in length özelliği var)
// uzunluguYazdir(10); // Hata: 'number' tipi 'HasLength' arayüzüne atanabilir değil.
// uzunluguYazdir({ a: 1 }); // Hata: '{ a: number }' tipi 'HasLength' arayüzüne atanabilir değil.
uzunluguYazdir({ length: 5, value: "test" }); // Uzunluk: 5 (nesnenin length özelliği var)
Yukarıdaki örnekte, uzunluguYazdir fonksiyonu sadece length özelliğine sahip tiplerle çalışabilir. Bu, T tipinin HasLength arayüzünü genişletmesi gerektiğini belirtir. Bu sayede, fonksiyon içinde arg.length özelliğine güvenli bir şekilde erişebiliriz.
Callback Fonksiyonlar ve Yüksek Mertebeden Fonksiyonlar
Fonksiyonlar, JavaScript ve TypeScript'te birinci sınıf vatandaşlardır, yani değişkenlere atanabilir, argüman olarak geçirilebilir ve diğer fonksiyonlardan döndürülebilirler. Bu özellik, callback fonksiyonları ve yüksek mertebeden fonksiyonlar kavramlarını ortaya çıkarır.
Callback Fonksiyonlar Nedir?
Bir callback fonksiyonu, başka bir fonksiyona argüman olarak geçirilen ve ana fonksiyonun belirli bir işlemi tamamladıktan sonra veya belirli bir olay gerçekleştiğinde çağrılan fonksiyondur. Asenkron programlamada ve olay odaklı mimarilerde yaygın olarak kullanılırlar.
function veriIsle(veri: string, callback: (islenmisVeri: string) => void): void {
console.log("Veri işleniyor...");
// Simüle edilmiş asenkron işlem
setTimeout(() => {
const islenmisVeri = veri.toUpperCase();
callback(islenmisVeri);
}, 1000);
}
veriIsle("typescript öğreniyorum", (sonuc) => {
console.log(İşlem tamamlandı: ${sonuc});
});
// Çıktı:
// Veri işleniyor...
// (1 saniye sonra)
// İşlem tamamlandı: TYPESCRIPT ÖĞRENİYORUM
Burada veriIsle fonksiyonu, ikinci parametre olarak bir callback fonksiyonu alır. Bu callback, bir string argümanı alan ve hiçbir değer döndürmeyen bir fonksiyondur.
Yüksek Mertebeden Fonksiyonlar (Higher-Order Functions)
Yüksek mertebeden fonksiyonlar, bir veya daha fazla fonksiyonu argüman olarak alan veya bir fonksiyon döndüren fonksiyonlardır. Callback'ler, yüksek mertebeden fonksiyonların bir alt kümesidir.
// Bir fonksiyonu argüman olarak alan yüksek mertebeden fonksiyon
function islemYapici(islem: (sayi: number) => number): (dizi: number[]) => number[] {
return (dizi: number[]) => dizi.map(islem);
}
const kareAlici = (x: number) => x * x;
const ikiyleCarpici = (x: number) => x * 2;
const kareleriAl = islemYapici(kareAlici);
const ikiyleCarp = islemYapici(ikiyleCarpici);
console.log(kareleriAl([1, 2, 3])); // [1, 4, 9]
console.log(ikiyleCarp([1, 2, 3])); // [2, 4, 6]
// Bir fonksiyon döndüren yüksek mertebeden fonksiyon
function loglayiciOlustur(prefix: string): (mesaj: string) => void {
return (mesaj: string) => {
console.log(${prefix}: ${mesaj});
};
}
const hataLoglayici = loglayiciOlustur("HATA");
const bilgiLoglayici = loglayiciOlustur("BİLGİ");
hataLoglayici("Bir sorun oluştu!"); // HATA: Bir sorun oluştu!
bilgiLoglayici("İşlem başarıyla tamamlandı."); // BİLGİ: İşlem başarıyla tamamlandı.
Yüksek mertebeden fonksiyonlar, fonksiyonel programlama prensiplerinin temelini oluşturur ve kodun daha modüler, yeniden kullanılabilir ve test edilebilir olmasını sağlar.
Async/Await Fonksiyonlar
Asenkron programlama, modern JavaScript ve TypeScript uygulamalarının ayrılmaz bir parçasıdır. Ağ istekleri, dosya okuma/yazma gibi uzun süren işlemlerin ana iş parçacığını (main thread) bloke etmeden çalışmasını sağlar. async/await sözdizimi, Promise tabanlı asenkron kodu senkron koda benzer şekilde yazmamızı kolaylaştırır.
Asenkron Programlama ve Promises
async fonksiyonlar, her zaman bir Promise döndürür. await anahtar kelimesi ise sadece async fonksiyonlar içinde kullanılabilir ve bir Promise'in çözülmesini bekler. Bu, asenkron kodun adım adım, okunabilir bir şekilde yazılmasını sağlar.
function gecikmeliVeriGetir(id: number): Promise {
return new Promise((resolve) => {
setTimeout(() => {
resolve(ID ${id} için veri);
}, 1500);
});
}
async function veriIsleAsync(): Promise {
try {
console.log("Veri bekleniyor...");
const veri1 = await gecikmeliVeriGetir(1);
console.log(veri1); // 1.5 saniye sonra: "ID 1 için veri"
const veri2 = await gecikmeliVeriGetir(2);
console.log(veri2); // 1.5 saniye sonra: "ID 2 için veri"
console.log("Tüm veriler işlendi.");
} catch (error) {
console.error("Bir hata oluştu:", error);
}
}
veriIsleAsync();
Yukarıdaki örnekte, veriIsleAsync bir async fonksiyondur. İçindeki await ifadeleri, gecikmeliVeriGetir Promise'lerinin tamamlanmasını bekler. Bu sayede kod, sanki senkron bir şekilde çalışıyormuş gibi görünür, ancak aslında arka planda asenkron olarak ilerler. Promise dönüş tipi, fonksiyonun bir Promise döndürdüğünü ancak çözüldüğünde bir değer taşımadığını gösterir.
Tip Korumaları ve Fonksiyonlar
TypeScript'in en güçlü özelliklerinden biri, derleme zamanında tip kontrolü yapmasıdır. Ancak bazen çalışma zamanında bir değişkenin tipini daraltmamız gerekebilir. Bu durumlarda "tip korumaları" (type guards) kullanırız. Fonksiyonlar, kullanıcı tanımlı tip korumaları oluşturmak için mükemmel bir araçtır.
Kullanıcı Tanımlı Tip Korumaları
Kullanıcı tanımlı tip korumaları, bir fonksiyonun belirli bir tip testi yaptığını ve eğer test başarılı olursa, fonksiyonun argümanının belirli bir tipe daraltıldığını TypeScript'e bildiren özel bir dönüş tipi imzasına sahiptir: parametreAdi is Tip.
interface Kedi {
miyawla(): void;
cins: string;
}
interface Kopek {
havla(): void;
irk: string;
}
function isKedi(hayvan: Kedi | Kopek): hayvan is Kedi {
return (hayvan as Kedi).miyawla !== undefined;
}
function hayvanSesiniCikar(hayvan: Kedi | Kopek): void {
if (isKedi(hayvan)) {
hayvan.miyawla(); // TypeScript burada 'hayvan'ın bir 'Kedi' olduğunu bilir
console.log(Cinsi: ${hayvan.cins});
} else {
hayvan.havla(); // TypeScript burada 'hayvan'ın bir 'Kopek' olduğunu bilir
console.log(Irkı: ${hayvan.irk});
}
}
const tekir: Kedi = { miyawla: () => console.log("Miyav!"), cins: "Tekir" };
const pitbull: Kopek = { havla: () => console.log("Hav hav!"), irk: "Pitbull" };
hayvanSesiniCikar(tekir); // Miyav! Cinsi: Tekir
hayvanSesiniCikar(pitbull); // Hav hav! Irkı: Pitbull
isKedi fonksiyonu, hayvan is Kedi dönüş tipi imzasına sahiptir. Bu, fonksiyon true döndürdüğünde, TypeScript derleyicisinin hayvan değişkenini Kedi tipine daraltacağını anladığı anlamına gelir. Bu sayede, if bloğunun içinde hayvan.miyawla() gibi Kedi'ye özgü metodlara güvenle erişebiliriz.
Fonksiyon Tipleri ve Arayüzler
TypeScript'te fonksiyonların kendileri de tiplere sahip olabilir. Bu, fonksiyonları değişkenlere atarken, fonksiyon parametreleri olarak kullanırken veya nesne özelliklerinin fonksiyon olmasını belirtirken çok kullanışlıdır. Fonksiyon tiplerini tanımlamak için tip takma adları (type) veya arayüzler (interface) kullanabiliriz.
Fonksiyon Tiplerini Tanımlama
Bir fonksiyonun tipini tanımlamak için arrow fonksiyon sözdizimine benzer bir yapı kullanabiliriz:
type MatematikselIslem = (x: number, y: number) => number;
const toplaFonk: MatematikselIslem = (a, b) => a + b;
const cikarFonk: MatematikselIslem = (a, b) => a - b;
console.log(toplaFonk(10, 5)); // 15
console.log(cikarFonk(10, 5)); // 5
// Bu tip, bir fonksiyonu parametre olarak alan başka bir fonksiyon için de kullanılabilir:
function hesapla(islem: MatematikselIslem, sayi1: number, sayi2: number): number {
return islem(sayi1, sayi2);
}
console.log(hesapla(toplaFonk, 20, 10)); // 30
MatematikselIslem tipi, iki number parametresi alan ve bir number döndüren herhangi bir fonksiyonu temsil eder. Bu, kodun daha modüler ve anlaşılır olmasını sağlar.
Arayüzler ile Fonksiyon Tipleri
Arayüzler de fonksiyon tiplerini tanımlamak için kullanılabilir, özellikle bir nesnenin belirli bir fonksiyon özelliğine sahip olmasını beklediğimizde.
interface Karsilastirici {
(a: string, b: string): boolean;
}
const alfabetikKarsilastir: Karsilastirici = (s1, s2) => s1.localeCompare(s2) > 0;
const uzunlukKarsilastir: Karsilastirici = (s1, s2) => s1.length > s2.length;
console.log(alfabetikKarsilastir("elma", "armut")); // true (e > a)
console.log(uzunlukKarsilastir("uzun", "kısa")); // true (4 > 3)
// Bir nesne içindeki fonksiyonu tanımlamak için de kullanılabilir:
interface HesapMakinesi {
topla: MatematikselIslem;
carp(x: number, y: number): number; // Direkt arayüz içinde de tanımlanabilir
}
const benimHesapMakinesi: HesapMakinesi = {
topla: (a, b) => a + b,
carp: (a, b) => a * b
};
console.log(benimHesapMakinesi.topla(5, 2)); // 7
console.log(benimHesapMakinesi.carp(5, 2)); // 10
Hem type hem de interface, fonksiyon tiplerini tanımlamak için kullanılabilir. Genellikle, basit fonksiyon tipleri için type takma adları tercih edilirken, bir nesnenin parçası olan fonksiyonlar için veya bir sınıfın uygulaması gereken bir davranış kümesini tanımlarken interface daha uygun olabilir.
Pratik İpuçları ve En İyi Uygulamalar
TypeScript'te fonksiyonları etkin bir şekilde kullanmak sadece doğru sözdizimini bilmekle kalmaz, aynı zamanda iyi uygulama prensiplerini benimsemeyi de gerektirir. Bu prensipler, kodun daha okunabilir, bakımı kolay ve hatasız olmasını sağlar.
Okunabilirlik ve Bakım
- Açık ve Anlamlı İsimler: Fonksiyonlarınıza ve parametrelerinize ne iş yaptıklarını açıkça belirten isimler verin. Örneğin,
hesaplayerinetoplamHesaplaveyakullaniciVerisiGetir. - Kısa ve Tek Sorumluluk: Her fonksiyonun tek bir işlevi olmalıdır (Single Responsibility Principle). Uzun ve karmaşık fonksiyonlar yerine, daha küçük, odaklanmış fonksiyonlara bölün. Bu, test etmeyi ve anlamayı kolaylaştırır.
- Yorumlar ve JSDoc: Karmaşık mantık içeren veya özel gereksinimleri olan fonksiyonlar için açıklayıcı yorumlar veya JSDoc kullanın. TypeScript, JSDoc'u tanır ve tip çıkarımında kullanabilir.
/
* Verilen iki sayıyı toplar ve sonucu döndürür.
* @param {number} sayi1 - İlk sayı.
* @param {number} sayi2 - İkinci sayı.
* @returns {number} İki sayının toplamı.
*/
function topla(sayi1: number, sayi2: number): number {
return sayi1 + sayi2;
}
Saf Fonksiyonlar
Mümkün olduğunca saf (pure) fonksiyonlar yazmaya çalışın. Saf fonksiyonlar:
- Aynı girdilerle her zaman aynı çıktıyı üretirler.
- Dış dünyada herhangi bir yan etkiye (side effect) neden olmazlar (global değişkenleri değiştirmezler, konsola yazmazlar, ağ istekleri yapmazlar vb.).
Saf fonksiyonlar daha kolay test edilebilir, önbelleğe alınabilir ve paralel olarak çalıştırılabilir.
// Saf fonksiyon
function kareAl(sayi: number): number {
return sayi * sayi;
}
// Saf olmayan fonksiyon (yan etkisi var: global değişkeni değiştiriyor)
let sayac = 0;
function artirSayac(): void {
sayac++;
}
Hata Yönetimi
Fonksiyonlarınızda beklenen hataları uygun bir şekilde ele alın. try...catch blokları, Promise zincirleri veya async/await ile hata yakalama mekanizmalarını kullanarak uygulamanızın kararlılığını sağlayın. TypeScript, fırlatılan hataların tipini belirtmenize izin vermese de, yakaladığınız hataları unknown olarak ele alıp tip korumaları ile daraltmanız güvenli bir yaklaşımdır.
async function veriYukle(url: string): Promise {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(HTTP Hata: ${response.status});
}
return await response.json();
} catch (error: unknown) {
if (error instanceof Error) {
console.error(Veri yüklenirken bir hata oluştu: ${error.message});
} else {
console.error("Beklenmedik bir hata oluştu:", error);
}
throw error; // Hatayı tekrar fırlat veya uygun şekilde ele al
}
}
Sonuç
TypeScript, fonksiyonlara statik tipleme gücü ekleyerek JavaScript geliştirme deneyimini kökten değiştirir. Bu makalede, fonksiyonların temel tanımlamalarından başlayarak, parametre ve dönüş tiplerinin detaylarına, arrow fonksiyonların modern kullanımına, fonksiyon aşırı yüklemesinin ve jenerik fonksiyonların esnekliğine, callback'ler ve yüksek mertebeden fonksiyonların gücüne, asenkron programlamanın async/await ile nasıl daha okunabilir hale geldiğine ve tip korumalarının çalışma zamanı tip daraltmasındaki önemine değindik. Ayrıca, fonksiyon tiplerini tanımlamak için type ve interface kullanımlarını inceledik ve son olarak, okunabilirlik, saf fonksiyonlar ve hata yönetimi gibi pratik ipuçlarıyla en iyi uygulamaları tartıştık.
TypeScript'te fonksiyonları etkin bir şekilde kullanmak, sadece derleme zamanı hatalarını azaltmakla kalmaz, aynı zamanda kodunuzu daha anlaşılır, bakımı daha kolay ve gelecekteki değişikliklere daha dirençli hale getirir. Tip güvenliğinin sağladığı bu avantajlar, özellikle büyük ve karmaşık projelerde geliştirme verimliliğini ve güvenilirliği önemli ölçüde artırır. TypeScript'in fonksiyonlarla ilgili sunduğu tüm bu özellikleri derinlemesine anlamak ve projelerinizde uygulamak, daha sağlam ve sürdürülebilir yazılımlar geliştirmenizin anahtarı olacaktır.
