TypeScript 'infer' anahtar kelimesi için kapsamlı bir rehber. Güçlü tip çıkarımı ve manipülasyonu için koşullu tiplerle nasıl kullanılacağını ve gelişmiş kullanım senaryolarını açıklar.
TypeScript Infer'de Uzmanlaşma: Gelişmiş Tip Manipülasyonu için Koşullu Tip Çıkarımı
TypeScript'in tip sistemi, geliştiricilerin sağlam ve sürdürülebilir uygulamalar oluşturmasına olanak tanıyan inanılmaz derecede güçlüdür. Bu gücü sağlayan temel özelliklerden biri, koşullu tiplerle birlikte kullanılan infer
anahtar kelimesidir. Bu kombinasyon, karmaşık tip yapılarından belirli tipleri çıkarmak için bir mekanizma sağlar. Bu blog yazısı, infer
anahtar kelimesini derinlemesine inceleyerek işlevselliğini açıklıyor ve gelişmiş kullanım senaryolarını sergiliyor. API etkileşiminden karmaşık veri yapısı manipülasyonuna kadar çeşitli yazılım geliştirme senaryolarına uygulanabilir pratik örnekleri keşfedeceğiz.
Koşullu Tipler Nedir?
infer
konusuna dalmadan önce, koşullu tipleri hızlıca gözden geçirelim. TypeScript'teki koşullu tipler, JavaScript'teki üçlü operatöre benzer şekilde, bir koşula dayalı olarak bir tip tanımlamanıza olanak tanır. Temel sözdizimi şöyledir:
T extends U ? X : Y
Bu şu şekilde okunur: "Eğer T
tipi, U
tipine atanabiliyorsa, tip X
olur; aksi takdirde tip Y
olur."
Örnek:
type IsString<T> = T extends string ? true : false;
type StringResult = IsString<string>; // type StringResult = true
type NumberResult = IsString<number>; // type NumberResult = false
infer
Anahtar Kelimesine Giriş
infer
anahtar kelimesi, koşullu bir tipin extends
yan tümcesi içinde, kontrol edilen tipten çıkarılabilecek bir tip değişkeni bildirmek için kullanılır. Esasen, bir tipin bir parçasını daha sonra kullanmak üzere "yakalamanıza" olanak tanır.
Temel Sözdizimi:
type MyType<T> = T extends (infer U) ? U : never;
Bu örnekte, eğer T
bir tipe atanabiliyorsa, TypeScript U
tipini çıkarmaya çalışacaktır. Çıkarım başarılı olursa, tip U
olacaktır; aksi takdirde never
olacaktır.
Basit infer
Örnekleri
1. Bir Fonksiyonun Geri Dönüş Tipini Çıkarma
Yaygın bir kullanım durumu, bir fonksiyonun geri dönüş tipini çıkarmaktır:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType<typeof add>; // type AddReturnType = number
function greet(name: string): string {
return `Hello, ${name}!`;
}
type GreetReturnType = ReturnType<typeof greet>; // type GreetReturnType = string
Bu örnekte, ReturnType<T>
girdi olarak bir T
fonksiyon tipini alır. T
'nin herhangi bir argüman kabul eden ve bir değer döndüren bir fonksiyona atanabilir olup olmadığını kontrol eder. Eğer öyleyse, geri dönüş tipini R
olarak çıkarır ve onu döndürür. Aksi takdirde, any
döndürür.
2. Dizi Eleman Tipini Çıkarma
Bir başka kullanışlı senaryo, bir diziden eleman tipini çıkarmaktır:
type ArrayElementType<T> = T extends (infer U)[] ? U : never;
type NumberArrayType = ArrayElementType<number[]>; // type NumberArrayType = number
type StringArrayType = ArrayElementType<string[]>; // type StringArrayType = string
type MixedArrayType = ArrayElementType<(string | number)[]>; // type MixedArrayType = string | number
type NotAnArrayType = ArrayElementType<number>; // type NotAnArrayType = never
Burada, ArrayElementType<T>
, T
'nin bir dizi tipi olup olmadığını kontrol eder. Eğer öyleyse, eleman tipini U
olarak çıkarır ve onu döndürür. Değilse, never
döndürür.
Gelişmiş infer
Kullanım Senaryoları
1. Bir Constructor'ın Parametrelerini Çıkarma
Bir constructor fonksiyonunun parametre tiplerini çıkarmak için infer
kullanabilirsiniz:
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
class Person {
constructor(public name: string, public age: number) {}
}
type PersonConstructorParams = ConstructorParameters<typeof Person>; // type PersonConstructorParams = [string, number]
class Point {
constructor(public x: number, public y: number) {}
}
type PointConstructorParams = ConstructorParameters<typeof Point>; // type PointConstructorParams = [number, number]
Bu durumda, ConstructorParameters<T>
bir constructor fonksiyon tipi olan T
'yi alır. Constructor parametrelerinin tiplerini P
olarak çıkarır ve bunları bir demet (tuple) olarak döndürür.
2. Nesne Tiplerinden Özellikleri Çıkarma
infer
, eşlenmiş tipler (mapped types) ve koşullu tipler kullanılarak nesne tiplerinden belirli özellikleri çıkarmak için de kullanılabilir:
type PickByType<T, K extends keyof T, U> = {
[P in K as T[P] extends U ? P : never]: T[P];
};
interface User {
id: number;
name: string;
age: number;
email: string;
isActive: boolean;
}
type StringProperties = PickByType<User, keyof User, string>; // type StringProperties = { name: string; email: string; }
type NumberProperties = PickByType<User, keyof User, number>; // type NumberProperties = { id: number; age: number; }
//Coğrafi koordinatları temsil eden bir arayüz.
interface GeoCoordinates {
latitude: number;
longitude: number;
altitude: number;
country: string;
city: string;
timezone: string;
}
type NumberCoordinateProperties = PickByType<GeoCoordinates, keyof GeoCoordinates, number>; // type NumberCoordinateProperties = { latitude: number; longitude: number; altitude: number; }
Burada, PickByType<T, K, U>
, T
'nin yalnızca değerleri U
tipine atanabilen özelliklerini (K
'daki anahtarlarla) içeren yeni bir tip oluşturur. Eşlenmiş tip, T
'nin anahtarları üzerinde döner ve koşullu tip, belirtilen tiple eşleşmeyen anahtarları filtreler.
3. Promise'lerle Çalışma
Bir Promise
'in çözümlenmiş (resolved) tipini çıkarabilirsiniz:
type Awaited<T> = T extends Promise<infer U> ? U : T;
async function fetchData(): Promise<string> {
return 'Data from API';
}
type FetchDataType = Awaited<ReturnType<typeof fetchData>>; // type FetchDataType = string
async function fetchNumbers(): Promise<number[]> {
return [1, 2, 3];
}
type FetchedNumbersType = Awaited<ReturnType<typeof fetchNumbers>>; //type FetchedNumbersType = number[]
Awaited<T>
tipi, bir Promise olması beklenen bir T
tipini alır. Tip daha sonra Promise'in çözümlenmiş tipi olan U
'yu çıkarır ve onu döndürür. Eğer T
bir promise değilse, T'yi döndürür. Bu, TypeScript'in daha yeni sürümlerinde yerleşik bir yardımcı tiptir.
4. Promise Dizisinin Tipini Çıkarma
Awaited
ve dizi tipi çıkarımını birleştirmek, bir Promise dizisi tarafından çözümlenen tipi çıkarmanıza olanak tanır. Bu, özellikle Promise.all
ile uğraşırken kullanışlıdır.
type PromiseArrayReturnType<T extends Promise<any>[]> = {
[K in keyof T]: Awaited<T[K]>;
};
async function getUSDRate(): Promise<number> {
return 0.0069;
}
async function getEURRate(): Promise<number> {
return 0.0064;
}
const rates = [getUSDRate(), getEURRate()];
type RatesType = PromiseArrayReturnType<typeof rates>;
// type RatesType = [number, number]
Bu örnek önce, döviz kurlarını almayı simüle eden iki asenkron fonksiyon olan getUSDRate
ve getEURRate
'i tanımlar. PromiseArrayReturnType
yardımcı tipi daha sonra dizideki her bir Promise
'ten çözümlenmiş tipi çıkarır, bu da her elemanın karşılık gelen Promise'in beklenen (awaited) tipi olduğu bir demet tipiyle sonuçlanır.
Farklı Alanlardan Pratik Örnekler
1. E-ticaret Uygulaması
Bir API'den ürün detaylarını çektiğiniz bir e-ticaret uygulamasını düşünün. Ürün verisinin tipini çıkarmak için infer
kullanabilirsiniz:
interface Product {
id: number;
name: string;
price: number;
description: string;
imageUrl: string;
category: string;
rating: number;
countryOfOrigin: string;
}
async function fetchProduct(productId: number): Promise<Product> {
// Simulate API call
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: productId,
name: 'Example Product',
price: 29.99,
description: 'A sample product',
imageUrl: 'https://example.com/image.jpg',
category: 'Electronics',
rating: 4.5,
countryOfOrigin: 'Canada'
});
}, 500);
});
}
type ProductType = Awaited<ReturnType<typeof fetchProduct>>; // type ProductType = Product
function displayProductDetails(product: ProductType) {
console.log(`Product Name: ${product.name}`);
console.log(`Price: ${product.price} ${product.countryOfOrigin === 'Canada' ? 'CAD' : (product.countryOfOrigin === 'USA' ? 'USD' : 'EUR')}`);
}
fetchProduct(123).then(displayProductDetails);
Bu örnekte, bir Product
arayüzü ve bir API'den ürün detaylarını çeken bir fetchProduct
fonksiyonu tanımlıyoruz. fetchProduct
fonksiyonunun geri dönüş tipinden Product
tipini çıkarmak için Awaited
ve ReturnType
kullanıyoruz, bu da displayProductDetails
fonksiyonunu tip kontrolünden geçirmemizi sağlıyor.
2. Uluslararasılaştırma (i18n)
Yerel ayara (locale) göre farklı dizeler döndüren bir çeviri fonksiyonunuz olduğunu varsayalım. Tip güvenliği için bu fonksiyonun geri dönüş tipini çıkarmak üzere infer
kullanabilirsiniz:
interface Translations {
greeting: string;
farewell: string;
welcomeMessage: (name: string) => string;
}
const enTranslations: Translations = {
greeting: 'Hello',
farewell: 'Goodbye',
welcomeMessage: (name: string) => `Welcome, ${name}!`,
};
const frTranslations: Translations = {
greeting: 'Bonjour',
farewell: 'Au revoir',
welcomeMessage: (name: string) => `Bienvenue, ${name}!`,
};
function getTranslation(locale: 'en' | 'fr'): Translations {
return locale === 'en' ? enTranslations : frTranslations;
}
type TranslationType = ReturnType<typeof getTranslation>;
function greetUser(locale: 'en' | 'fr', name: string) {
const translations = getTranslation(locale);
console.log(translations.welcomeMessage(name));
}
greetUser('fr', 'Jean'); // Output: Bienvenue, Jean!
Burada, TranslationType
'ın Translations
arayüzü olduğu çıkarılır, bu da greetUser
fonksiyonunun çevrilmiş dizelere erişim için doğru tip bilgisine sahip olmasını sağlar.
3. API Yanıt İşleme
API'lerle çalışırken, yanıt yapısı karmaşık olabilir. infer
, iç içe geçmiş API yanıtlarından belirli veri tiplerini çıkarmaya yardımcı olabilir:
interface ApiResponse<T> {
status: number;
data: T;
message?: string;
}
interface UserData {
id: number;
username: string;
email: string;
profile: {
firstName: string;
lastName: string;
country: string;
language: string;
}
}
async function fetchUser(userId: number): Promise<ApiResponse<UserData>> {
// Simulate API call
return new Promise((resolve) => {
setTimeout(() => {
resolve({
status: 200,
data: {
id: userId,
username: 'johndoe',
email: 'john.doe@example.com',
profile: {
firstName: 'John',
lastName: 'Doe',
country: 'USA',
language: 'en'
}
}
});
}, 500);
});
}
type UserApiResponse = Awaited<ReturnType<typeof fetchUser>>;
type UserProfileType = UserApiResponse['data']['profile'];
function displayUserProfile(profile: UserProfileType) {
console.log(`Name: ${profile.firstName} ${profile.lastName}`);
console.log(`Country: ${profile.country}`);
}
fetchUser(123).then((response) => {
if (response.status === 200) {
displayUserProfile(response.data.profile);
}
});
Bu örnekte, bir ApiResponse
arayüzü ve bir UserData
arayüzü tanımlıyoruz. API yanıtından UserProfileType
'ı çıkarmak için infer
ve tip indekslemeyi kullanıyoruz, bu da displayUserProfile
fonksiyonunun doğru tipi almasını sağlıyor.
infer
Kullanımı için En İyi Uygulamalar
- Basit Tutun:
infer
'ı yalnızca gerektiğinde kullanın. Aşırı kullanımı kodunuzu okumayı ve anlamayı zorlaştırabilir. - Tiplerinizi Belgeleyin: Koşullu tiplerinizin ve
infer
ifadelerinizin ne yaptığını açıklamak için yorumlar ekleyin. - Tiplerinizi Test Edin: Tiplerinizin beklendiği gibi davrandığından emin olmak için TypeScript'in tip kontrolünü kullanın.
- Performansı Göz Önünde Bulundurun: Karmaşık koşullu tipler bazen derleme süresini etkileyebilir. Tiplerinizin karmaşıklığına dikkat edin.
- Yardımcı Tipleri Kullanın: TypeScript, kodunuzu basitleştirebilecek ve özel
infer
ifadelerine olan ihtiyacı azaltabilecek birkaç yerleşik yardımcı tip (örneğin,ReturnType
,Awaited
) sağlar.
Yaygın Hatalar
- Yanlış Çıkarım: Bazen, TypeScript beklediğinizden farklı bir tip çıkarabilir. Tip tanımlarınızı ve koşullarınızı iki kez kontrol edin.
- Döngüsel Bağımlılıklar:
infer
kullanarak özyinelemeli (recursive) tipler tanımlarken dikkatli olun, çünkü bu durum döngüsel bağımlılıklara ve derleme hatalarına yol açabilir. - Aşırı Karmaşık Tipler: Anlaşılması ve sürdürülmesi zor olan aşırı karmaşık koşullu tipler oluşturmaktan kaçının. Onları daha küçük, daha yönetilebilir tiplere ayırın.
infer
Alternatifleri
infer
güçlü bir araç olsa da, alternatif yaklaşımların daha uygun olabileceği durumlar vardır:
- Tip İddiaları (Type Assertions): Bazı durumlarda, bir değerin tipini çıkarmak yerine açıkça belirtmek için tip iddialarını kullanabilirsiniz. Ancak, tip kontrolünü atlayabilecekleri için tip iddialarıyla dikkatli olun.
- Tip Koruyucuları (Type Guards): Tip koruyucuları, çalışma zamanı kontrollerine dayanarak bir değerin tipini daraltmak için kullanılabilir. Bu, çalışma zamanı koşullarına göre farklı tipleri işlemeniz gerektiğinde kullanışlıdır.
- Yardımcı Tipler: TypeScript, özel
infer
ifadelerine ihtiyaç duymadan birçok yaygın tip manipülasyon görevini yerine getirebilen zengin bir yardımcı tip seti sağlar.
Sonuç
TypeScript'teki infer
anahtar kelimesi, koşullu tiplerle birleştirildiğinde, gelişmiş tip manipülasyon yeteneklerinin kilidini açar. Karmaşık tip yapılarından belirli tipleri çıkarmanıza olanak tanıyarak daha sağlam, sürdürülebilir ve tip güvenli kod yazmanızı sağlar. Fonksiyon geri dönüş tiplerini çıkarmaktan nesne tiplerinden özellikleri çıkarmaya kadar olasılıklar çok geniştir. Bu kılavuzda özetlenen ilkeleri ve en iyi uygulamaları anlayarak, infer
'ı tam potansiyeliyle kullanabilir ve TypeScript becerilerinizi yükseltebilirsiniz. Tiplerinizi belgelemeyi, onları kapsamlı bir şekilde test etmeyi ve uygun olduğunda alternatif yaklaşımları göz önünde bulundurmayı unutmayın. infer
'de uzmanlaşmak, sizi gerçekten etkileyici ve güçlü TypeScript kodu yazma konusunda güçlendirir ve sonuç olarak daha iyi yazılımlara yol açar.