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
- Paprastumas: Naudokite
infer
tik tada, kai tai būtina. Pernelyg dažnas naudojimas gali padaryti jūsų kodą sunkiau skaitomą ir suprantamą. - Dokumentuokite savo tipus: Pridėkite komentarus, paaiškinančius, ką daro jūsų sąlyginiai tipai ir
infer
sakiniai. - Testuokite savo tipus: Naudokite TypeScript tipo patikrinimą, kad įsitikintumėte, jog jūsų tipai veikia kaip tikėtasi.
- Atsižvelkite į našumą: Sudėtingi sąlyginiai tipai kartais gali paveikti kompiliavimo laiką. Būkite atidūs savo tipų sudėtingumui.
- Naudokite pagalbinius tipus: TypeScript teikia keletą įmontuotų pagalbinių tipų (pvz.,
ReturnType
,Awaited
), kurie gali supaprastinti jūsų kodą ir sumažinti poreikį kurti pasirinktiniusinfer
sakinius.
Dažniausios klaidos
- Neteisingas išvedimas: Kartais TypeScript gali išvesti tipą, kuris nėra toks, kokio tikitės. Dukart patikrinkite savo tipų apibrėžimus ir sąlygas.
- Ciklinės priklausomybės: Būkite atsargūs apibrėždami rekursinius tipus naudojant
infer
, nes jie gali sukelti ciklinių priklausomybių ir kompiliavimo klaidų. - Perdėtai sudėtingi tipai: Venkite kurti perdėtai sudėtingus sąlyginius tipus, kuriuos sunku suprasti ir prižiūrėti. Suskaidykite juos į mažesnius, lengviau valdomus tipus.
infer
alternatyvos
Nors infer
yra galingas įrankis, yra situacijų, kai alternatyvūs metodai gali būti tinkamesni:
- Tipų tvirtinimai (Type Assertions): Kai kuriais atvejais galite naudoti tipų tvirtinimus, kad aiškiai nurodytumėte reikšmės tipą, užuot jį išvedus. Tačiau būkite atsargūs su tipų tvirtinimais, nes jie gali apeiti tipo patikrinimą.
- Tipų apsaugos (Type Guards): Tipų apsaugos gali būti naudojamos siekiant susiaurinti reikšmės tipą remiantis vykdymo laiko patikrinimais. Tai naudinga, kai reikia tvarkyti skirtingus tipus pagal vykdymo laiko sąlygas.
- Pagalbiniai tipai (Utility Types): TypeScript teikia platų pagalbinių tipų rinkinį, kuris gali atlikti daugelį įprastų tipų manipuliavimo užduočių be poreikio kurti pasirinktinius
infer
sakinius.
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ą.