Eesti

Täielik juhend TypeScripti 'infer'-märksõnale, selgitades selle kasutamist tingimuslike tüüpidena võimsaks tüübi väljavõtteks ja manipuleerimiseks, sealhulgas täpsemad kasutusjuhud.

TypeScripti infer-lause omandamine: Tingimuslikud tüübi väljavõtted täpsemaks tüübi manipuleerimiseks

TypeScripti tüübisüsteem on uskumatult võimas, võimaldades arendajatel luua robustseid ja hooldatavaid rakendusi. Üks peamisi funktsioone, mis seda võimu võimaldab, on infer-märksõna, mida kasutatakse koos tingimuslike tüüpidena. See kombinatsioon pakub mehhanismi konkreetsete tüüpide väljavõtmiseks keerulistest tüübistruktuuridest. See ajaveebipostitus sukeldub sügavale infer-märksõnasse, selgitades selle funktsionaalsust ja näidates täpsemaid kasutusjuhte. Uurime praktilisi näiteid, mida saab rakendada erinevates tarkvaraarenduse stsenaariumides, alates API interaktsioonist kuni keerulise andmestruktuuri manipuleerimiseni.

Mis on tingimuslikud tüübid?

Enne kui sukeldume infer-i, vaatame kiiresti üle tingimuslikud tüübid. Tingimuslikud tüübid TypeScriptis võimaldavad teil määratleda tüübi tingimuse põhjal, sarnaselt JavaScripti kolmikoperaatoriga. Põhisüntaks on:

T extends U ? X : Y

See loeb: "Kui tüüp T on tüübile U omistatav, siis tüüp on X; vastasel juhul on tüüp Y."

Näide:

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

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

Tutvustus infer-märksõnale

infer-märksõna kasutatakse tingimusliku tüübi extends klauslis, et deklareerida tüübimuutuja, mida saab kontrollitavast tüübist järeldada. Sisuliselt võimaldab see teil "püüda" osa tüübist hilisemaks kasutamiseks.

Põhisüntaks:

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

Selles näites, kui T on omistatav mingile tüübile, püüab TypeScript järeldada U tüüpi. Kui järeldus on edukas, on tüüp U; vastasel juhul on see never.

Lihtsad infer-i näited

1. Funktsiooni tagastustüübi järeldamine

Tavaline kasutusjuht on funktsiooni tagastustüübi järeldamine:

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

Selles näites võtab ReturnType<T> sisendiks funktsioonitüübi T. See kontrollib, kas T on omistatav funktsioonile, mis aktsepteerib mis tahes argumente ja tagastab väärtuse. Kui see nii on, järeldab see tagastustüübi kui R ja tagastab selle. Vastasel juhul tagastab see any.

2. Massiivi elemendi tüübi järeldamine

Teine kasulik stsenaarium on massiivi elemendi tüübi väljavõtmine:

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

Siin kontrollib ArrayElementType<T>, kas T on massiivitüüp. Kui see on nii, järeldab see elemendi tüübi kui U ja tagastab selle. Kui ei, siis tagastab never.

infer-i täpsemad kasutusjuhud

1. Konstruktori parameetrite järeldamine

Konstruktorifunktsiooni parameetritüüpide väljavõtmiseks saate kasutada infer-i:

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]

Sel juhul võtab ConstructorParameters<T> sisendiks konstruktorifunktsiooni tüübi T. See järeldab konstruktoriparameetrite tüübid kui P ja tagastab need tuplena.

2. Objektitüüpide omaduste väljavõtmine

infer-i saab kasutada ka objektitüüpide konkreetsete omaduste väljavõtmiseks, kasutades kaardistatud tüüpe ja tingimuslikke tüüpe:

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

Siin loob PickByType<T, K, U> uue tüübi, mis sisaldab ainult T omadusi (võtmetega K), mille väärtused on tüübile U omistatavad. Kaardistatud tüüp itereerib T võtmeid ja tingimuslik tüüp filtreerib välja võtmed, mis ei vasta määratud tüübile.

3. Promise'idega töötamine

Saate Promise-i lahendatud tüübi järeldada:

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> tüüp võtab tüübi T, mis eeldatavasti on Promise. Seejärel järeldab tüüp Promise'i lahendatud tüübi U ja tagastab selle. Kui T ei ole Promise, tagastab see T. See on TypeScripti uuemates versioonides sisseehitatud utiliittüüp.

4. Promise'ide massiivi tüübi väljavõtmine

Awaited ja massiivitüübi järeldamise kombineerimine võimaldab teil järeldada Promise'ide massiivi lahendatud tüübi. See on eriti kasulik, kui töötate Promise.all-iga.

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]

See näide määratleb esmalt kaks asünkroonset funktsiooni, getUSDRate ja getEURRate, mis simuleerivad vahetuskursside hankimist. Seejärel ekstraheerib PromiseArrayReturnType utiliittüüp lahendatud tüübi igast massiivis olevast Promise-ist, mille tulemuseks on tupletüüp, kus iga element on vastava Promise'i oodatud tüüp.

Praktilised näited erinevates valdkondades

1. E-kaubanduse rakendus

Mõelge e-kaubanduse rakendusele, kus hangite toote üksikasju API-st. Saate tooteandmete tüübi väljavõtmiseks kasutada infer-i:

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

Selles näites määratleme Product liidese ja fetchProduct funktsiooni, mis hangib toote üksikasjad API-st. Kasutame Awaited ja ReturnType, et eraldada Product tüüp fetchProduct funktsiooni tagastustüübist, võimaldades meil displayProductDetails funktsiooni tüüpkontrollida.

2. Rahvusvahelistumine (i18n)

Oletagem, et teil on tõlkefunktsioon, mis tagastab sõltuvalt lokaadist erinevaid stringe. Tüübiga turvalisuse tagamiseks saate selle funktsiooni tagastustüübi väljavõtmiseks kasutada infer-i:

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!

Siin järeldatakse TranslationType Translations liidesena, tagades, et greetUser funktsioonil on tõlgitud stringidele juurdepääsemiseks õige tüübiteave.

3. API vastuse käitlemine

API-dega töötamisel võib vastuse struktuur olla keeruline. infer saab aidata konkreetsete andmetüüpide väljavõtmisel pesastatud API vastustest:

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

Selles näites määratleme ApiResponse liidese ja UserData liidese. Kasutame infer-i ja tüübi indekseerimist, et eraldada UserProfileType API vastusest, tagades, et displayUserProfile funktsioon saab õige tüübi.

infer-i kasutamise parimad tavad

Levinumad probleemid

infer-i alternatiivid

Kuigi infer on võimas tööriist, on olukordi, kus alternatiivsed lähenemisviisid võivad olla sobivamad:

Kokkuvõte

TypeScripti infer-märksõna koos tingimuslike tüüpidega avab täpsemad tüübi manipuleerimise võimalused. See võimaldab teil eraldada konkreetseid tüüpe keerulistest tüübistruktuuridest, võimaldades teil kirjutada robustsemat, hooldatavamat ja tüübiga turvalisemat koodi. Alates funktsiooni tagastustüüpide järeldamisest kuni objektitüüpide omaduste väljavõtmiseni on võimalused tohutud. Mõistes selles juhendis välja toodud põhimõtteid ja parimaid tavasid, saate infer-i kasutada täielikult ja tõsta oma TypeScripti oskusi. Pidage meeles oma tüüpide dokumenteerimist, nende põhjalikku testimist ja vajadusel kaaluge alternatiivseid lähenemisviise. infer-i omandamine annab teile võimaluse kirjutada tõeliselt väljendusrikast ja võimsat TypeScripti koodi, mis lõppkokkuvõttes viib parema tarkvarani.