Ελληνικά

Ένας περιεκτικός οδηγός για τη λέξη-κλειδί '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 είναι ένα ισχυρό εργαλείο, υπάρχουν περιπτώσεις όπου οι εναλλακτικές προσεγγίσεις μπορεί να είναι πιο κατάλληλες:

Συμπέρασμα

Η λέξη-κλειδί infer στο TypeScript, όταν συνδυάζεται με υπό όρους τύπους, ξεκλειδώνει προηγμένες δυνατότητες χειρισμού τύπων. Σας επιτρέπει να εξαγάγετε συγκεκριμένους τύπους από σύνθετες δομές τύπων, δίνοντάς σας τη δυνατότητα να γράψετε πιο ισχυρό, συντηρήσιμο και ασφαλή κώδικα τύπου. Από την εξαγωγή των τύπων επιστροφής συναρτήσεων έως την εξαγωγή ιδιοτήτων από τύπους αντικειμένων, οι δυνατότητες είναι τεράστιες. Κατανοώντας τις αρχές και τις βέλτιστες πρακτικές που περιγράφονται σε αυτόν τον οδηγό, μπορείτε να αξιοποιήσετε το infer στο μέγιστο των δυνατοτήτων του και να αναβαθμίσετε τις δεξιότητές σας στο TypeScript. Θυμηθείτε να τεκμηριώνετε τους τύπους σας, να τους δοκιμάζετε διεξοδικά και να εξετάζετε εναλλακτικές προσεγγίσεις όταν είναι σκόπιμο. Η κυριαρχία του infer σάς δίνει τη δυνατότητα να γράψετε πραγματικά εκφραστικό και ισχυρό κώδικα TypeScript, οδηγώντας τελικά σε καλύτερο λογισμικό.

Κατανόηση του TypeScript Infer: Εξαγωγή υπό όρους τύπων για προηγμένο χειρισμό τύπων | MLOG