Slovenčina

Komplexný sprievodca kľúčovým slovom 'infer' v TypeScripte, vysvetľujúci ako ho používať s podmienenými typmi pre výkonnú extrakciu a manipuláciu s typmi, vrátane pokročilých prípadov použitia.

Zvládnutie TypeScript Infer: Podmienená Extrakcia Typov pre Pokročilú Manipuláciu s Typmi

Typový systém TypeScriptu je neuveriteľne výkonný a umožňuje vývojárom vytvárať robustné a udržiavateľné aplikácie. Jednou z kľúčových vlastností, ktoré umožňujú túto silu, je kľúčové slovo infer používané v spojení s podmienenými typmi. Táto kombinácia poskytuje mechanizmus na extrahovanie špecifických typov z komplexných typových štruktúr. Tento blogový príspevok sa ponorí hlboko do kľúčového slova infer, vysvetľuje jeho funkčnosť a predstavuje pokročilé prípady použitia. Preskúmame praktické príklady použiteľné v rôznych scenároch vývoja softvéru, od interakcie s API až po komplexnú manipuláciu s dátovými štruktúrami.

Čo sú Podmienené Typy?

Skôr ako sa ponoríme do infer, rýchlo si zopakujme podmienené typy. Podmienené typy v TypeScripte vám umožňujú definovať typ na základe podmienky, podobne ako ternárny operátor v JavaScripte. Základná syntax je:

T extends U ? X : Y

Toto sa číta ako: "Ak je typ T priraditeľný k typu U, potom je typ X; inak je typ Y."

Príklad:

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

type StringResult = IsString<string>; // type StringResult = true
type NumberResult = IsString<number>; // type NumberResult = false

Predstavujeme Kľúčové Slovo infer

Kľúčové slovo infer sa používa v rámci klauzuly extends podmieneného typu na deklarovanie typovej premennej, ktorá sa dá odvodiť z kontrolovaného typu. V podstate vám umožňuje "zachytiť" časť typu na neskoršie použitie.

Základná Syntax:

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

V tomto príklade, ak je T priraditeľné k nejakému typu, TypeScript sa pokúsi odvodiť typ U. Ak je odvodenie úspešné, typ bude U; inak bude never.

Jednoduché Príklady infer

1. Odvodenie Návratového Typu Funkcie

Bežným prípadom použitia je odvodenie návratového typu funkcie:

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

V tomto príklade, ReturnType<T> berie typ funkcie T ako vstup. Kontroluje, či je T priraditeľné k funkcii, ktorá prijíma ľubovoľné argumenty a vracia hodnotu. Ak áno, odvodí návratový typ ako R a vráti ho. Inak vráti any.

2. Odvodenie Typu Prvku Poľa

Ďalším užitočným scenárom je extrahovanie typu prvku z poľa:

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

Tu ArrayElementType<T> kontroluje, či je T typ poľa. Ak áno, odvodí typ prvku ako U a vráti ho. Ak nie, vráti never.

Pokročilé Prípady Použitia infer

1. Odvodenie Parametrov Konštruktora

Môžete použiť infer na extrahovanie typov parametrov konštruktorovej funkcie:

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]

V tomto prípade, ConstructorParameters<T> berie typ konštruktorovej funkcie T. Odvodí typy parametrov konštruktora ako P a vráti ich ako tuple.

2. Extrahovanie Vlastností z Objektových Typov

infer sa dá použiť aj na extrahovanie špecifických vlastností z objektových typov pomocou mapovaných typov a podmienených typov:

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; }

//An interface representing geographic coordinates.
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; }

Tu, PickByType<T, K, U> vytvára nový typ, ktorý obsahuje iba vlastnosti T (s kľúčmi v K), ktorých hodnoty sú priraditeľné k typu U. Mapovaný typ iteruje cez kľúče T a podmienený typ odfiltruje kľúče, ktoré nezodpovedajú zadanému typu.

3. Práca s Promise

Môžete odvodiť vyriešený typ Promise:

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[]

Typ Awaited<T> berie typ T, ktorý sa očakáva, že bude Promise. Typ potom odvodí vyriešený typ U z Promise a vráti ho. Ak T nie je promise, vráti T. Toto je vstavaný pomocný typ v novších verziách TypeScriptu.

4. Extrahovanie Typu z Poľa Promise

Kombinácia Awaited a odvodenia typu poľa vám umožňuje odvodiť typ vyriešený poľom Promise. Toto je obzvlášť užitočné pri práci s Promise.all.

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]

Tento príklad najprv definuje dve asynchrónne funkcie, getUSDRate a getEURRate, ktoré simulujú načítanie výmenných kurzov. Pomocný typ PromiseArrayReturnType potom extrahuje vyriešený typ z každého Promise v poli, čo vedie k typu tuple, kde každý prvok je očakávaný typ zodpovedajúceho Promise.

Praktické Príklady v Rôznych Doménach

1. Aplikácia Elektronického Obchodu

Zvážte aplikáciu elektronického obchodu, kde načítate podrobnosti o produkte z API. Môžete použiť infer na extrahovanie typu údajov o produkte:

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);

V tomto príklade definujeme rozhranie Product a funkciu fetchProduct, ktorá načítava podrobnosti o produkte z API. Používame Awaited a ReturnType na extrahovanie typu Product z návratového typu funkcie fetchProduct, čo nám umožňuje kontrolovať typ funkcie displayProductDetails.

2. Internacionalizácia (i18n)

Predpokladajme, že máte prekladovú funkciu, ktorá vracia rôzne reťazce na základe lokality. Môžete použiť infer na extrahovanie návratového typu tejto funkcie pre typovú bezpečnosť:

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!

Tu je TranslationType odvodený ako rozhranie Translations, čo zaisťuje, že funkcia greetUser má správne informácie o type na prístup k preloženým reťazcom.

3. Spracovanie Odpovedí API

Pri práci s API môže byť štruktúra odpovede komplexná. infer vám môže pomôcť extrahovať špecifické typy údajov z vnorených odpovedí API:

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);
  }
});

V tomto príklade definujeme rozhranie ApiResponse a rozhranie UserData. Používame infer a indexovanie typov na extrahovanie UserProfileType z odpovede API, čím zabezpečujeme, že funkcia displayUserProfile dostane správny typ.

Osvedčené Postupy pre Používanie infer

Bežné Úskalia

Alternatívy k infer

Zatiaľ čo infer je výkonný nástroj, existujú situácie, kedy môžu byť vhodnejšie alternatívne prístupy:

Záver

Kľúčové slovo infer v TypeScripte, v kombinácii s podmienenými typmi, odomyká pokročilé možnosti manipulácie s typmi. Umožňuje vám extrahovať špecifické typy z komplexných typových štruktúr, čo vám umožňuje písať robustnejší, udržiavateľnejší a typovo bezpečnejší kód. Od odvodzovania návratových typov funkcií po extrahovanie vlastností z objektových typov, možnosti sú rozsiahle. Pochopením princípov a osvedčených postupov uvedených v tejto príručke môžete využiť infer naplno a pozdvihnúť svoje zručnosti v TypeScripte. Nezabudnite dokumentovať svoje typy, dôkladne ich testovať a zvážiť alternatívne prístupy, ak je to vhodné. Zvládnutie infer vám umožňuje písať skutočne expresívny a výkonný kód TypeScriptu, čo v konečnom dôsledku vedie k lepšiemu softvéru.