Suomi

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ä Promisen 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 Promisesta, 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

Yleisiä sudenkuoppia

Vaihtoehtoja infer-avainsanalle

Vaikka infer on tehokas työkalu, on tilanteita, joissa vaihtoehtoiset lähestymistavat saattavat olla tarkoituksenmukaisempia:

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.

TypeScript Inferin hallinta: Ehdollinen tyypin purku edistyneeseen tyyppimanipulaatioon | MLOG