Türkçe

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

Yaygın Hatalar

infer Alternatifleri

infer güçlü bir araç olsa da, alternatif yaklaşımların daha uygun olabileceği durumlar vardır:

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.