Komplexný sprievodca kľúčovým slovom 'infer' v TypeScripte, vysvetľujúci ako ho používať s podmienenými typmi pre výkonnú extrakciu a manipuláciu s typmi, vrátane pokročilých prípadov použitia.
Zvládnutie TypeScript Infer: Podmienená Extrakcia Typov pre Pokročilú Manipuláciu s Typmi
Typový systém TypeScriptu je neuveriteľne výkonný a umožňuje vývojárom vytvárať robustné a udržiavateľné aplikácie. Jednou z kľúčových vlastností, ktoré umožňujú túto silu, je kľúčové slovo infer
používané v spojení s podmienenými typmi. Táto kombinácia poskytuje mechanizmus na extrahovanie špecifických typov z komplexných typových štruktúr. Tento blogový príspevok sa ponorí hlboko do kľúčového slova infer
, vysvetľuje jeho funkčnosť a predstavuje pokročilé prípady použitia. Preskúmame praktické príklady použiteľné v rôznych scenároch vývoja softvéru, od interakcie s API až po komplexnú manipuláciu s dátovými štruktúrami.
Čo sú Podmienené Typy?
Skôr ako sa ponoríme do infer
, rýchlo si zopakujme podmienené typy. Podmienené typy v TypeScripte vám umožňujú definovať typ na základe podmienky, podobne ako ternárny operátor v JavaScripte. Základná syntax je:
T extends U ? X : Y
Toto sa číta ako: "Ak je typ T
priraditeľný k typu U
, potom je typ X
; inak je typ Y
."
Príklad:
type IsString<T> = T extends string ? true : false;
type StringResult = IsString<string>; // type StringResult = true
type NumberResult = IsString<number>; // type NumberResult = false
Predstavujeme Kľúčové Slovo infer
Kľúčové slovo infer
sa používa v rámci klauzuly extends
podmieneného typu na deklarovanie typovej premennej, ktorá sa dá odvodiť z kontrolovaného typu. V podstate vám umožňuje "zachytiť" časť typu na neskoršie použitie.
Základná Syntax:
type MyType<T> = T extends (infer U) ? U : never;
V tomto príklade, ak je T
priraditeľné k nejakému typu, TypeScript sa pokúsi odvodiť typ U
. Ak je odvodenie úspešné, typ bude U
; inak bude never
.
Jednoduché Príklady infer
1. Odvodenie Návratového Typu Funkcie
Bežným prípadom použitia je odvodenie návratového typu funkcie:
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
V tomto príklade, ReturnType<T>
berie typ funkcie T
ako vstup. Kontroluje, či je T
priraditeľné k funkcii, ktorá prijíma ľubovoľné argumenty a vracia hodnotu. Ak áno, odvodí návratový typ ako R
a vráti ho. Inak vráti any
.
2. Odvodenie Typu Prvku Poľa
Ďalším užitočným scenárom je extrahovanie typu prvku z poľa:
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
Tu ArrayElementType<T>
kontroluje, či je T
typ poľa. Ak áno, odvodí typ prvku ako U
a vráti ho. Ak nie, vráti never
.
Pokročilé Prípady Použitia infer
1. Odvodenie Parametrov Konštruktora
Môžete použiť infer
na extrahovanie typov parametrov konštruktorovej funkcie:
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]
V tomto prípade, ConstructorParameters<T>
berie typ konštruktorovej funkcie T
. Odvodí typy parametrov konštruktora ako P
a vráti ich ako tuple.
2. Extrahovanie Vlastností z Objektových Typov
infer
sa dá použiť aj na extrahovanie špecifických vlastností z objektových typov pomocou mapovaných typov a podmienených typov:
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; }
Tu, PickByType<T, K, U>
vytvára nový typ, ktorý obsahuje iba vlastnosti T
(s kľúčmi v K
), ktorých hodnoty sú priraditeľné k typu U
. Mapovaný typ iteruje cez kľúče T
a podmienený typ odfiltruje kľúče, ktoré nezodpovedajú zadanému typu.
3. Práca s Promise
Môžete odvodiť vyriešený typ Promise
:
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[]
Typ Awaited<T>
berie typ T
, ktorý sa očakáva, že bude Promise. Typ potom odvodí vyriešený typ U
z Promise a vráti ho. Ak T
nie je promise, vráti T. Toto je vstavaný pomocný typ v novších verziách TypeScriptu.
4. Extrahovanie Typu z Poľa Promise
Kombinácia Awaited
a odvodenia typu poľa vám umožňuje odvodiť typ vyriešený poľom Promise. Toto je obzvlášť užitočné pri práci s 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]
Tento príklad najprv definuje dve asynchrónne funkcie, getUSDRate
a getEURRate
, ktoré simulujú načítanie výmenných kurzov. Pomocný typ PromiseArrayReturnType
potom extrahuje vyriešený typ z každého Promise
v poli, čo vedie k typu tuple, kde každý prvok je očakávaný typ zodpovedajúceho Promise.
Praktické Príklady v Rôznych Doménach
1. Aplikácia Elektronického Obchodu
Zvážte aplikáciu elektronického obchodu, kde načítate podrobnosti o produkte z API. Môžete použiť infer
na extrahovanie typu údajov o produkte:
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);
V tomto príklade definujeme rozhranie Product
a funkciu fetchProduct
, ktorá načítava podrobnosti o produkte z API. Používame Awaited
a ReturnType
na extrahovanie typu Product
z návratového typu funkcie fetchProduct
, čo nám umožňuje kontrolovať typ funkcie displayProductDetails
.
2. Internacionalizácia (i18n)
Predpokladajme, že máte prekladovú funkciu, ktorá vracia rôzne reťazce na základe lokality. Môžete použiť infer
na extrahovanie návratového typu tejto funkcie pre typovú bezpečnosť:
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!
Tu je TranslationType
odvodený ako rozhranie Translations
, čo zaisťuje, že funkcia greetUser
má správne informácie o type na prístup k preloženým reťazcom.
3. Spracovanie Odpovedí API
Pri práci s API môže byť štruktúra odpovede komplexná. infer
vám môže pomôcť extrahovať špecifické typy údajov z vnorených odpovedí API:
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);
}
});
V tomto príklade definujeme rozhranie ApiResponse
a rozhranie UserData
. Používame infer
a indexovanie typov na extrahovanie UserProfileType
z odpovede API, čím zabezpečujeme, že funkcia displayUserProfile
dostane správny typ.
Osvedčené Postupy pre Používanie infer
- Nechajte to Jednoduché: Používajte
infer
iba vtedy, keď je to nevyhnutné. Nadmerné používanie môže sťažiť čítanie a pochopenie vášho kódu. - Dokumentujte Svoje Typy: Pridajte komentáre na vysvetlenie, čo robia vaše podmienené typy a príkazy
infer
. - Testujte Svoje Typy: Použite kontrolu typov TypeScriptu, aby ste sa uistili, že sa vaše typy správajú podľa očakávaní.
- Zvážte Výkon: Komplexné podmienené typy môžu niekedy ovplyvniť čas kompilácie. Dávajte pozor na zložitosť vašich typov.
- Používajte Pomocné Typy: TypeScript poskytuje niekoľko vstavaných pomocných typov (napr.
ReturnType
,Awaited
), ktoré môžu zjednodušiť váš kód a znížiť potrebu vlastných príkazovinfer
.
Bežné Úskalia
- Nesprávne Odvodenie: Niekedy môže TypeScript odvodiť typ, ktorý nie je to, čo očakávate. Skontrolujte svoje definície typov a podmienky.
- Cirkulárne Závislosti: Buďte opatrní pri definovaní rekurzívnych typov pomocou
infer
, pretože môžu viesť k cirkulárnym závislostiam a chybám kompilácie. - Príliš Komplexné Typy: Vyhnite sa vytváraniu príliš komplexných podmienených typov, ktoré je ťažké pochopiť a udržiavať. Rozdeľte ich na menšie, spravovateľnejšie typy.
Alternatívy k infer
Zatiaľ čo infer
je výkonný nástroj, existujú situácie, kedy môžu byť vhodnejšie alternatívne prístupy:
- Typové Asertácie: V niektorých prípadoch môžete použiť typové asertácie na explicitné určenie typu hodnoty namiesto odvodzovania. Buďte však opatrní s typovými asertáciami, pretože môžu obísť kontrolu typov.
- Typové Stráženia: Typové stráženia sa dajú použiť na zúženie typu hodnoty na základe kontrol za behu. Toto je užitočné, keď potrebujete spracovať rôzne typy na základe podmienok za behu.
- Pomocné Typy: TypeScript poskytuje bohatú sadu pomocných typov, ktoré dokážu zvládnuť mnoho bežných úloh manipulácie s typmi bez potreby vlastných príkazov
infer
.
Záver
Kľúčové slovo infer
v TypeScripte, v kombinácii s podmienenými typmi, odomyká pokročilé možnosti manipulácie s typmi. Umožňuje vám extrahovať špecifické typy z komplexných typových štruktúr, čo vám umožňuje písať robustnejší, udržiavateľnejší a typovo bezpečnejší kód. Od odvodzovania návratových typov funkcií po extrahovanie vlastností z objektových typov, možnosti sú rozsiahle. Pochopením princípov a osvedčených postupov uvedených v tejto príručke môžete využiť infer
naplno a pozdvihnúť svoje zručnosti v TypeScripte. Nezabudnite dokumentovať svoje typy, dôkladne ich testovať a zvážiť alternatívne prístupy, ak je to vhodné. Zvládnutie infer
vám umožňuje písať skutočne expresívny a výkonný kód TypeScriptu, čo v konečnom dôsledku vedie k lepšiemu softvéru.