Lietuvių

Išsamus TypeScript „infer“ raktažodžio vadovas, paaiškinantis, kaip jį naudoti su sąlyginiais tipais galingam tipų išskyrimui ir manipuliavimui, įskaitant pažangius atvejus.

TypeScript „Infer“ įvaldymas: sąlyginių tipų išskyrimas pažangiam tipų manipuliavimui

TypeScript tipų sistema yra neįtikėtinai galinga, leidžianti programuotojams kurti tvirtas ir lengvai prižiūrimas programas. Viena iš pagrindinių savybių, suteikiančių šią galią, yra infer raktažodis, naudojamas kartu su sąlyginiais tipais. Šis derinys suteikia mechanizmą, leidžiantį išskirti konkrečius tipus iš sudėtingų tipų struktūrų. Šiame tinklaraščio įraše gilinamasi į infer raktažodį, paaiškinama jo funkcionalumas ir parodomi pažangūs naudojimo atvejai. Nagrinėsime praktinius pavyzdžius, taikomus įvairiuose programinės įrangos kūrimo scenarijuose, nuo sąveikos su API iki sudėtingų duomenų struktūrų manipuliavimo.

Kas yra sąlyginiai tipai?

Prieš pasinerdami į infer, greitai peržvelkime sąlyginius tipus. Sąlyginiai tipai TypeScript kalboje leidžia apibrėžti tipą pagal sąlygą, panašiai kaip trijų dalių operatorius JavaScript. Bazinė sintaksė yra:

T extends U ? X : Y

Tai skaitoma taip: „Jei tipas T yra priskiriamas tipui U, tada tipas yra X; kitu atveju, tipas yra Y.“

Pavyzdys:

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

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

Pristatome infer raktažodį

infer raktažodis naudojamas sąlyginio tipo extends sakinyje, siekiant deklaruoti tipo kintamąjį, kurį galima nustatyti (išvesti) iš tikrinamo tipo. Iš esmės, jis leidžia „pagauti“ dalį tipo vėlesniam naudojimui.

Bazinė sintaksė:

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

Šiame pavyzdyje, jei T yra priskiriamas kokiam nors tipui, TypeScript bandys išvesti U tipą. Jei išvedimas sėkmingas, tipas bus U; kitu atveju, jis bus never.

Paprasti infer pavyzdžiai

1. Funkcijos grąžinamo tipo išvedimas

Dažnas naudojimo atvejis yra funkcijos grąžinamo tipo išvedimas:

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>; // tipas AddReturnType = number

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

type GreetReturnType = ReturnType<typeof greet>; // tipas GreetReturnType = string

Šiame pavyzdyje ReturnType<T> priima funkcijos tipą T kaip įvestį. Jis tikrina, ar T yra priskiriamas funkcijai, kuri priima bet kokius argumentus ir grąžina reikšmę. Jei taip, jis išveda grąžinamą tipą kaip R ir jį grąžina. Kitu atveju, jis grąžina any.

2. Masyvo elemento tipo išvedimas

Kitas naudingas scenarijus yra elemento tipo išskyrimas iš masyvo:

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

type NumberArrayType = ArrayElementType<number[]>; // tipas NumberArrayType = number
type StringArrayType = ArrayElementType<string[]>; // tipas StringArrayType = string
type MixedArrayType = ArrayElementType<(string | number)[]>; // tipas MixedArrayType = string | number
type NotAnArrayType = ArrayElementType<number>; // tipas NotAnArrayType = never

Čia ArrayElementType<T> tikrina, ar T yra masyvo tipas. Jei taip, jis išveda elemento tipą kaip U ir jį grąžina. Jei ne, jis grąžina never.

Pažangūs infer naudojimo atvejai

1. Konstruktoriaus parametrų išvedimas

Galite naudoti infer, kad išskirtumėte konstruktoriaus funkcijos parametrų 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>; // tipas PersonConstructorParams = [string, number]

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

type PointConstructorParams = ConstructorParameters<typeof Point>; // tipas PointConstructorParams = [number, number]

Šiuo atveju ConstructorParameters<T> priima konstruktoriaus funkcijos tipą T. Jis išveda konstruktoriaus parametrų tipus kaip P ir grąžina juos kaip kortežą (tuple).

2. Savybių išskyrimas iš objektų tipų

infer taip pat gali būti naudojamas išskirti konkrečias savybes iš objektų tipų, naudojant susietus (mapped) ir sąlyginius 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>; // tipas StringProperties = { name: string; email: string; }

type NumberProperties = PickByType<User, keyof User, number>; // tipas NumberProperties = { id: number; age: number; }

//Sąsaja, aprašanti geografines koordinates.
interface GeoCoordinates {
    latitude: number;
    longitude: number;
    altitude: number;
    country: string;
    city: string;
    timezone: string;
}

type NumberCoordinateProperties = PickByType<GeoCoordinates, keyof GeoCoordinates, number>; // tipas NumberCoordinateProperties = { latitude: number; longitude: number; altitude: number; }

Čia PickByType<T, K, U> sukuria naują tipą, kuris apima tik tas T savybes (kurių raktai yra K), kurių reikšmės yra priskiriamos tipui U. Susietas tipas iteruoja per T raktus, o sąlyginis tipas filtruoja raktus, kurie neatitinka nurodyto tipo.

3. Darbas su „Promise“

Galite išvesti išspręstą (resolved) Promise tipą:

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>>; // tipas FetchDataType = string

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

type FetchedNumbersType = Awaited<ReturnType<typeof fetchNumbers>>; //tipas FetchedNumbersType = number[]

Tipas Awaited<T> priima tipą T, kuris, tikimasi, yra „Promise“. Tada tipas išveda išspręstą „Promise“ tipą U ir jį grąžina. Jei T nėra „promise“, jis grąžina T. Tai yra įmontuotas pagalbinis tipas naujesnėse TypeScript versijose.

4. Tipo išskyrimas iš „Promise“ masyvo

Sujungus Awaited ir masyvo tipo išvedimą, galima išvesti tipą, kurį išsprendžia „Promise“ masyvas. Tai ypač naudinga dirbant su 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>;
// tipas RatesType = [number, number]

Šiame pavyzdyje pirmiausia apibrėžiamos dvi asinchroninės funkcijos, getUSDRate ir getEURRate, kurios imituoja valiutų kursų gavimą. Pagalbinis tipas PromiseArrayReturnType tada išskiria išspręstą tipą iš kiekvieno Promise masyve, gaunant kortežo tipą, kuriame kiekvienas elementas yra atitinkamo „Promise“ lauktas tipas.

Praktiniai pavyzdžiai iš įvairių sričių

1. Elektroninės komercijos programa

Įsivaizduokite elektroninės komercijos programą, kurioje gaunate išsamią produkto informaciją iš API. Galite naudoti infer, kad išskirtumėte produkto duomenų tipą:

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> {
  // Imituojamas API iškvietimas
  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>>; // tipas 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);

Šiame pavyzdyje apibrėžiame Product sąsają ir fetchProduct funkciją, kuri gauna produkto informaciją iš API. Naudojame Awaited ir ReturnType, kad išskirtume Product tipą iš fetchProduct funkcijos grąžinamo tipo, leisdami mums atlikti tipo patikrinimą displayProductDetails funkcijai.

2. Internacionalizacija (i18n)

Tarkime, turite vertimo funkciją, kuri grąžina skirtingas eilutes priklausomai nuo lokalės. Galite naudoti infer, kad išskirtumėte šios funkcijos grąžinamą tipą tipo saugumui užtikrinti:

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

Čia TranslationType yra išvedamas kaip Translations sąsaja, užtikrinant, kad greetUser funkcija turėtų teisingą tipo informaciją prieigai prie išverstų eilučių.

3. API atsakymų tvarkymas

Dirbant su API, atsakymo struktūra gali būti sudėtinga. infer gali padėti išskirti konkrečius duomenų tipus iš įdėtų API atsakymų:

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>> {
  // Imituojamas API iškvietimas
  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);
  }
});

Šiame pavyzdyje apibrėžiame ApiResponse ir UserData sąsajas. Naudojame infer ir tipų indeksavimą, kad išskirtume UserProfileType iš API atsakymo, užtikrindami, kad displayUserProfile funkcija gautų teisingą tipą.

Geroji infer naudojimo praktika

Dažniausios klaidos

infer alternatyvos

Nors infer yra galingas įrankis, yra situacijų, kai alternatyvūs metodai gali būti tinkamesni:

Išvada

infer raktažodis TypeScript kalboje, derinamas su sąlyginiais tipais, atveria pažangias tipų manipuliavimo galimybes. Jis leidžia išskirti konkrečius tipus iš sudėtingų tipų struktūrų, suteikdamas galimybę rašyti tvirtesnį, lengviau prižiūrimą ir tipo požiūriu saugesnį kodą. Nuo funkcijų grąžinamų tipų išvedimo iki savybių išskyrimo iš objektų tipų – galimybės yra didžiulės. Suprasdami šiame vadove išdėstytus principus ir geriausias praktikas, galite išnaudoti visas infer galimybes ir pakelti savo TypeScript įgūdžius. Nepamirškite dokumentuoti savo tipų, kruopščiai juos testuoti ir prireikus apsvarstyti alternatyvius metodus. Įvaldę infer, galėsite rašyti išties išraiškingą ir galingą TypeScript kodą, o tai galiausiai leis kurti geresnę programinę įrangą.