Kattava opas TypeScriptin 'infer'-avainsanaan, joka selittää kuinka sitä käytetään ehdollisten tyyppien kanssa tehokkaaseen tyypin purkamiseen ja manipulointiin, mukaan lukien edistyneet käyttötapaukset.
TypeScript Inferin hallinta: Ehdollinen tyypin purku edistyneeseen tyyppimanipulaatioon
TypeScriptin tyyppijärjestelmä on uskomattoman tehokas, minkä ansiosta kehittäjät voivat luoda vankkoja ja ylläpidettäviä sovelluksia. Yksi tärkeimmistä ominaisuuksista, joka mahdollistaa tämän tehon, on infer
-avainsana, jota käytetään yhdessä ehdollisten tyyppien kanssa. Tämä yhdistelmä tarjoaa mekanismin tiettyjen tyyppien purkamiseen monimutkaisista tyyppirakenteista. Tämä blogikirjoitus syventyy infer
-avainsanaan, selittää sen toiminnallisuuden ja esittelee edistyneitä käyttötapauksia. Tutustumme käytännön esimerkkeihin, jotka soveltuvat monipuolisiin ohjelmistokehityksen skenaarioihin, API-vuorovaikutuksesta monimutkaiseen tietorakenteiden manipulointiin.
Mitä ovat ehdolliset tyypit?
Ennen kuin sukellamme infer
-avainsanaan, katsotaanpa nopeasti ehdollisia tyyppejä. Ehdollisten tyyppien avulla TypeScriptissä voit määrittää tyypin ehdon perusteella, samalla tavalla kuin kolmioperandinen operaattori JavaScriptissä. Perussyntaksi on:
T extends U ? X : Y
Tämä luetaan: "Jos tyyppi T
on sijoitettavissa tyyppiin U
, tyyppi on X
; muuten tyyppi on Y
."
Esimerkki:
type IsString<T> = T extends string ? true : false;
type StringResult = IsString<string>; // type StringResult = true
type NumberResult = IsString<number>; // type NumberResult = false
Esittelyssä infer
-avainsana
infer
-avainsanaa käytetään ehdollisen tyypin extends
-lausekkeessa tyyppimuuttujan määrittämiseen, joka voidaan päätellä tarkistettavasta tyypistä. Pohjimmiltaan sen avulla voit "kaapata" osan tyypistä myöhempää käyttöä varten.
Perussyntaksi:
type MyType<T> = T extends (infer U) ? U : never;
Tässä esimerkissä, jos T
on sijoitettavissa johonkin tyyppiin, TypeScript yrittää päätellä tyypin U
. Jos päättely onnistuu, tyyppi on U
; muuten se on never
.
Yksinkertaisia esimerkkejä infer
-avainsanasta
1. Funktion palautustyypin päättely
Yleinen käyttötapaus on funktion palautustyypin päättely:
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
Tässä esimerkissä ReturnType<T>
ottaa funktioliikkeen T
syötteenä. Se tarkistaa, onko T
sijoitettavissa funktioon, joka hyväksyy argumentteja ja palauttaa arvon. Jos on, se päättelee palautustyypin nimellä R
ja palauttaa sen. Muuten se palauttaa any
.
2. Taulukon elementtityypin päättely
Toinen hyödyllinen skenaario on elementtityypin purkaminen taulukosta:
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
Tässä ArrayElementType<T>
tarkistaa, onko T
taulukkotyyppi. Jos on, se päättelee elementtityypin nimellä U
ja palauttaa sen. Jos ei, se palauttaa never
.
Edistyneitä käyttötapauksia infer
-avainsanalle
1. Konstruktorin parametrien päättely
Voit käyttää infer
-avainsanaa konstruktorifunktion parametrien tyyppien purkamiseen:
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]
Tässä tapauksessa ConstructorParameters<T>
ottaa konstruktorifunktiotyypin T
. Se päättelee konstruktoriparametrien tyypit nimellä P
ja palauttaa ne tupleena.
2. Ominaisuuksien purkaminen objektityypeistä
infer
-avainsanaa voidaan käyttää myös tiettyjen ominaisuuksien purkamiseen objektityypeistä käyttämällä yhdistettyjä tyyppejä ja ehdollisia tyyppejä:
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; }
Tässä PickByType<T, K, U>
luo uuden tyypin, joka sisältää vain T
:n ominaisuudet (avaimilla K
), joiden arvot ovat sijoitettavissa tyyppiin U
. Yhdistetty tyyppi iteroi T
:n avaimet, ja ehdollinen tyyppi suodattaa avaimet, jotka eivät vastaa määritettyä tyyppiä.
3. Työskentely Promisejen kanssa
Voit päätellä Promise
n ratkaistun tyypin:
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[]
Tyyppi Awaited<T>
ottaa tyypin T
, jonka odotetaan olevan Promise. Tyyppi päättelee sitten Promisen ratkaistun tyypin U
ja palauttaa sen. Jos T
ei ole lupaus, se palauttaa T:n. Tämä on sisäänrakennettu aputyypin uudemmissa TypeScript-versioissa.
4. Lupausjoukon tyypin purkaminen
YhdistämälläAwaited
ja taulukkotyyppien päättelyn voit päätellä lupausjoukon ratkaiseman tyypin. Tämä on erityisen hyödyllistä käsiteltäessä Promise.all
-funktiota.
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]
Tässä esimerkissä määritellään ensin kaksi asynkronista funktiota, getUSDRate
ja getEURRate
, jotka simuloivat valuuttakurssien hakemista. PromiseArrayReturnType
-aputyypin purkaa sitten ratkaistun tyypin jokaisesta taulukon Promise
sta, jolloin tuloksena on tuple-tyyppi, jossa jokainen elementti on vastaavan Promisen odotettu tyyppi.
Käytännön esimerkkejä eri aloilta
1. Verkkokauppasovellus
Harkitse verkkokauppasovellusta, jossa haet tuotetietoja API:sta. Voit käyttää infer
-avainsanaa tuotetietojen tyypin purkamiseen:
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);
Tässä esimerkissä määrittelemme Product
-rajapinnan ja fetchProduct
-funktion, joka hakee tuotetiedot API:sta. Käytämme Awaited
- ja ReturnType
-funktioita Product
-tyypin purkamiseen fetchProduct
-funktion palautustyypistä, jolloin voimme tyyppitarkistaa displayProductDetails
-funktion.
2. Kansainvälistäminen (i18n)
Oletetaan, että sinulla on käännösfunktio, joka palauttaa erilaisia merkkijonoja lokaalin perusteella. Voit käyttää infer
-avainsanaa tämän funktion palautustyypin purkamiseen tyyppiturvallisuuden vuoksi:
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!
Tässä TranslationType
päätellään olevan Translations
-rajapinta, mikä varmistaa, että greetUser
-funktiolla on oikeat tyyppitiedot käännettyjen merkkijonojen käyttämiseksi.
3. API-vastauksen käsittely
API:ien kanssa työskenneltäessä vastausrakenne voi olla monimutkainen. infer
voi auttaa purkamaan tiettyjä tietotyyppejä sisäkkäisistä API-vastauksista:
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);
}
});
Tässä esimerkissä määritämme ApiResponse
-rajapinnan ja UserData
-rajapinnan. Käytämme infer
- ja tyyppi-indeksointia UserProfileType
-tyypin purkamiseen API-vastauksesta, mikä varmistaa, että displayUserProfile
-funktio saa oikean tyypin.
Parhaat käytännöt infer
-avainsanan käytölle
- Pidä se yksinkertaisena: Käytä
infer
-avainsanaa vain tarvittaessa. Sen liiallinen käyttö voi vaikeuttaa koodin lukemista ja ymmärtämistä. - Dokumentoi tyyppisi: Lisää kommentteja selittämään, mitä ehdolliset tyyppisi ja
infer
-lausekkeesi tekevät. - Testaa tyyppisi: Varmista TypeScriptin tyyppitarkistuksen avulla, että tyyppisi toimivat odotetulla tavalla.
- Harkitse suorituskykyä: Monimutkaiset ehdolliset tyypit voivat joskus vaikuttaa käännösaikaan. Ole tietoinen tyyppiesi monimutkaisuudesta.
- Käytä aputyypit: TypeScript tarjoaa useita sisäänrakennettuja aputyypit (esim.
ReturnType
,Awaited
), jotka voivat yksinkertaistaa koodiasi ja vähentää mukautettujeninfer
-lausekkeiden tarvetta.
Yleisiä sudenkuoppia
- Virheellinen päättely: Joskus TypeScript saattaa päätellä tyypin, joka ei ole odottamasi. Tarkista tyyppimäärityksesi ja -ehdot.
- Pyöreät riippuvuudet: Ole varovainen, kun määrittelet rekursiivisia tyyppejä käyttämällä
infer
-avainsanaa, koska ne voivat johtaa pyöreisiin riippuvuuksiin ja käännösvirheisiin. - Liian monimutkaiset tyypit: Vältä liian monimutkaisten ehdollisten tyyppien luomista, joita on vaikea ymmärtää ja ylläpitää. Jaa ne pienempiin, hallittavampiin tyyppeihin.
Vaihtoehtoja infer
-avainsanalle
Vaikka infer
on tehokas työkalu, on tilanteita, joissa vaihtoehtoiset lähestymistavat saattavat olla tarkoituksenmukaisempia:
- Tyyppiväittämät: Joissakin tapauksissa voit käyttää tyyppiväittämiä määrittääksesi nimenomaisesti arvon tyypin sen sijaan, että päättelisit sen. Ole kuitenkin varovainen tyyppiväittämien kanssa, koska ne voivat ohittaa tyyppitarkistuksen.
- Tyyppisuojat: Tyyppisuojia voidaan käyttää arvon tyypin rajaamiseen suoritusaikaisten tarkistusten perusteella. Tämä on hyödyllistä, kun sinun on käsiteltävä erilaisia tyyppejä suoritusaikaisten ehtojen perusteella.
- Aputyypit: TypeScript tarjoaa runsaasti aputyypejä, jotka voivat käsitellä monia yleisiä tyyppimanipulointitehtäviä ilman mukautettujen
infer
-lausekkeiden tarvetta.
Johtopäätös
TypeScriptin infer
-avainsana, kun se yhdistetään ehdollisten tyyppien kanssa, avaa edistyneitä tyyppimanipulointiominaisuuksia. Sen avulla voit purkaa tiettyjä tyyppejä monimutkaisista tyyppirakenteista, jolloin voit kirjoittaa vankempaa, ylläpidettävämpää ja tyyppiturvallisempaa koodia. Mahdollisuudet ovat laajat, funktioiden palautustyyppien päättelystä objektityyppien ominaisuuksien purkamiseen. Ymmärtämällä tässä oppaassa esitetyt periaatteet ja parhaat käytännöt voit hyödyntää infer
-avainsanaa täydellä potentiaalillaan ja nostaa TypeScript-taitojasi. Muista dokumentoida tyyppisi, testata ne perusteellisesti ja harkita vaihtoehtoisia lähestymistapoja tarvittaessa. infer
-avainsanan hallinta antaa sinulle mahdollisuuden kirjoittaa todella ilmeikästä ja tehokasta TypeScript-koodia, mikä lopulta johtaa parempaan ohjelmistoon.