Temel tiplendirmelerin ötesine geçin. İnanılmaz derecede sağlam ve tür güvenli API'ler oluşturmak için koşullu tipler, şablon literalleri ve string işleme gibi gelişmiş TypeScript özelliklerinde uzmanlaşın. Küresel geliştiriciler için kapsamlı bir kılavuz.
TypeScript'in Tüm Potansiyelini Ortaya Çıkarmak: Koşullu Tiplere, Şablon Literallerine ve Gelişmiş String İşlemeye Derinlemesine Bir Bakış
Modern yazılım geliştirme dünyasında, TypeScript, JavaScript için basit bir tür denetleyicisi olarak başlangıçtaki rolünün çok ötesine geçti. Tür seviyesinde programlama olarak tanımlanabilecek bir şey için gelişmiş bir araç haline geldi. Bu paradigma, geliştiricilerin türlerin kendileri üzerinde çalışan, dinamik, kendi kendini belgeleyen ve son derece güvenli API'ler oluşturan kod yazmalarına olanak tanır. Bu devrimin kalbinde, uyum içinde çalışan üç güçlü özellik vardır: Koşullu Tipler, Şablon Literal Tipleri ve bir dizi içsel String İşleme Tipleri.
TypeScript becerilerini geliştirmek isteyen dünyanın dört bir yanındaki geliştiriciler için, bu kavramları anlamak artık bir lüks değil, ölçeklenebilir ve sürdürülebilir uygulamalar oluşturmak için bir zorunluluktur. Bu kılavuz sizi, temel ilkelerden başlayıp, birleşik güçlerini gösteren karmaşık, gerçek dünya kalıplarına kadar derinlemesine bir yolculuğa çıkaracaktır. İster bir tasarım sistemi, ister tür güvenli bir API istemcisi veya karmaşık bir veri işleme kitaplığı oluşturuyor olun, bu özelliklerde uzmanlaşmak TypeScript yazma şeklinizi temelden değiştirecektir.
Temel: Koşullu Tipler (`extends` Üçlüsü)
Özünde, koşullu bir tip, bir tür ilişkisi kontrolüne bağlı olarak iki olası türden birini seçmenize olanak tanır. JavaScript'in üçlü operatörüne (condition ? valueIfTrue : valueIfFalse) aşina iseniz, sözdizimini hemen sezgisel bulacaksınız:
type Result = SomeType extends OtherType ? TrueType : FalseType;
Burada, extends anahtar kelimesi koşulumuz olarak işlev görür. SomeType'ın OtherType'a atanabilir olup olmadığını kontrol eder. Basit bir örnekle parçalayalım.
Temel Örnek: Bir Türü Kontrol Etme
Verilen bir T türü bir string ise true, aksi takdirde false olarak çözümlenen bir tür oluşturmak istediğimizi hayal edin.
type IsString
O zaman bu türü şu şekilde kullanabiliriz:
type A = IsString<"hello">; // type A is true
type B = IsString<123>; // type B is false
Bu temel yapı taşıdır. Ancak koşullu tiplerin gerçek gücü, infer anahtar kelimesiyle birleştirildiğinde ortaya çıkar.
`infer` Gücü: Türleri İçeriden Çıkarma
infer anahtar kelimesi bir oyun değiştiricidir. extends ifadesi içinde yeni bir genel tür değişkeni bildirmenize olanak tanır ve bu da denetlediğiniz türün bir bölümünü etkili bir şekilde yakalar. Bunu, değerini örüntü eşleştirmeden alan tür seviyesinde bir değişken bildirimi olarak düşünün.
Klasik bir örnek, bir Promise içinde bulunan türü çözmektir.
type UnwrapPromise
Bunu analiz edelim:
T extends Promise: Bu,T'nin birPromiseolup olmadığını kontrol eder. Eğer öyleyse, TypeScript yapıyı eşleştirmeye çalışır.infer U: Eşleşme başarılı olursa, TypeScriptPromise'in çözüldüğü türü yakalar veUadlı yeni bir tür değişkenine koyar.? U : T: Koşul doğruysa (TbirPromiseidi), sonuçtaki türU'dur (çözülmüş tür). Aksi takdirde, sonuçtaki tür sadece orijinal türT'dir.
Kullanım:
type User = { id: number; name: string; };
type UserPromise = Promise
type UnwrappedUser = UnwrapPromise
type UnwrappedNumber = UnwrapPromise
Bu model o kadar yaygındır ki, TypeScript, bir fonksiyonun dönüş türünü çıkarmak için aynı ilke kullanılarak uygulanan ReturnType gibi yerleşik yardımcı türler içerir.
Dağıtıcı Koşullu Tipler: Birlikler ile Çalışma
Koşullu tiplerin büyüleyici ve çok önemli bir davranışı, kontrol edilen türün "çıplak" bir genel tür parametresi olduğunda dağıtıcı hale gelmeleridir. Bu, ona bir birleşim türü aktarırsanız, koşulun birliğin her üyesine ayrı ayrı uygulanacağı ve sonuçların yeni bir birleşim halinde toplanacağı anlamına gelir.
Bir türü o türün bir dizisine dönüştüren bir tür düşünün:
type ToArray
ToArray'e bir birleşim türü aktarırsak:
type StrOrNumArray = ToArray
Sonuç (string | number)[] değildir. T çıplak bir tür parametresi olduğundan, koşul dağıtılır:
ToArraystring[]olurToArraynumber[]olur
Nihai sonuç, bu bireysel sonuçların birleşimidir: string[] | number[].
Bu dağıtıcı özellik, birlikleri filtrelemek için inanılmaz derecede kullanışlıdır. Örneğin, yerleşik Extract yardımcı türü, bunu T birliğinden U'ya atanabilir olan üyeleri seçmek için kullanır.
Bu dağıtıcı davranışı önlemeniz gerekiyorsa, tür parametresini extends ifadesinin her iki tarafında bir demet içine sarabilirsiniz:
type ToArrayNonDistributive
type StrOrNumArrayUnified = ToArrayNonDistributive
Bu sağlam temel ile dinamik string türlerini nasıl oluşturabileceğimizi keşfedelim.
Tür Seviyesinde Dinamik String'ler Oluşturma: Şablon Literal Tipleri
TypeScript 4.1'de tanıtılan Şablon Literal Tipleri, JavaScript'in şablon literal string'lerine benzeyen türler tanımlamanıza olanak tanır. Mevcut olanlardan yeni string literal türlerini birleştirmenize, bir araya getirmenize ve oluşturmanıza olanak tanırlar.
Sözdizimi tam olarak bekleyeceğiniz gibidir:
type World = "World";
type Greeting = `Hello, ${World}!`; // type Greeting is "Hello, World!"
Bu basit görünebilir, ancak gücü onu birlikler ve jeneriklerle birleştirmekte yatar.
Birlikler ve Permütasyonlar
Bir şablon literal türü bir birlik içerdiğinde, olası her string permütasyonunu içeren yeni bir birliğe genişler. Bu, iyi tanımlanmış bir sabitler kümesi oluşturmanın güçlü bir yoludur.
Bir CSS kenar boşluğu özellikleri kümesi tanımladığınızı hayal edin:
type Side = "top" | "right" | "bottom" | "left";
type MarginProperty = `margin-${Side}`;
MarginProperty için sonuçtaki tür şudur:
"margin-top" | "margin-right" | "margin-bottom" | "margin-left"
Bu, yalnızca belirli string biçimlerine izin verilen tür güvenli bileşen özellikleri veya fonksiyon argümanları oluşturmak için mükemmeldir.
Jeneriklerle Birleştirme
Şablon literaller gerçekten jeneriklerle kullanıldığında parlar. Bazı girdilere dayalı olarak yeni string literal türleri oluşturan fabrika türleri oluşturabilirsiniz.
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Bu model, dinamik, tür güvenli API'ler oluşturmanın anahtarıdır. Ancak string'in durumunu değiştirmemiz gerekirse, örneğin "user"'ı "User" olarak değiştirerek "onUserChange" elde edersek ne olur? String işleme türleri burada devreye girer.
Araç Kiti: İçsel String İşleme Türleri
Şablon literalleri daha da güçlü hale getirmek için TypeScript, string literalleri işlemek için bir dizi yerleşik tür sağlar. Bunlar, tür sistemi için yardımcı fonksiyonlar gibidir.
Durum Değiştiriciler: `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`
Bu dört tür tam olarak adlarının önerdiğini yapar:
Uppercase: Tüm string türünü büyük harfe dönüştürür.type LOUD = Uppercase<"hello">; // "HELLO"Lowercase: Tüm string türünü küçük harfe dönüştürür.type quiet = Lowercase<"WORLD">; // "world"Capitalize: String türünün ilk karakterini büyük harfe dönüştürür.type Proper = Capitalize<"john">; // "John"Uncapitalize: String türünün ilk karakterini küçük harfe dönüştürür.type variable = Uncapitalize<"PersonName">; // "personName"
Önceki örneğimize geri dönelim ve geleneksel olay işleyici adları oluşturmak için Capitalize kullanarak geliştirelim:
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Şimdi tüm parçalara sahibiz. Karmaşık, gerçek dünya sorunlarını çözmek için nasıl birleştiklerini görelim.
Sentez: Gelişmiş Kalıplar için Üçünü Birden Birleştirme
Teorinin pratikle buluştuğu yer burasıdır. Koşullu tipleri, şablon literallerini ve string işlemeyi bir araya getirerek, inanılmaz derecede karmaşık ve güvenli tür tanımları oluşturabiliriz.
Kalıp 1: Tamamen Tür Güvenli Olay Yayıcı
Amaç: Tamamen tür güvenli olan on(), off() ve emit() gibi yöntemlere sahip genel bir EventEmitter sınıfı oluşturun. Bu şu anlama gelir:
- Yöntemlere iletilen olay adı geçerli bir olay olmalıdır.
emit()'e iletilen yük, o olay için tanımlanan türle eşleşmelidir.on()'a iletilen geri arama fonksiyonu, o olay için doğru yük türünü kabul etmelidir.
İlk olarak, olay adlarının yük türlerine eşlemesini tanımlıyoruz:
interface EventMap {
"user:created": { userId: number; name: string; };
"user:deleted": { userId: number; };
"product:added": { productId: string; price: number; };
}
Şimdi, genel EventEmitter sınıfını oluşturabiliriz. EventMap yapımızı genişletmesi gereken genel bir parametre Events kullanacağız.
class TypedEventEmitter
private listeners: { [K in keyof Events]?: ((payload: Events[K]) => void)[] } = {};
// `on` metodu, Events haritamızın bir anahtarı olan genel bir `K` kullanır
on
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]?.push(callback);
}
// `emit` metodu, yükün olayın türüyle eşleşmesini sağlar
emit
this.listeners[event]?.forEach(callback => callback(payload));
}
}
Örneklendirelim ve kullanalım:
const appEvents = new TypedEventEmitter
// Bu tür güvenlidir. Yük doğru bir şekilde { userId: number; name: string; } olarak çıkarılır
appEvents.on("user:created", (payload) => {
console.log(`User created: ${payload.name} (ID: ${payload.userId})`);
});
// TypeScript burada hata verecektir çünkü "user:updated" EventMap'te bir anahtar değildir
// appEvents.on("user:updated", () => {}); // Hata!
// TypeScript burada hata verecektir çünkü yükte 'name' özelliği eksik
// appEvents.emit("user:created", { userId: 123 }); // Hata!
Bu kalıp, geleneksel olarak birçok uygulamanın çok dinamik ve hataya açık bir parçası olan şey için derleme zamanı güvenliği sağlar.
Kalıp 2: İç İçe Geçmiş Nesneler İçin Tür Güvenli Yol Erişimi
Amaç: Bir nokta gösterimi string yolu P (örneğin, "user.address.city") kullanarak iç içe geçmiş bir nesnedeki T bir değerin türünü belirleyebilen PathValue yardımcı türünü oluşturun.
Bu, yinelemeli koşullu türleri sergileyen son derece gelişmiş bir kalıptır.
İşte parçalayacağımız uygulama:
type PathValue
? Key extends keyof T
? PathValue
: never
: P extends keyof T
? T[P]
: never;
Mantığını bir örnekle izleyelim: PathValue
- İlk Çağrı:
P"a.b.c"'dir. Bu, şablon literal`${infer Key}.${infer Rest}`ile eşleşir. Key"a"olarak çıkarılır.Rest"b.c"olarak çıkarılır.- İlk Yineleme: Tür,
"a"'nınMyObject'in bir anahtarı olup olmadığını kontrol eder. Evet ise,PathValue'yi yinelemeli olarak çağırır. - İkinci Yineleme: Şimdi,
P"b.c"'dir. Şablon literal ile tekrar eşleşir. Key"b"olarak çıkarılır.Rest"c"olarak çıkarılır.- Tür,
"b"'ninMyObject["a"]'nın bir anahtarı olup olmadığını kontrol eder ve yinelemeli olarakPathValue'yi çağırır. - Temel Durum: Son olarak,
P"c"'dir. Bu,`${infer Key}.${infer Rest}`ile eşleşmez. Tür mantığı ikinci koşula düşer:P extends keyof T ? T[P] : never. - Tür,
"c"'ninMyObject["a"]["b"]'nin bir anahtarı olup olmadığını kontrol eder. Evet ise, sonuçMyObject["a"]["b"]["c"]'dir. Değilse,never'dır.
Bir yardımcı fonksiyonla kullanım:
declare function get
const myObject = {
user: {
name: "Alice",
address: {
city: "Wonderland",
zip: 12345
}
}
};
const city = get(myObject, "user.address.city"); // const city: string
const zip = get(myObject, "user.address.zip"); // const zip: number
const invalid = get(myObject, "user.email"); // const invalid: never
Bu güçlü tür, yollardaki yazım hatalarından kaynaklanan çalışma zamanı hatalarını önler ve karmaşık API yanıtlarıyla ilgilenen küresel uygulamalarda yaygın bir zorluk olan derinlemesine iç içe geçmiş veri yapıları için mükemmel tür çıkarımı sağlar.
En İyi Uygulamalar ve Performans Hususları
Herhangi bir güçlü araçta olduğu gibi, bu özellikleri akıllıca kullanmak önemlidir.
- Okunabilirliğe Öncelik Verin: Karmaşık türler hızla okunamaz hale gelebilir. Bunları daha küçük, iyi adlandırılmış yardımcı türlere ayırın. Tıpkı karmaşık çalışma zamanı kodunda olduğu gibi, mantığı açıklamak için yorumlar kullanın.
- `never` Türünü Anlayın:
nevertürü, hata durumlarını işlemek ve koşullu türlerde birlikleri filtrelemek için birincil aracınızdır. Asla meydana gelmemesi gereken bir durumu temsil eder. - Yineleme Sınırlarına Dikkat Edin: TypeScript'in tür örneklendirmesi için bir yineleme derinliği sınırı vardır. Türleriniz çok derinden iç içe geçmişse veya sonsuz yinelemeliyse, derleyici hata verecektir. Yinelemeli türlerinizin net bir temel durumu olduğundan emin olun.
- IDE Performansını İzleyin: Aşırı karmaşık türler bazen TypeScript dil sunucusunun performansını etkileyebilir ve bu da düzenleyicinizde daha yavaş otomatik tamamlama ve tür denetimine yol açar. Yavaşlamalar yaşarsanız, karmaşık bir türün basitleştirilip basitleştirilemeyeceğine veya ayrılabileceğine bakın.
- Ne Zaman Duracağınızı Bilin: Bu özellikler, tür güvenliği ve geliştirici deneyiminin karmaşık sorunlarını çözmek içindir. Bunları basit türleri aşırı mühendislik yapmak için kullanmayın. Amaç, gereksiz karmaşıklık eklemek değil, netliği ve güvenliği artırmaktır.
Sonuç
Koşullu tipler, şablon literalleri ve string işleme türleri yalnızca yalıtılmış özellikler değildir; tür seviyesinde karmaşık mantık gerçekleştirmek için sıkı bir şekilde entegre edilmiş bir sistemdir. Bize basit açıklamalardan öteye geçme ve kendi yapılarını ve kısıtlamalarını derinden bilen sistemler oluşturma gücü verirler.
Bu üçlüde uzmanlaşarak şunları yapabilirsiniz:
- Kendi Kendine Belgelenen API'ler Oluşturun: Türlerin kendileri, geliştiricilere bunları doğru şekilde kullanmaları için rehberlik eden belgeler haline gelir.
- Tüm Hata Sınıflarını Ortadan Kaldırın: Tür hataları, üretimdeki kullanıcılar tarafından değil, derleme zamanında yakalanır.
- Geliştirici Deneyimini İyileştirin: Kod tabanınızın en dinamik parçaları için bile zengin otomatik tamamlama ve satır içi hata mesajlarının keyfini çıkarın.
Bu gelişmiş yetenekleri benimsemek, TypeScript'i bir güvenlik ağından geliştirmede güçlü bir ortağa dönüştürür. Karmaşık iş mantığını ve değişmezleri doğrudan tür sistemine kodlamanıza olanak tanır ve uygulamalarınızın küresel bir hedef kitle için daha sağlam, sürdürülebilir ve ölçeklenebilir olmasını sağlar.