Latviešu

Visaptverošs ceļvedis par TypeScript `infer` atslēgvārdu, paskaidrojot tā lietošanu ar nosacījumu tipiem jaudīgai tipu ekstrakcijai un manipulācijai, ieskaitot sarežģītus piemērus.

TypeScript `infer` apguve: nosacījumu tipu ekstrakcija padziļinātai tipu manipulācijai

TypeScript tipu sistēma ir neticami jaudīga, ļaujot izstrādātājiem veidot robustas un uzturamas lietojumprogrammas. Viena no galvenajām funkcijām, kas nodrošina šo jaudu, ir infer atslēgvārds, ko izmanto kopā ar nosacījumu tipiem. Šī kombinācija nodrošina mehānismu specifisku tipu ekstrakcijai no sarežģītām tipu struktūrām. Šis emuāra ieraksts dziļi ienirst infer atslēgvārdā, izskaidrojot tā funkcionalitāti un demonstrējot padziļinātus lietošanas gadījumus. Mēs izpētīsim praktiskus piemērus, kas piemērojami dažādiem programmatūras izstrādes scenārijiem, sākot ar API mijiedarbību un beidzot ar sarežģītu datu struktūru manipulāciju.

Kas ir nosacījumu tipi?

Pirms iedziļināmies infer, ātri apskatīsim nosacījumu tipus. Nosacījumu tipi TypeScript valodā ļauj definēt tipu, pamatojoties uz nosacījumu, līdzīgi kā ternārais operators JavaScript. Pamata sintakse ir:

T extends U ? X : Y

To varētu tulkot šādi: "Ja tips T ir piešķirams tipam U, tad tips ir X; pretējā gadījumā tips ir Y."

Piemērs:

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

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

Iepazīstinām ar infer atslēgvārdu

infer atslēgvārds tiek lietots nosacījuma tipa extends klauzulā, lai deklarētu tipa mainīgo, ko var secināt no pārbaudāmā tipa. Būtībā tas ļauj "notvert" daļu no tipa vēlākai izmantošanai.

Pamata sintakse:

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

Šajā piemērā, ja T ir piešķirams kādam tipam, TypeScript mēģinās noteikt U tipu. Ja noteikšana ir veiksmīga, tips būs U; pretējā gadījumā tas būs never.

Vienkārši infer piemēri

1. Funkcijas atgrieztā tipa noteikšana

Bieži sastopams pielietojums ir funkcijas atgrieztā tipa noteikšana:

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

Šajā piemērā ReturnType<T> kā ievaddatus saņem funkcijas tipu T. Tas pārbauda, vai T ir piešķirams funkcijai, kas pieņem jebkādus argumentus un atgriež vērtību. Ja tā ir, tas nosaka atgriezto tipu kā R un atgriež to. Pretējā gadījumā tas atgriež any.

2. Masīva elementu tipa noteikšana

Vēl viens noderīgs scenārijs ir elementa tipa ekstrakcija no masīva:

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

Šeit ArrayElementType<T> pārbauda, vai T ir masīva tips. Ja tā ir, tas nosaka elementa tipu kā U un atgriež to. Ja nē, tas atgriež never.

Padziļināti infer lietošanas gadījumi

1. Konstruktora parametru noteikšana

Jūs varat izmantot infer, lai ekstrahētu konstruktora funkcijas parametru tipus:

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]

Šajā gadījumā ConstructorParameters<T> saņem konstruktora funkcijas tipu T. Tas nosaka konstruktora parametru tipus kā P un atgriež tos kā kortežu (tuple).

2. Īpašību ekstrakcija no objektu tipiem

infer var izmantot arī, lai ekstrahētu specifiskas īpašības no objektu tipiem, izmantojot kartētos tipus (mapped types) un nosacījumu tipus:

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

//Interfeiss, kas attēlo ģeogrāfiskās koordinātas.
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; }

Šeit PickByType<T, K, U> izveido jaunu tipu, kas ietver tikai tās T īpašības (ar atslēgām no K), kuru vērtības ir piešķiramas tipam U. Kartētais tips iterē pār T atslēgām, un nosacījuma tips izfiltrē atslēgas, kas neatbilst norādītajam tipam.

3. Darbs ar Promise

Jūs varat noteikt Promise atrisināto tipu:

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> tips saņem tipu T, kam būtu jābūt Promise. Pēc tam tips nosaka Promise atrisināto tipu U un atgriež to. Ja T nav Promise, tas atgriež T. Šis ir iebūvēts utilītu tips jaunākās TypeScript versijās.

4. Tipa ekstrakcija no Promise masīva

Kombinējot Awaited un masīva tipa noteikšanu, varat noteikt tipu, ko atrisina Promise masīvs. Tas ir īpaši noderīgi, strādājot ar 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]

Šis piemērs vispirms definē divas asinhronas funkcijas, getUSDRate un getEURRate, kas simulē valūtas kursu iegūšanu. Utilītas tips PromiseArrayReturnType pēc tam ekstrahē atrisināto tipu no katra Promise masīvā, rezultātā iegūstot korteža tipu, kur katrs elements ir atbilstošā Promise gaidītais (awaited) tips.

Praktiski piemēri dažādās jomās

1. E-komercijas lietotne

Apsveriet e-komercijas lietotni, kurā jūs iegūstat produkta datus no API. Jūs varat izmantot infer, lai ekstrahētu produkta datu tipu:

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> {
  // Simulē API izsaukumu
  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);

Šajā piemērā mēs definējam Product interfeisu un fetchProduct funkciju, kas iegūst produkta datus no API. Mēs izmantojam Awaited un ReturnType, lai ekstrahētu Product tipu no fetchProduct funkcijas atgrieztā tipa, kas ļauj mums veikt tipu pārbaudi displayProductDetails funkcijai.

2. Internacionalizācija (i18n)

Pieņemsim, ka jums ir tulkošanas funkcija, kas atgriež dažādas virknes atkarībā no lokalizācijas. Jūs varat izmantot infer, lai ekstrahētu šīs funkcijas atgriezto tipu tipu drošībai:

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!

Šeit TranslationType tiek noteikts kā Translations interfeiss, nodrošinot, ka greetUser funkcijai ir pareiza tipa informācija, lai piekļūtu tulkotajām virknēm.

3. API atbilžu apstrāde

Strādājot ar API, atbildes struktūra var būt sarežģīta. infer var palīdzēt ekstrahēt specifiskus datu tipus no ligzdotām API atbildēm:

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>> {
  // Simulē API izsaukumu
  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);
  }
});

Šajā piemērā mēs definējam ApiResponse interfeisu un UserData interfeisu. Mēs izmantojam infer un tipu indeksēšanu, lai ekstrahētu UserProfileType no API atbildes, nodrošinot, ka displayUserProfile funkcija saņem pareizo tipu.

Labākās prakses infer lietošanai

Biežākās kļūdas

Alternatīvas infer

Lai gan infer ir jaudīgs rīks, ir situācijas, kurās piemērotākas varētu būt alternatīvas pieejas:

Noslēgums

infer atslēgvārds TypeScript, kombinācijā ar nosacījumu tipiem, paver padziļinātas tipu manipulācijas iespējas. Tas ļauj jums ekstrahēt specifiskus tipus no sarežģītām tipu struktūrām, dodot iespēju rakstīt robustāku, uzturamāku un tipu drošāku kodu. No funkciju atgriezto tipu noteikšanas līdz īpašību ekstrakcijai no objektu tipiem, iespējas ir plašas. Izprotot šajā ceļvedī izklāstītos principus un labākās prakses, jūs varat pilnībā izmantot infer potenciālu un uzlabot savas TypeScript prasmes. Atcerieties dokumentēt savus tipus, rūpīgi tos pārbaudīt un apsvērt alternatīvas pieejas, ja tas ir nepieciešams. Apgūstot infer, jūs varēsiet rakstīt patiesi izteiksmīgu un jaudīgu TypeScript kodu, kas galu galā novedīs pie labākas programmatūras.