Magyar

Részletes útmutató a TypeScript 'infer' kulcsszavához, bemutatva használatát feltételes típusokkal a hatékony típuskinyeréshez és -manipulációhoz, haladó felhasználási esetekkel együtt.

A TypeScript Infer kulcsszó mesteri használata: Feltételes típuskinyerés haladó típusmanipulációhoz

A TypeScript típusrendszere rendkívül erőteljes, lehetővé téve a fejlesztők számára, hogy robusztus és karbantartható alkalmazásokat hozzanak létre. Ennek az erőnek az egyik kulcsfontosságú eleme az infer kulcsszó, amelyet feltételes típusokkal együtt használunk. Ez a kombináció mechanizmust biztosít specifikus típusok kinyerésére összetett típusstruktúrákból. Ez a blogbejegyzés mélyen elmerül az infer kulcsszóban, elmagyarázva annak működését és bemutatva haladó felhasználási eseteit. Gyakorlati példákat fogunk megvizsgálni, amelyek különféle szoftverfejlesztési forgatókönyvekre alkalmazhatók, az API-interakciótól a komplex adatstruktúrák manipulálásáig.

Mik azok a feltételes típusok?

Mielőtt belemerülnénk az infer-be, gyorsan tekintsük át a feltételes típusokat. A feltételes típusok a TypeScriptben lehetővé teszik egy típus meghatározását egy feltétel alapján, hasonlóan a JavaScript ternáris operátorához. Az alapvető szintaxis a következő:

T extends U ? X : Y

Ez így olvasható: "Ha a T típus hozzárendelhető az U típushoz, akkor a típus X; egyébként a típus Y."

Példa:

type IsString<T> = T extends string ? true : false;

type StringResult = IsString<string>; // a StringResult típusa = true
type NumberResult = IsString<number>; // a NumberResult típusa = false

Az infer kulcsszó bemutatása

Az infer kulcsszót a feltételes típus extends klózában használjuk egy típusváltozó deklarálására, amelyet a vizsgált típusból lehet kikövetkeztetni (inferálni). Lényegében lehetővé teszi, hogy egy típus egy részét "elkapjuk" későbbi felhasználás céljából.

Alapvető szintaxis:

type MyType<T> = T extends (infer U) ? U : never;

Ebben a példában, ha a T hozzárendelhető valamilyen típushoz, a TypeScript megpróbálja kikövetkeztetni az U típusát. Ha a következtetés sikeres, a típus U lesz; egyébként never.

Egyszerű példák az infer használatára

1. Függvény visszatérési típusának kikövetkeztetése

Gyakori felhasználási eset egy függvény visszatérési típusának kikövetkeztetése:

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>; // az AddReturnType típusa = number

function greet(name: string): string {
  return `Hello, ${name}!`;
}

type GreetReturnType = ReturnType<typeof greet>; // a GreetReturnType típusa = string

Ebben a példában a ReturnType<T> egy T függvénytípust kap bemenetként. Ellenőrzi, hogy a T hozzárendelhető-e egy olyan függvényhez, amely bármilyen argumentumot elfogad és visszaad egy értéket. Ha igen, akkor a visszatérési típust R-ként következteti ki és visszaadja. Ellenkező esetben any-t ad vissza.

2. Tömb elemtípusának kikövetkeztetése

Egy másik hasznos forgatókönyv az elemtípus kinyerése egy tömbből:

type ArrayElementType<T> = T extends (infer U)[] ? U : never;

type NumberArrayType = ArrayElementType<number[]>; // a NumberArrayType típusa = number
type StringArrayType = ArrayElementType<string[]>; // a StringArrayType típusa = string
type MixedArrayType = ArrayElementType<(string | number)[]>; // a MixedArrayType típusa = string | number
type NotAnArrayType = ArrayElementType<number>; // a NotAnArrayType típusa = never

Itt az ArrayElementType<T> ellenőrzi, hogy a T egy tömbtípus-e. Ha igen, akkor az elemtípust U-ként következteti ki és visszaadja. Ha nem, akkor never-t ad vissza.

Az infer haladó felhasználási esetei

1. Konstruktor paramétereinek kikövetkeztetése

Az infer segítségével kinyerhetjük egy konstruktor függvény paramétereinek típusait:

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>; // a PersonConstructorParams típusa = [string, number]

class Point {
    constructor(public x: number, public y: number) {}
}

type PointConstructorParams = ConstructorParameters<typeof Point>; // a PointConstructorParams típusa = [number, number]

Ebben az esetben a ConstructorParameters<T> egy T konstruktor függvénytípust kap. Kikövetkezteti a konstruktor paramétereinek típusait P-ként, és tuple-ként adja vissza őket.

2. Tulajdonságok kinyerése objektumtípusokból

Az infer-t arra is használhatjuk, hogy specifikus tulajdonságokat nyerjünk ki objektumtípusokból leképezett (mapped) és feltételes típusok segítségével:

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>; // a StringProperties típusa = { name: string; email: string; }

type NumberProperties = PickByType<User, keyof User, number>; // a NumberProperties típusa = { id: number; age: number; }

//Egy interfész a földrajzi koordináták reprezentálására.
interface GeoCoordinates {
    latitude: number;
    longitude: number;
    altitude: number;
    country: string;
    city: string;
    timezone: string;
}

type NumberCoordinateProperties = PickByType<GeoCoordinates, keyof GeoCoordinates, number>; // a NumberCoordinateProperties típusa = { latitude: number; longitude: number; altitude: number; }

Itt a PickByType<T, K, U> egy új típust hoz létre, amely csak azokat a T tulajdonságait tartalmazza (a K-ban lévő kulcsokkal), amelyek értékei hozzárendelhetők az U típushoz. A leképezett típus végigiterál a T kulcsain, a feltételes típus pedig kiszűri azokat a kulcsokat, amelyek nem felelnek meg a megadott típusnak.

3. Munkavégzés Promise-okkal

Kikövetkeztethetjük egy Promise feloldott (resolved) típusát:

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>>; // a FetchDataType típusa = string

async function fetchNumbers(): Promise<number[]> {
    return [1, 2, 3];
}

type FetchedNumbersType = Awaited<ReturnType<typeof fetchNumbers>>; //a FetchedNumbersType típusa = number[]

Az Awaited<T> típus egy T típust kap, amely várhatóan egy Promise. A típus ezután kikövetkezteti a Promise feloldott U típusát, és visszaadja azt. Ha a T nem egy Promise, akkor a T-t adja vissza. Ez egy beépített segédtípus a TypeScript újabb verzióiban.

4. Promise-okat tartalmazó tömb típusának kinyerése

Az Awaited és a tömbtípus-következtetés kombinálásával kikövetkeztethetjük egy Promise-okat tartalmazó tömb által feloldott típust. Ez különösen hasznos a Promise.all kezelésekor.

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>;
// a RatesType típusa = [number, number]

Ez a példa először két aszinkron függvényt definiál, a getUSDRate-et és a getEURRate-et, amelyek árfolyamok lekérését szimulálják. A PromiseArrayReturnType segédtípus ezután kinyeri a feloldott típust minden egyes Promise-ból a tömbben, ami egy tuple típust eredményez, ahol minden elem a megfelelő Promise awaited típusa.

Gyakorlati példák különböző területekről

1. E-kereskedelmi alkalmazás

Vegyünk egy e-kereskedelmi alkalmazást, ahol a termék részleteit egy API-ból kérjük le. Az infer segítségével kinyerhetjük a termékadatok típusát:

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> {
  // API hívás szimulálása
  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>>; // a ProductType típusa = 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);

Ebben a példában definiálunk egy Product interfészt és egy fetchProduct függvényt, amely lekéri a termék részleteit egy API-ból. Az Awaited és a ReturnType segítségével kinyerjük a Product típust a fetchProduct függvény visszatérési típusából, ami lehetővé teszi a displayProductDetails függvény típusellenőrzését.

2. Nemzetköziesítés (i18n)

Tegyük fel, hogy van egy fordító függvénye, amely a területi beállításoktól függően különböző szövegeket ad vissza. Az infer segítségével kinyerheti ennek a függvénynek a visszatérési típusát a típusbiztonság érdekében:

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'); // Kimenet: Bienvenue, Jean!

Itt a TranslationType típust a Translations interfésznek következtetjük ki, biztosítva, hogy a greetUser függvény a megfelelő típusinformációval rendelkezzen a lefordított szövegek eléréséhez.

3. API válaszok kezelése

Amikor API-kkal dolgozunk, a válaszstruktúra összetett lehet. Az infer segíthet specifikus adattípusok kinyerésében beágyazott API válaszokból:

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>> {
  // API hívás szimulálása
  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);
  }
});

Ebben a példában definiálunk egy ApiResponse interfészt és egy UserData interfészt. Az infer és a típusindexelés segítségével kinyerjük a UserProfileType-ot az API válaszból, biztosítva, hogy a displayUserProfile függvény a megfelelő típust kapja.

Bevált gyakorlatok az infer használatához

Gyakori buktatók

Alternatívák az infer-re

Bár az infer egy erőteljes eszköz, vannak helyzetek, amikor alternatív megközelítések megfelelőbbek lehetnek:

Összegzés

A TypeScript infer kulcsszava, feltételes típusokkal kombinálva, haladó típusmanipulációs képességeket nyit meg. Lehetővé teszi specifikus típusok kinyerését összetett típusstruktúrákból, így robusztusabb, karbantarthatóbb és típusbiztosabb kódot írhatsz. A függvények visszatérési típusainak kikövetkeztetésétől az objektumtípusokból való tulajdonságkinyerésig a lehetőségek hatalmasak. Az ebben az útmutatóban felvázolt elvek és bevált gyakorlatok megértésével teljes mértékben kihasználhatod az infer-t és emelheted a TypeScript-tudásodat. Ne felejtsd el dokumentálni a típusokat, alaposan tesztelni őket, és szükség esetén fontolóra venni alternatív megközelítéseket. Az infer elsajátítása képessé tesz arra, hogy igazán kifejező és erőteljes TypeScript kódot írj, ami végső soron jobb szoftverhez vezet.