Svenska

En omfattande guide till TypeScript's kraftfulla Mapped Types och Conditional Types, med praktiska exempel och avancerade användningsfall för att skapa robusta och typsäkra applikationer.

Bemästra TypeScript's Mapped Types och Conditional Types

TypeScript, ett superset av JavaScript, erbjuder kraftfulla funktioner för att skapa robusta och underhållbara applikationer. Bland dessa funktioner utmärker sig Mapped Types och Conditional Types som väsentliga verktyg för avancerad typhandläggning. Denna guide ger en omfattande översikt av dessa koncept, utforskar deras syntax, praktiska tillämpningar och avancerade användningsfall. Oavsett om du är en erfaren TypeScript-utvecklare eller precis har börjat din resa, kommer den här artikeln att utrusta dig med kunskapen för att utnyttja dessa funktioner effektivt.

Vad är Mapped Types?

Mapped Types låter dig skapa nya typer genom att omvandla befintliga. De itererar över egenskaperna i en befintlig typ och applicerar en omvandling på varje egenskap. Detta är särskilt användbart för att skapa variationer av befintliga typer, som att göra alla egenskaper valfria eller skrivskyddade.

Grundläggande syntax

Syntaxen för en Mapped Type är som följer:

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

Praktiska exempel

Göra egenskaper skrivskyddade

Låt oss säga att du har ett interface som representerar en användarprofil:

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

Du kan skapa en ny typ där alla egenskaper är skrivskyddade:

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

Nu kommer ReadOnlyUserProfile att ha samma egenskaper som UserProfile, men de kommer alla att vara skrivskyddade.

Göra egenskaper valfria

På samma sätt kan du göra alla egenskaper valfria:

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

OptionalUserProfile kommer att ha alla egenskaper från UserProfile, men varje egenskap kommer att vara valfri.

Modifiera egenskapstyper

Du kan också modifiera typen för varje egenskap. Till exempel kan du omvandla alla egenskaper till att vara strängar:

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

I det här fallet kommer alla egenskaper i StringifiedUserProfile att vara av typen string.

Vad är Conditional Types?

Conditional Types låter dig definiera typer som beror på ett villkor. De ger ett sätt att uttrycka typrelationer baserat på om en typ uppfyller ett visst krav. Detta liknar en ternär operator i JavaScript, men för typer.

Grundläggande syntax

Syntaxen för en Conditional Type är som följer:

T extends U ? X : Y

Praktiska exempel

Avgöra om en typ är en sträng

Låt oss skapa en typ som returnerar string om indatatypen är en sträng, och number i annat fall:

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

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

Extrahera typ från en union

Du kan använda villkorliga typer för att extrahera en specifik typ från en union-typ. Till exempel för att extrahera icke-nullbara typer:

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

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

Här, om T är null eller undefined, blir typen never, vilket sedan filtreras bort av TypeScript's förenkling av union-typer.

Härleda typer (Inferring Types)

Villkorliga typer kan också användas för att härleda typer med nyckelordet infer. Detta gör att du kan extrahera en typ från en mer komplex typstruktur.

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 det här exemplet extraherar ReturnType returtypen från en funktion. Den kontrollerar om T är en funktion som tar vilka argument som helst och returnerar en typ R. Om den är det, returnerar den R; annars returnerar den any.

Kombinera Mapped Types och Conditional Types

Den verkliga kraften med Mapped Types och Conditional Types kommer från att kombinera dem. Detta gör att du kan skapa mycket flexibla och uttrycksfulla typomvandlingar.

Exempel: Deep Readonly

Ett vanligt användningsfall är att skapa en typ som gör alla egenskaper i ett objekt, inklusive nästlade egenskaper, skrivskyddade. Detta kan uppnås med en rekursiv villkorlig typ.

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>;

Här applicerar DeepReadonly rekursivt readonly-modifieraren på alla egenskaper och deras nästlade egenskaper. Om en egenskap är ett objekt anropar den rekursivt DeepReadonly på det objektet. Annars applicerar den helt enkelt readonly-modifieraren på egenskapen.

Exempel: Filtrera egenskaper efter typ

Låt oss säga att du vill skapa en typ som bara inkluderar egenskaper av en specifik typ. Du kan kombinera Mapped Types och Conditional Types för att uppnå detta.

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 detta exempel itererar FilterByType över egenskaperna i T och kontrollerar om typen för varje egenskap utökar U. Om den gör det, inkluderas egenskapen i den resulterande typen; annars exkluderas den genom att mappa nyckeln till never. Notera användningen av "as" för att mappa om nycklar. Vi använder sedan `Omit` och `keyof StringProperties` för att ta bort strängegenskaperna från det ursprungliga interfacet.

Avancerade användningsfall och mönster

Utöver de grundläggande exemplen kan Mapped Types och Conditional Types användas i mer avancerade scenarier för att skapa mycket anpassningsbara och typsäkra applikationer.

Distributiva villkorliga typer

Villkorliga typer är distributiva när typen som kontrolleras är en union-typ. Detta innebär att villkoret tillämpas på varje medlem i unionen individuellt, och resultaten kombineras sedan till en ny union-typ.

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

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

I detta exempel tillämpas ToArray på varje medlem i unionen string | number individuellt, vilket resulterar i string[] | number[]. Om villkoret inte hade varit distributivt skulle resultatet ha varit (string | number)[].

Använda Utility Types

TypeScript tillhandahåller flera inbyggda "utility types" som utnyttjar Mapped Types och Conditional Types. Dessa "utility types" kan användas som byggstenar för mer komplexa typomvandlingar.

Dessa "utility types" är kraftfulla verktyg som kan förenkla komplexa typhanteringar. Du kan till exempel kombinera Pick och Partial för att skapa en typ som gör endast vissa egenskaper valfria:

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 detta exempel har OptionalDescriptionProduct alla egenskaper från Product, men egenskapen description är valfri.

Använda Template Literal Types

Template Literal Types låter dig skapa typer baserade på strängliteraler. De kan användas i kombination med Mapped Types och Conditional Types för att skapa dynamiska och uttrycksfulla typomvandlingar. Till exempel kan du skapa en typ som lägger till ett prefix till alla egenskapsnamn med en specifik sträng:

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 detta exempel kommer PrefixedSettings att ha egenskaperna data_apiUrl och data_timeout.

Bästa praxis och överväganden

Slutsats

Mapped Types och Conditional Types är kraftfulla funktioner i TypeScript som gör det möjligt för dig att skapa mycket flexibla och uttrycksfulla typomvandlingar. Genom att bemästra dessa koncept kan du förbättra typsäkerheten, underhållbarheten och den övergripande kvaliteten på dina TypeScript-applikationer. Från enkla omvandlingar som att göra egenskaper valfria eller skrivskyddade till komplexa rekursiva omvandlingar och villkorlig logik, ger dessa funktioner de verktyg du behöver för att bygga robusta och skalbara applikationer. Fortsätt att utforska och experimentera med dessa funktioner för att låsa upp deras fulla potential och bli en mer skicklig TypeScript-utvecklare.

När du fortsätter din TypeScript-resa, kom ihåg att utnyttja den mängd resurser som finns tillgängliga, inklusive den officiella TypeScript-dokumentationen, online-communities och open source-projekt. Omfamna kraften i Mapped Types och Conditional Types, och du kommer att vara väl rustad för att ta itu med även de mest utmanande typrelaterade problemen.