Norsk

En omfattende guide til TypeScript sine kraftige Mapped Types og Conditional Types, med praktiske eksempler og avanserte bruksområder for robuste, typesikre applikasjoner.

Mestring av TypeScript sine Mapped Types og Conditional Types

TypeScript, et supersett av JavaScript, tilbyr kraftige funksjoner for å skape robuste og vedlikeholdbare applikasjoner. Blant disse funksjonene skiller Mapped Types og Conditional Types seg ut som essensielle verktøy for avansert typemanipulering. Denne guiden gir en omfattende oversikt over disse konseptene, utforsker deres syntaks, praktiske anvendelser og avanserte bruksområder. Enten du er en erfaren TypeScript-utvikler eller nettopp har startet din reise, vil denne artikkelen utstyre deg med kunnskapen til å utnytte disse funksjonene effektivt.

Hva er Mapped Types?

Mapped Types lar deg lage nye typer ved å transformere eksisterende. De itererer over egenskapene til en eksisterende type og anvender en transformasjon på hver egenskap. Dette er spesielt nyttig for å lage variasjoner av eksisterende typer, som for eksempel å gjøre alle egenskaper valgfrie eller skrivebeskyttede.

Grunnleggende syntaks

Syntaksen for en Mapped Type er som følger:

type NewType<T> = {
  [K in keyof T]: Transformation;
};

Praktiske eksempler

Gjøre egenskaper skrivebeskyttede

La oss si at du har et grensesnitt som representerer en brukerprofil:

interface UserProfile {
  name: string;
  age: number;
  email: string;
}

Du kan lage en ny type der alle egenskaper er skrivebeskyttede:

type ReadOnlyUserProfile = {
  readonly [K in keyof UserProfile]: UserProfile[K];
};

Nå vil ReadOnlyUserProfile ha de samme egenskapene som UserProfile, men de vil alle være skrivebeskyttede.

Gjøre egenskaper valgfrie

På samme måte kan du gjøre alle egenskaper valgfrie:

type OptionalUserProfile = {
  [K in keyof UserProfile]?: UserProfile[K];
};

OptionalUserProfile vil ha alle egenskapene til UserProfile, men hver egenskap vil være valgfri.

Endre egenskapstyper

Du kan også endre typen til hver egenskap. For eksempel kan du transformere alle egenskaper til å være strenger:

type StringifiedUserProfile = {
  [K in keyof UserProfile]: string;
};

I dette tilfellet vil alle egenskaper i StringifiedUserProfile være av typen string.

Hva er Conditional Types?

Conditional Types lar deg definere typer som avhenger av en betingelse. De gir en måte å uttrykke typeforhold basert på om en type tilfredsstiller en bestemt begrensning. Dette ligner på en ternær operator i JavaScript, men for typer.

Grunnleggende syntaks

Syntaksen for en Conditional Type er som følger:

T extends U ? X : Y

Praktiske eksempler

Avgjøre om en type er en streng

La oss lage en type som returnerer string hvis inndatatypen er en streng, og number ellers:

type StringOrNumber<T> = T extends string ? string : number;

type Result1 = StringOrNumber<string>;  // string
type Result2 = StringOrNumber<number>;  // number
type Result3 = StringOrNumber<boolean>; // number

Trekke ut type fra en Union

Du kan bruke betingede typer for å trekke ut en spesifikk type fra en union-type. For eksempel, for å trekke ut ikke-nullbare typer:

type NonNullable<T> = T extends null | undefined ? never : T;

type Result4 = NonNullable<string | null | undefined>; // string

Her, hvis T er null eller undefined, blir typen never, som deretter filtreres ut av TypeScripts forenkling av union-typer.

Inferere typer

Betingede typer kan også brukes til å inferere typer ved hjelp av nøkkelordet infer. Dette lar deg trekke ut en type fra en mer kompleks typestruktur.

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

function myFunction(x: number): string {
  return x.toString();
}

type Result5 = ReturnType<typeof myFunction>; // string

I dette eksemplet trekker ReturnType ut returtypen til en funksjon. Den sjekker om T er en funksjon som tar hvilke som helst argumenter og returnerer en type R. Hvis den er det, returnerer den R; ellers returnerer den any.

Kombinere Mapped Types og Conditional Types

Den virkelige kraften til Mapped Types og Conditional Types kommer fra å kombinere dem. Dette lar deg lage svært fleksible og uttrykksfulle typetransformasjoner.

Eksempel: Deep Readonly

Et vanlig bruksområde er å lage en type som gjør alle egenskapene til et objekt, inkludert nestede egenskaper, skrivebeskyttede. Dette kan oppnås ved hjelp av en rekursiv betinget type.

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

interface Company {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

type ReadonlyCompany = DeepReadonly<Company>;

Her anvender DeepReadonly rekursivt readonly-modifikatoren på alle egenskaper og deres nestede egenskaper. Hvis en egenskap er et objekt, kaller den rekursivt DeepReadonly på det objektet. Ellers anvender den bare readonly-modifikatoren på egenskapen.

Eksempel: Filtrere egenskaper etter type

La oss si at du vil lage en type som bare inkluderer egenskaper av en bestemt type. Du kan kombinere Mapped Types og Conditional Types for å oppnå dette.

type FilterByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface Person {
  name: string;
  age: number;
  isEmployed: boolean;
}

type StringProperties = FilterByType<Person, string>; // { name: string; }

type NonStringProperties = Omit<Person, keyof StringProperties>;

I dette eksemplet itererer FilterByType over egenskapene til T og sjekker om typen til hver egenskap utvider U. Hvis den gjør det, inkluderer den egenskapen i den resulterende typen; ellers ekskluderer den den ved å mappe nøkkelen til never. Legg merke til bruken av "as" for å re-mappe nøkler. Vi bruker deretter `Omit` og `keyof StringProperties` for å fjerne strengegenskapene fra det opprinnelige grensesnittet.

Avanserte bruksområder og mønstre

Utover de grunnleggende eksemplene kan Mapped Types og Conditional Types brukes i mer avanserte scenarier for å skape svært tilpassbare og typesikre applikasjoner.

Distributive Conditional Types

Betingede typer er distributive når typen som sjekkes er en union-type. Dette betyr at betingelsen anvendes på hvert medlem av unionen individuelt, og resultatene kombineres deretter til en ny union-type.

type ToArray<T> = T extends any ? T[] : never;

type Result6 = ToArray<string | number>; // string[] | number[]

I dette eksemplet blir ToArray anvendt på hvert medlem av unionen string | number individuelt, noe som resulterer i string[] | number[]. Hvis betingelsen ikke var distributiv, ville resultatet vært (string | number)[].

Bruke Hjelpetyper

TypeScript tilbyr flere innebygde hjelpetyper som utnytter Mapped Types og Conditional Types. Disse hjelpetypene kan brukes som byggeklosser for mer komplekse typetransformasjoner.

Disse hjelpetypene er kraftige verktøy som kan forenkle komplekse typemanipuleringer. For eksempel kan du kombinere Pick og Partial for å lage en type som bare gjør visse egenskaper valgfrie:

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

type OptionalDescriptionProduct = Optional<Product, "description">;

I dette eksemplet har OptionalDescriptionProduct alle egenskapene til Product, men description-egenskapen er valgfri.

Bruke Template Literal Types

Template Literal Types lar deg lage typer basert på streng-literaler. De kan brukes i kombinasjon med Mapped Types og Conditional Types for å lage dynamiske og uttrykksfulle typetransformasjoner. For eksempel kan du lage en type som prefikser alle egenskapsnavn med en bestemt streng:

type Prefix<T, P extends string> = {
  [K in keyof T as `${P}${string & K}`]: T[K];
};

interface Settings {
  apiUrl: string;
  timeout: number;
}

type PrefixedSettings = Prefix<Settings, "data_">;

I dette eksemplet vil PrefixedSettings ha egenskapene data_apiUrl og data_timeout.

Beste praksis og hensyn

Konklusjon

Mapped Types og Conditional Types er kraftige funksjoner i TypeScript som lar deg lage svært fleksible og uttrykksfulle typetransformasjoner. Ved å mestre disse konseptene kan du forbedre typesikkerheten, vedlikeholdbarheten og den generelle kvaliteten på TypeScript-applikasjonene dine. Fra enkle transformasjoner som å gjøre egenskaper valgfrie eller skrivebeskyttede til komplekse rekursive transformasjoner og betinget logikk, gir disse funksjonene verktøyene du trenger for å bygge robuste og skalerbare applikasjoner. Fortsett å utforske og eksperimentere med disse funksjonene for å frigjøre deres fulle potensial og bli en dyktigere TypeScript-utvikler.

Når du fortsetter din TypeScript-reise, husk å utnytte den store mengden tilgjengelige ressurser, inkludert den offisielle TypeScript-dokumentasjonen, nettsamfunn og åpen kildekode-prosjekter. Omfavn kraften til Mapped Types og Conditional Types, og du vil være godt rustet til å takle selv de mest utfordrende typerelaterte problemene.