Română

Un ghid cuprinzător despre cuvântul cheie 'infer' din TypeScript, explicând cum să-l folosești cu tipuri condiționale pentru extracție și manipulare puternică a tipurilor.

Stăpânirea lui TypeScript Infer: Extracția Tipului Condițional pentru Manipularea Avansată a Tipurilor

Sistemul de tipuri TypeScript este incredibil de puternic, permițând dezvoltatorilor să creeze aplicații robuste și ușor de întreținut. Una dintre caracteristicile cheie care permit această putere este cuvântul cheie infer, utilizat în combinație cu tipurile condiționale. Această combinație oferă un mecanism pentru extragerea tipurilor specifice din structuri de tip complexe. Această postare de blog analizează în profunzime cuvântul cheie infer, explicându-i funcționalitatea și prezentând cazuri de utilizare avansate. Vom explora exemple practice aplicabile diverselor scenarii de dezvoltare software, de la interacțiunea API până la manipularea complexă a structurilor de date.

Ce sunt Tipurile Condiționale?

Înainte de a ne adânci în infer, haideți să trecem rapid în revistă tipurile condiționale. Tipurile condiționale din TypeScript vă permit să definiți un tip pe baza unei condiții, similar cu un operator ternar în JavaScript. Sintaxa de bază este:

T extends U ? X : Y

Aceasta se citește astfel: "Dacă tipul T este atribuibil tipului U, atunci tipul este X; altfel, tipul este Y."

Exemplu:

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

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

Introducere în Cuvântul Cheie infer

Cuvântul cheie infer este utilizat în cadrul clauzei extends a unui tip condițional pentru a declara o variabilă de tip care poate fi dedusă din tipul verificat. În esență, vă permite să "capturați" o parte a unui tip pentru utilizare ulterioară.

Sintaxă de Bază:

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

În acest exemplu, dacă T este atribuibil unui anumit tip, TypeScript va încerca să deducă tipul lui U. Dacă inferența are succes, tipul va fi U; altfel, va fi never.

Exemple Simple de infer

1. Deducerea Tipului Returnat al unei Funcții

Un caz de utilizare comun este deducerea tipului returnat al unei funcții:

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

În acest exemplu, ReturnType<T> ia un tip de funcție T ca intrare. Verifică dacă T este atribuibil unei funcții care acceptă orice argumente și returnează o valoare. Dacă este, deduce tipul returnat ca R și îl returnează. Altfel, returnează any.

2. Deducerea Tipului Elementului de Tablou

Un alt scenariu util este extragerea tipului de element dintr-un tablou:

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

Aici, ArrayElementType<T> verifică dacă T este un tip de tablou. Dacă este, deduce tipul elementului ca U și îl returnează. Dacă nu, returnează never.

Cazuri Avansate de Utilizare a lui infer

1. Deducerea Parametrilor unui Constructor

Puteți utiliza infer pentru a extrage tipurile de parametri ale unei funcții constructor:

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]

În acest caz, ConstructorParameters<T> ia un tip de funcție constructor T. Deducă tipurile parametrilor constructorului ca P și le returnează ca un tuplu.

2. Extragerea Proprietăților din Tipurile de Obiecte

infer poate fi, de asemenea, utilizat pentru a extrage proprietăți specifice din tipurile de obiecte utilizând tipuri mapate și tipuri condiționale:

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

Aici, PickByType<T, K, U> creează un tip nou care include doar proprietățile lui T (cu chei în K) ale căror valori sunt atribuibile tipului U. Tipul mapat iterează asupra cheilor lui T, iar tipul condițional filtrează cheile care nu se potrivesc cu tipul specificat.

3. Lucrul cu Promisiuni

Puteți deduce tipul rezolvat al unei 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[]

Tipul Awaited<T> ia un tip T, care se așteaptă să fie o Promise. Tipul deduce apoi tipul rezolvat U al Promise-ului și îl returnează. Dacă T nu este o promisiune, returnează T. Acesta este un tip de utilitate încorporat în versiunile mai noi de TypeScript.

4. Extragerea Tipului unui Tablou de Promisiuni

Combinarea Awaited și a inferenței tipului de tablou vă permite să deduceți tipul rezolvat de un tablou de Promisiuni. Acest lucru este util mai ales atunci când aveți de-a face cu 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]

Acest exemplu definește mai întâi două funcții asincrone, getUSDRate și getEURRate, care simulează preluarea cursurilor de schimb. Tipul de utilitate PromiseArrayReturnType extrage apoi tipul rezolvat din fiecare Promise din tablou, rezultând un tip tuplu în care fiecare element este tipul așteptat al Promise-ului corespunzător.

Exemple Practice în Diferite Domenii

1. Aplicație de Comerț Electronic

Luați în considerare o aplicație de comerț electronic în care preluați detaliile produsului dintr-un API. Puteți utiliza infer pentru a extrage tipul datelor despre produs:

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

În acest exemplu, definim o interfață Product și o funcție fetchProduct care preia detaliile produsului dintr-un API. Folosim Awaited și ReturnType pentru a extrage tipul Product din tipul returnat al funcției fetchProduct, permițându-ne să verificăm tipul funcției displayProductDetails.

2. Internaționalizare (i18n)

Să presupunem că aveți o funcție de traducere care returnează șiruri diferite în funcție de localizare. Puteți utiliza infer pentru a extrage tipul returnat al acestei funcții pentru siguranța tipului:

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!

Aici, TranslationType este dedus ca fiind interfața Translations, asigurându-se că funcția greetUser are informațiile corecte de tip pentru accesarea șirurilor traduse.

3. Gestionarea Răspunsurilor API

Când lucrați cu API-uri, structura răspunsului poate fi complexă. infer vă poate ajuta să extrageți tipuri de date specifice din răspunsurile API imbricate:

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

În acest exemplu, definim o interfață ApiResponse și o interfață UserData. Folosim infer și indexarea tipurilor pentru a extrage UserProfileType din răspunsul API, asigurându-ne că funcția displayUserProfile primește tipul corect.

Cele Mai Bune Practici Pentru Utilizarea lui infer

Capcane Comune

Alternative la infer

În timp ce infer este un instrument puternic, există situații în care abordările alternative ar putea fi mai adecvate:

Concluzie

Cuvântul cheie infer din TypeScript, atunci când este combinat cu tipurile condiționale, deblochează capabilități avansate de manipulare a tipurilor. Vă permite să extrageți tipuri specifice din structuri de tip complexe, permițându-vă să scrieți cod mai robust, mai ușor de întreținut și mai sigur pentru tipuri. De la deducerea tipurilor returnate ale funcțiilor până la extragerea proprietăților din tipurile de obiecte, posibilitățile sunt vaste. Înțelegând principiile și cele mai bune practici prezentate în acest ghid, puteți valorifica infer la potențialul său maxim și vă puteți îmbunătăți abilitățile TypeScript. Nu uitați să vă documentați tipurile, să le testați temeinic și să luați în considerare abordări alternative atunci când este cazul. Stăpânirea lui infer vă permite să scrieți cod TypeScript cu adevărat expresiv și puternic, ceea ce duce în cele din urmă la un software mai bun.