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;
};
T
: Inndatatypen du vil mappe over.K in keyof T
: Itererer over hver nøkkel i inndatatypenT
.keyof T
lager en union av alle egenskapsnavn iT
, ogK
representerer hver enkelt nøkkel under iterasjonen.Transformation
: Transformasjonen du ønsker å anvende på hver egenskap. Dette kan være å legge til en modifikator (somreadonly
eller?
), endre typen, eller noe helt annet.
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
T
: Typen som sjekkes.U
: Typen somT
utvider (betingelsen).X
: Typen som returneres hvisT
utviderU
(betingelsen er sann).Y
: Typen som returneres hvisT
ikke utviderU
(betingelsen er usann).
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.
Partial<T>
: Gjør alle egenskaper iT
valgfrie.Required<T>
: Gjør alle egenskaper iT
påkrevde.Readonly<T>
: Gjør alle egenskaper iT
skrivebeskyttede.Pick<T, K>
: Velger et sett med egenskaperK
fraT
.Omit<T, K>
: Fjerner et sett med egenskaperK
fraT
.Record<K, T>
: Konstruerer en type med et sett med egenskaperK
av typenT
.Exclude<T, U>
: Ekskluderer fraT
alle typer som er tilordningsbare tilU
.Extract<T, U>
: Trekker ut fraT
alle typer som er tilordningsbare tilU
.NonNullable<T>
: Ekskluderernull
ogundefined
fraT
.Parameters<T>
: Henter parameterne til en funksjonstypeT
.ReturnType<T>
: Henter returtypen til en funksjonstypeT
.InstanceType<T>
: Henter instansetypen til en konstruktørfunksjonstypeT
.
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
- Hold det enkelt: Selv om Mapped Types og Conditional Types er kraftige, kan de også gjøre koden din mer kompleks. Prøv å holde typetransformasjonene dine så enkle som mulig.
- Bruk hjelpetyper: Utnytt TypeScripts innebygde hjelpetyper når det er mulig. De er godt testet og kan forenkle koden din.
- Dokumenter typene dine: Dokumenter typetransformasjonene dine tydelig, spesielt hvis de er komplekse. Dette vil hjelpe andre utviklere å forstå koden din.
- Test typene dine: Bruk TypeScripts typesjekking for å sikre at typetransformasjonene dine fungerer som forventet. Du kan skrive enhetstester for å verifisere oppførselen til typene dine.
- Vurder ytelse: Komplekse typetransformasjoner kan påvirke ytelsen til TypeScript-kompilatoren din. Vær oppmerksom på kompleksiteten til typene dine og unngå unødvendige beregninger.
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.