Ένας περιεκτικός οδηγός για τη λέξη-κλειδί 'infer' του TypeScript, εξηγώντας πώς να τη χρησιμοποιήσετε με υπό όρους τύπους για ισχυρή εξαγωγή και χειρισμό τύπων.
Κατανόηση του TypeScript Infer: Εξαγωγή υπό όρους τύπων για προηγμένο χειρισμό τύπων
Το σύστημα τύπων του TypeScript είναι απίστευτα ισχυρό, επιτρέποντας στους προγραμματιστές να δημιουργούν ισχυρές και συντηρήσιμες εφαρμογές. Ένα από τα βασικά χαρακτηριστικά που επιτρέπουν αυτήν την ισχύ είναι η λέξη-κλειδί infer
που χρησιμοποιείται σε συνδυασμό με υπό όρους τύπους. Αυτός ο συνδυασμός παρέχει έναν μηχανισμό για την εξαγωγή συγκεκριμένων τύπων από σύνθετες δομές τύπων. Αυτή η ανάρτηση ιστολογίου εμβαθύνει στη λέξη-κλειδί infer
, εξηγώντας τη λειτουργικότητά της και παρουσιάζοντας προηγμένες περιπτώσεις χρήσης. Θα εξερευνήσουμε πρακτικά παραδείγματα που εφαρμόζονται σε διάφορα σενάρια ανάπτυξης λογισμικού, από την αλληλεπίδραση API έως τον χειρισμό σύνθετων δομών δεδομένων.
Τι είναι οι υπό όρους τύποι;
Πριν εμβαθύνουμε στο infer
, ας εξετάσουμε γρήγορα τους υπό όρους τύπους. Οι υπό όρους τύποι στο TypeScript σάς επιτρέπουν να ορίσετε έναν τύπο με βάση μια συνθήκη, παρόμοια με έναν τριαδικό τελεστή στην JavaScript. Η βασική σύνταξη είναι:
T extends U ? X : Y
Αυτό διαβάζεται ως: "Εάν ο τύπος T
μπορεί να αντιστοιχιστεί στον τύπο U
, τότε ο τύπος είναι X
, διαφορετικά ο τύπος είναι Y
."
Παράδειγμα:
type IsString<T> = T extends string ? true : false;
type StringResult = IsString<string>; // type StringResult = true
type NumberResult = IsString<number>; // type NumberResult = false
Εισαγωγή της λέξης-κλειδί infer
Η λέξη-κλειδί infer
χρησιμοποιείται μέσα στη ρήτρα extends
ενός υπό όρους τύπου για να δηλώσει μια μεταβλητή τύπου που μπορεί να συναχθεί από τον τύπο που ελέγχεται. Στην ουσία, σας επιτρέπει να "καταγράψετε" ένα μέρος ενός τύπου για μεταγενέστερη χρήση.
Βασική Σύνταξη:
type MyType<T> = T extends (infer U) ? U : never;
Σε αυτό το παράδειγμα, εάν το T
μπορεί να αντιστοιχιστεί σε κάποιον τύπο, το TypeScript θα προσπαθήσει να συναγάγει τον τύπο του U
. Εάν η εξαγωγή είναι επιτυχής, ο τύπος θα είναι U
, διαφορετικά θα είναι never
.
Απλά Παραδείγματα του infer
1. Εξαγωγή του τύπου επιστροφής μιας συνάρτησης
Μια κοινή περίπτωση χρήσης είναι η εξαγωγή του τύπου επιστροφής μιας συνάρτησης:
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
Σε αυτό το παράδειγμα, το ReturnType<T>
λαμβάνει έναν τύπο συνάρτησης T
ως είσοδο. Ελέγχει εάν το T
μπορεί να αντιστοιχιστεί σε μια συνάρτηση που δέχεται οποιαδήποτε ορίσματα και επιστρέφει μια τιμή. Εάν είναι, συμπεραίνει τον τύπο επιστροφής ως R
και τον επιστρέφει. Διαφορετικά, επιστρέφει any
.
2. Εξαγωγή Τύπου Στοιχείου Πίνακα
Ένα άλλο χρήσιμο σενάριο είναι η εξαγωγή του τύπου στοιχείου από έναν πίνακα:
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
Εδώ, το ArrayElementType<T>
ελέγχει εάν το T
είναι ένας τύπος πίνακα. Εάν είναι, συμπεραίνει τον τύπο στοιχείου ως U
και τον επιστρέφει. Εάν όχι, επιστρέφει never
.
Προηγμένες Περιπτώσεις Χρήσης του infer
1. Εξαγωγή Παραμέτρων ενός Constructor
Μπορείτε να χρησιμοποιήσετε το infer
για να εξαγάγετε τους τύπους παραμέτρων μιας συνάρτησης constructor:
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]
Σε αυτήν την περίπτωση, το ConstructorParameters<T>
λαμβάνει έναν τύπο συνάρτησης constructor T
. Συμπεραίνει τους τύπους των παραμέτρων του constructor ως P
και τους επιστρέφει ως πλειάδα.
2. Εξαγωγή Ιδιοτήτων από Τύπους Αντικειμένων
Το infer
μπορεί επίσης να χρησιμοποιηθεί για την εξαγωγή συγκεκριμένων ιδιοτήτων από τύπους αντικειμένων χρησιμοποιώντας αντιστοιχισμένους τύπους και υπό όρους τύπους:
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; }
Εδώ, το PickByType<T, K, U>
δημιουργεί έναν νέο τύπο που περιλαμβάνει μόνο τις ιδιότητες του T
(με κλειδιά στο K
) των οποίων οι τιμές μπορούν να αντιστοιχιστούν στον τύπο U
. Ο αντιστοιχισμένος τύπος επαναλαμβάνει τα κλειδιά του T
και ο υπό όρους τύπος φιλτράρει τα κλειδιά που δεν ταιριάζουν με τον καθορισμένο τύπο.
3. Εργασία με Promises
Μπορείτε να εξαγάγετε τον επιλυμένο τύπο ενός 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[]
Ο τύπος Awaited<T>
λαμβάνει έναν τύπο T
, ο οποίος αναμένεται να είναι μια Promise. Στη συνέχεια, ο τύπος συμπεραίνει τον επιλυμένο τύπο U
της Promise και τον επιστρέφει. Εάν το T
δεν είναι μια υπόσχεση, επιστρέφει το T. Αυτός είναι ένας ενσωματωμένος βοηθητικός τύπος στις νεότερες εκδόσεις του TypeScript.
4. Εξαγωγή του Τύπου ενός Πίνακα Promises
Ο συνδυασμός της εξαγωγής τύπου Awaited
και πίνακα σάς επιτρέπει να συμπεράνετε τον τύπο που επιλύεται από έναν πίνακα Promises. Αυτό είναι ιδιαίτερα χρήσιμο όταν ασχολείστε με το 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]
Αυτό το παράδειγμα ορίζει πρώτα δύο ασύγχρονες συναρτήσεις, getUSDRate
και getEURRate
, οι οποίες προσομοιώνουν την ανάκτηση συναλλαγματικών ισοτιμιών. Ο βοηθητικός τύπος PromiseArrayReturnType
στη συνέχεια εξάγει τον επιλυμένο τύπο από κάθε Promise
στον πίνακα, με αποτέλεσμα έναν τύπο πλειάδας όπου κάθε στοιχείο είναι ο αναμενόμενος τύπος της αντίστοιχης Promise.
Πρακτικά Παραδείγματα σε Διαφορετικούς Τομείς
1. Εφαρμογή Ηλεκτρονικού Εμπορίου
Εξετάστε μια εφαρμογή ηλεκτρονικού εμπορίου όπου ανακτάτε λεπτομέρειες προϊόντων από ένα API. Μπορείτε να χρησιμοποιήσετε το infer
για να εξαγάγετε τον τύπο των δεδομένων του προϊόντος:
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);
Σε αυτό το παράδειγμα, ορίζουμε μια διεπαφή Product
και μια συνάρτηση fetchProduct
που ανακτά λεπτομέρειες προϊόντων από ένα API. Χρησιμοποιούμε Awaited
και ReturnType
για να εξαγάγουμε τον τύπο Product
από τον τύπο επιστροφής της συνάρτησης fetchProduct
, επιτρέποντάς μας να ελέγξουμε τον τύπο της συνάρτησης displayProductDetails
.
2. Διεθνοποίηση (i18n)
Ας υποθέσουμε ότι έχετε μια συνάρτηση μετάφρασης που επιστρέφει διαφορετικές συμβολοσειρές με βάση την τοπική ρύθμιση. Μπορείτε να χρησιμοποιήσετε το infer
για να εξαγάγετε τον τύπο επιστροφής αυτής της συνάρτησης για ασφάλεια τύπου:
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!
Εδώ, ο TranslationType
συμπεραίνεται ότι είναι η διεπαφή Translations
, διασφαλίζοντας ότι η συνάρτηση greetUser
έχει τις σωστές πληροφορίες τύπου για την πρόσβαση σε μεταφρασμένες συμβολοσειρές.
3. Χειρισμός Απόκρισης API
Όταν εργάζεστε με API, η δομή απόκρισης μπορεί να είναι σύνθετη. Το infer
μπορεί να βοηθήσει στην εξαγωγή συγκεκριμένων τύπων δεδομένων από ένθετες αποκρίσεις 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);
}
});
Σε αυτό το παράδειγμα, ορίζουμε μια διεπαφή ApiResponse
και μια διεπαφή UserData
. Χρησιμοποιούμε infer
και ευρετηρίαση τύπων για να εξαγάγουμε τον UserProfileType
από την απόκριση API, διασφαλίζοντας ότι η συνάρτηση displayUserProfile
λαμβάνει τον σωστό τύπο.
Βέλτιστες Πρακτικές για τη Χρήση του infer
- Διατηρήστε το Απλό: Χρησιμοποιήστε το
infer
μόνο όταν είναι απαραίτητο. Η υπερβολική χρήση του μπορεί να κάνει τον κώδικά σας πιο δύσκολο στην ανάγνωση και την κατανόηση. - Τεκμηριώστε τους Τύπους σας: Προσθέστε σχόλια για να εξηγήσετε τι κάνουν οι υπό όρους τύποι και οι δηλώσεις
infer
. - Δοκιμάστε τους Τύπους σας: Χρησιμοποιήστε τον έλεγχο τύπων του TypeScript για να διασφαλίσετε ότι οι τύποι σας συμπεριφέρονται όπως αναμένεται.
- Λάβετε υπόψη την Απόδοση: Οι σύνθετοι υπό όρους τύποι μπορεί μερικές φορές να επηρεάσουν τον χρόνο μεταγλώττισης. Να είστε προσεκτικοί με την πολυπλοκότητα των τύπων σας.
- Χρησιμοποιήστε Βοηθητικούς Τύπους: Το TypeScript παρέχει πολλούς ενσωματωμένους βοηθητικούς τύπους (π.χ.
ReturnType
,Awaited
) που μπορούν να απλοποιήσουν τον κώδικά σας και να μειώσουν την ανάγκη για προσαρμοσμένες δηλώσειςinfer
.
Συνήθη Λάθη
- Εσφαλμένη Εξαγωγή: Μερικές φορές, το TypeScript μπορεί να συμπεράνει έναν τύπο που δεν είναι αυτό που περιμένετε. Ελέγξτε ξανά τους ορισμούς και τις συνθήκες των τύπων σας.
- Κυκλικές Εξαρτήσεις: Να είστε προσεκτικοί όταν ορίζετε αναδρομικούς τύπους χρησιμοποιώντας το
infer
, καθώς μπορεί να οδηγήσουν σε κυκλικές εξαρτήσεις και σφάλματα μεταγλώττισης. - Υπερβολικά Σύνθετοι Τύποι: Αποφύγετε τη δημιουργία υπερβολικά σύνθετων υπό όρους τύπων που είναι δύσκολο να κατανοηθούν και να συντηρηθούν. Αναλύστε τους σε μικρότερους, πιο διαχειρίσιμους τύπους.
Εναλλακτικές λύσεις στο infer
Ενώ το infer
είναι ένα ισχυρό εργαλείο, υπάρχουν περιπτώσεις όπου οι εναλλακτικές προσεγγίσεις μπορεί να είναι πιο κατάλληλες:
- Δηλώσεις Τύπου: Σε ορισμένες περιπτώσεις, μπορείτε να χρησιμοποιήσετε δηλώσεις τύπου για να καθορίσετε ρητά τον τύπο μιας τιμής αντί να τον εξαγάγετε. Ωστόσο, να είστε προσεκτικοί με τις δηλώσεις τύπου, καθώς μπορούν να παρακάμψουν τον έλεγχο τύπου.
- Φύλακες Τύπου: Οι φύλακες τύπου μπορούν να χρησιμοποιηθούν για να περιορίσουν τον τύπο μιας τιμής με βάση ελέγχους χρόνου εκτέλεσης. Αυτό είναι χρήσιμο όταν πρέπει να χειριστείτε διαφορετικούς τύπους με βάση συνθήκες χρόνου εκτέλεσης.
- Βοηθητικοί Τύποι: Το TypeScript παρέχει ένα πλούσιο σύνολο βοηθητικών τύπων που μπορούν να χειριστούν πολλές κοινές εργασίες χειρισμού τύπων χωρίς την ανάγκη για προσαρμοσμένες δηλώσεις
infer
.
Συμπέρασμα
Η λέξη-κλειδί infer
στο TypeScript, όταν συνδυάζεται με υπό όρους τύπους, ξεκλειδώνει προηγμένες δυνατότητες χειρισμού τύπων. Σας επιτρέπει να εξαγάγετε συγκεκριμένους τύπους από σύνθετες δομές τύπων, δίνοντάς σας τη δυνατότητα να γράψετε πιο ισχυρό, συντηρήσιμο και ασφαλή κώδικα τύπου. Από την εξαγωγή των τύπων επιστροφής συναρτήσεων έως την εξαγωγή ιδιοτήτων από τύπους αντικειμένων, οι δυνατότητες είναι τεράστιες. Κατανοώντας τις αρχές και τις βέλτιστες πρακτικές που περιγράφονται σε αυτόν τον οδηγό, μπορείτε να αξιοποιήσετε το infer
στο μέγιστο των δυνατοτήτων του και να αναβαθμίσετε τις δεξιότητές σας στο TypeScript. Θυμηθείτε να τεκμηριώνετε τους τύπους σας, να τους δοκιμάζετε διεξοδικά και να εξετάζετε εναλλακτικές προσεγγίσεις όταν είναι σκόπιμο. Η κυριαρχία του infer
σάς δίνει τη δυνατότητα να γράψετε πραγματικά εκφραστικό και ισχυρό κώδικα TypeScript, οδηγώντας τελικά σε καλύτερο λογισμικό.