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
- Vienkāršība: Izmantojiet
infer
tikai tad, kad tas ir nepieciešams. Pārmērīga lietošana var padarīt jūsu kodu grūtāk lasāmu un saprotamu. - Dokumentējiet savus tipus: Pievienojiet komentārus, lai paskaidrotu, ko dara jūsu nosacījumu tipi un
infer
izteiksmes. - Pārbaudiet savus tipus: Izmantojiet TypeScript tipu pārbaudi, lai nodrošinātu, ka jūsu tipi darbojas kā paredzēts.
- Apsveriet veiktspēju: Sarežģīti nosacījumu tipi dažkārt var ietekmēt kompilācijas laiku. Esiet uzmanīgi ar savu tipu sarežģītību.
- Izmantojiet utilītu tipus: TypeScript nodrošina vairākus iebūvētus utilītu tipus (piemēram,
ReturnType
,Awaited
), kas var vienkāršot jūsu kodu un samazināt nepieciešamību pēc pielāgotāminfer
izteiksmēm.
Biežākās kļūdas
- Nepareiza tipa noteikšana: Dažreiz TypeScript var noteikt tipu, kas nav tas, ko jūs sagaidāt. Vēlreiz pārbaudiet savas tipu definīcijas un nosacījumus.
- Cikliskas atkarības: Esiet uzmanīgi, definējot rekursīvus tipus ar
infer
, jo tie var novest pie cikliskām atkarībām un kompilācijas kļūdām. - Pārāk sarežģīti tipi: Izvairieties no pārāk sarežģītu nosacījumu tipu veidošanas, kurus ir grūti saprast un uzturēt. Sadaliet tos mazākos, vieglāk pārvaldāmos tipos.
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:
- Tipu apgalvojumi (Type Assertions): Dažos gadījumos varat izmantot tipu apgalvojumus, lai tieši norādītu vērtības tipu, nevis to noteiktu. Tomēr esiet piesardzīgi ar tipu apgalvojumiem, jo tie var apiet tipu pārbaudi.
- Tipu aizsargi (Type Guards): Tipu aizsargus var izmantot, lai sašaurinātu vērtības tipu, pamatojoties uz izpildlaika pārbaudēm. Tas ir noderīgi, ja nepieciešams apstrādāt dažādus tipus atkarībā no izpildlaika nosacījumiem.
- Utilītu tipi: TypeScript nodrošina bagātīgu utilītu tipu kopumu, kas var veikt daudzus izplatītus tipu manipulācijas uzdevumus bez nepieciešamības pēc pielāgotām
infer
izteiksmēm.
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.