Svenska

Bemästra TypeScript's utility-typer: kraftfulla verktyg för typtransformationer som förbättrar kodens återanvändbarhet och typsäkerhet i dina applikationer.

TypeScript Utility-typer: Inbyggda verktyg för typmanipulering

TypeScript är ett kraftfullt språk som tillför statisk typning till JavaScript. En av dess nyckelfunktioner är förmågan att manipulera typer, vilket gör det möjligt för utvecklare att skapa mer robust och underhållbar kod. TypeScript tillhandahåller en uppsättning inbyggda utility-typer som förenklar vanliga typtransformationer. Dessa utility-typer är ovärderliga verktyg för att förbättra typsäkerheten, öka kodens återanvändbarhet och effektivisera ditt utvecklingsflöde. Denna omfattande guide utforskar de mest väsentliga TypeScript utility-typerna, med praktiska exempel och användbara insikter för att hjälpa dig bemästra dem.

Vad är TypeScript Utility-typer?

Utility-typer är fördefinierade typoperatorer som omvandlar befintliga typer till nya typer. De är inbyggda i TypeScript-språket och erbjuder ett koncist och deklarativt sätt att utföra vanliga typmanipulationer. Att använda utility-typer kan avsevärt minska mängden standardkod (boilerplate) och göra dina typdefinitioner mer uttrycksfulla och lättare att förstå.

Tänk på dem som funktioner som opererar på typer istället för värden. De tar en typ som indata och returnerar en modifierad typ som utdata. Detta gör att du kan skapa komplexa typrelationer och transformationer med minimal kod.

Varför använda Utility-typer?

Det finns flera övertygande skäl att införliva utility-typer i dina TypeScript-projekt:

Väsentliga TypeScript Utility-typer

Låt oss utforska några av de vanligaste och mest användbara utility-typerna i TypeScript. Vi kommer att gå igenom deras syfte, syntax och ge praktiska exempel för att illustrera deras användning.

1. Partial<T>

Utility-typen Partial<T> gör alla egenskaper i typen T valfria. Detta är användbart när du vill skapa en ny typ som har några eller alla egenskaper från en befintlig typ, men du inte vill kräva att alla måste finnas med.

Syntax:

type Partial<T> = { [P in keyof T]?: T[P]; };

Exempel:

interface User {
 id: number;
 name: string;
 email: string;
}

type OptionalUser = Partial<User>; // Alla egenskaper är nu valfria

const partialUser: OptionalUser = {
 name: "Alice", // Endast name-egenskapen anges
};

Användningsfall: Uppdatera ett objekt med endast vissa egenskaper. Föreställ dig till exempel ett formulär för att uppdatera en användarprofil. Du vill inte kräva att användaren uppdaterar varje fält på en gång.

2. Required<T>

Utility-typen Required<T> gör alla egenskaper i typen T obligatoriska. Det är motsatsen till Partial<T>. Detta är användbart när du har en typ med valfria egenskaper och vill säkerställa att alla egenskaper finns med.

Syntax:

type Required<T> = { [P in keyof T]-?: T[P]; };

Exempel:

interface Config {
 apiKey?: string;
 apiUrl?: string;
}

type CompleteConfig = Required<Config>; // Alla egenskaper är nu obligatoriska

const config: CompleteConfig = {
 apiKey: "your-api-key",
 apiUrl: "https://example.com/api",
};

Användningsfall: Säkerställa att alla konfigurationsinställningar är angivna innan en applikation startas. Detta kan hjälpa till att förhindra körtidsfel orsakade av saknade eller odefinierade inställningar.

3. Readonly<T>

Utility-typen Readonly<T> gör alla egenskaper i typen T skrivskyddade. Detta förhindrar att du av misstag ändrar egenskaperna hos ett objekt efter att det har skapats. Detta främjar oföränderlighet (immutability) och förbättrar förutsägbarheten i din kod.

Syntax:

type Readonly<T> = { readonly [P in keyof T]: T[P]; };

Exempel:

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

type ImmutableProduct = Readonly<Product>; // Alla egenskaper är nu skrivskyddade

const product: ImmutableProduct = {
 id: 123,
 name: "Exempelprodukt",
 price: 25.99,
};

// product.price = 29.99; // Fel: Cannot assign to 'price' because it is a read-only property.

Användningsfall: Skapa oföränderliga datastrukturer, såsom konfigurationsobjekt eller dataöverföringsobjekt (DTOs), som inte bör ändras efter att de har skapats. Detta är särskilt användbart i funktionella programmeringsparadigm.

4. Pick<T, K extends keyof T>

Utility-typen Pick<T, K extends keyof T> skapar en ny typ genom att plocka ut en uppsättning egenskaper K från typen T. Detta är användbart när du bara behöver en delmängd av egenskaperna från en befintlig typ.

Syntax:

type Pick<T, K extends keyof T> = { [P in K]: T[P]; };

Exempel:

interface Employee {
 id: number;
 name: string;
 department: string;
salary: number;
}

type EmployeeNameAndDepartment = Pick<Employee, "name" | "department">; // Plocka endast ut name och department

const employeeInfo: EmployeeNameAndDepartment = {
 name: "Bob",
 department: "Engineering",
};

Användningsfall: Skapa specialiserade dataöverföringsobjekt (DTOs) som endast innehåller nödvändig data för en specifik operation. Detta kan förbättra prestandan och minska mängden data som överförs över nätverket. Föreställ dig att du skickar användardetaljer till klienten men exkluderar känslig information som lön. Du kan använda Pick för att endast skicka id och name.

5. Omit<T, K extends keyof any>

Utility-typen Omit<T, K extends keyof any> skapar en ny typ genom att utelämna en uppsättning egenskaper K från typen T. Detta är motsatsen till Pick<T, K extends keyof T> och är användbart när du vill exkludera vissa egenskaper från en befintlig typ.

Syntax:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Exempel:

interface Event {
 id: number;
 title: string;
description: string;
 date: Date;
 location: string;
}

type EventSummary = Omit<Event, "description" | "location">; // Utelämna description och location

const eventPreview: EventSummary = {
 id: 1,
 title: "Konferens",
 date: new Date(),
};

Användningsfall: Skapa förenklade versioner av datamodeller för specifika ändamål, som att visa en sammanfattning av ett evenemang utan att inkludera den fullständiga beskrivningen och platsen. Detta kan också användas för att ta bort känsliga fält innan data skickas till en klient.

6. Exclude<T, U>

Utility-typen Exclude<T, U> skapar en ny typ genom att från T exkludera alla typer som är tilldelningsbara till U. Detta är användbart när du vill ta bort vissa typer från en union-typ.

Syntax:

type Exclude<T, U> = T extends U ? never : T;

Exempel:

type AllowedFileTypes = "image" | "video" | "audio" | "document";
type MediaFileTypes = "image" | "video" | "audio";

type DocumentFileTypes = Exclude<AllowedFileTypes, MediaFileTypes>; // "document"

const fileType: DocumentFileTypes = "document";

Användningsfall: Filtrera en union-typ för att ta bort specifika typer som inte är relevanta i ett visst sammanhang. Till exempel kan du vilja exkludera vissa filtyper från en lista över tillåtna filtyper.

7. Extract<T, U>

Utility-typen Extract<T, U> skapar en ny typ genom att från T extrahera alla typer som är tilldelningsbara till U. Detta är motsatsen till Exclude<T, U> och är användbart när du vill välja ut specifika typer från en union-typ.

Syntax:

type Extract<T, U> = T extends U ? T : never;

Exempel:

type InputTypes = string | number | boolean | null | undefined;
type PrimitiveTypes = string | number | boolean;

type NonNullablePrimitives = Extract<InputTypes, PrimitiveTypes>; // string | number | boolean

const value: NonNullablePrimitives = "hello";

Användningsfall: Välja ut specifika typer från en union-typ baserat på vissa kriterier. Till exempel kan du vilja extrahera alla primitiva typer från en union-typ som inkluderar både primitiva typer och objekttyper.

8. NonNullable<T>

Utility-typen NonNullable<T> skapar en ny typ genom att exkludera null och undefined från typen T. Detta är användbart när du vill säkerställa att en typ inte kan vara null eller undefined.

Syntax:

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

Exempel:

type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>; // string

const message: DefinitelyString = "Hello, world!";

Användningsfall: Säkerställa att ett värde inte är null eller undefined innan en operation utförs på det. Detta kan hjälpa till att förhindra körtidsfel orsakade av oväntade null- eller undefined-värden. Tänk dig ett scenario där du behöver bearbeta en användares adress, och det är avgörande att adressen inte är null innan någon operation.

9. ReturnType<T extends (...args: any) => any>

Utility-typen ReturnType<T extends (...args: any) => any> extraherar returtypen från en funktionstyp T. Detta är användbart när du vill veta typen av värdet som en funktion returnerar.

Syntax:

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

Exempel:

function fetchData(url: string): Promise<{ data: any }> {
 return fetch(url).then(response => response.json());
}

type FetchDataReturnType = ReturnType<typeof fetchData>; // Promise<{ data: any }>

async function processData(data: FetchDataReturnType) {
 // ...
}

Användningsfall: Fastställa typen av värdet som returneras av en funktion, särskilt när man hanterar asynkrona operationer eller komplexa funktionssignaturer. Detta gör att du kan säkerställa att du hanterar det returnerade värdet korrekt.

10. Parameters<T extends (...args: any) => any>

Utility-typen Parameters<T extends (...args: any) => any> extraherar parametertyperna från en funktionstyp T som en tuppel. Detta är användbart när du vill veta typerna av argumenten som en funktion accepterar.

Syntax:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

Exempel:

function createUser(name: string, age: number, email: string): void {
 // ...
}

type CreateUserParams = Parameters<typeof createUser>; // [string, number, string]

function logUser(...args: CreateUserParams) {
 console.log("Skapar användare med:", args);
}

Användningsfall: Fastställa typerna av argumenten som en funktion accepterar, vilket kan vara användbart för att skapa generiska funktioner eller dekoratörer som behöver fungera med funktioner med olika signaturer. Det hjälper till att säkerställa typsäkerhet när argument skickas dynamiskt till en funktion.

11. ConstructorParameters<T extends abstract new (...args: any) => any>

Utility-typen ConstructorParameters<T extends abstract new (...args: any) => any> extraherar parametertyperna från en konstruktortyp T som en tuppel. Detta är användbart när du vill veta typerna av argumenten som en konstruktor accepterar.

Syntax:

type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

Exempel:

class Logger {
 constructor(public prefix: string, public enabled: boolean) {}
 log(message: string) {
 if (this.enabled) {
 console.log(`${this.prefix}: ${message}`);
 }
 }
}

type LoggerConstructorParams = ConstructorParameters<typeof Logger>; // [string, boolean]

function createLogger(...args: LoggerConstructorParams) {
 return new Logger(...args);
}

Användningsfall: Liknar Parameters, men specifikt för konstruktorfunktioner. Det hjälper när man skapar fabriker (factories) eller system för beroendeinjektion (dependency injection) där du dynamiskt behöver instansiera klasser med olika konstruktorsignaturer.

12. InstanceType<T extends abstract new (...args: any) => any>

Utility-typen InstanceType<T extends abstract new (...args: any) => any> extraherar instanstypen från en konstruktortyp T. Detta är användbart när du vill veta typen av objektet som en konstruktor skapar.

Syntax:

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

Exempel:

class Greeter {
 greeting: string;
 constructor(message: string) {
 this.greeting = message;
 }
 greet() {
 return "Hello, " + this.greeting;
 }
}

type GreeterInstance = InstanceType<typeof Greeter>; // Greeter

const myGreeter: GreeterInstance = new Greeter("World");
console.log(myGreeter.greet());

Användningsfall: Fastställa typen av objektet som skapas av en konstruktor, vilket är användbart när man arbetar med arv eller polymorfism. Det ger ett typsäkert sätt att referera till instansen av en klass.

13. Record<K extends keyof any, T>

Utility-typen Record<K extends keyof any, T> konstruerar en objekttyp vars egenskapsnycklar är K och vars egenskapsvärden är T. Detta är användbart för att skapa ordlisteliknande typer där du känner till nycklarna i förväg.

Syntax:

type Record<K extends keyof any, T> = { [P in K]: T; };

Exempel:

type CountryCode = "US" | "CA" | "GB" | "DE";

type CurrencyMap = Record<CountryCode, string>; // { US: string; CA: string; GB: string; DE: string; }

const currencies: CurrencyMap = {
 US: "USD",
 CA: "CAD",
 GB: "GBP",
 DE: "EUR",
};

Användningsfall: Skapa ordlisteliknande objekt där du har en fast uppsättning nycklar och vill säkerställa att alla nycklar har värden av en specifik typ. Detta är vanligt när man arbetar med konfigurationsfiler, datamappningar eller uppslagstabeller.

Anpassade Utility-typer

Även om TypeScript's inbyggda utility-typer är kraftfulla, kan du också skapa dina egna anpassade utility-typer för att möta specifika behov i dina projekt. Detta gör att du kan kapsla in komplexa typtransformationer och återanvända dem i hela din kodbas.

Exempel:

// En utility-typ för att få nycklarna till ett objekt som har en specifik typ
type KeysOfType<T, U> = { [K in keyof T]: T[K] extends U ? K : never }[keyof T];

interface Person {
 name: string;
 age: number;
 address: string;
 phoneNumber: number;
}

type StringKeys = KeysOfType<Person, string>; // "name" | "address"

Bästa praxis för att använda Utility-typer

Slutsats

TypeScript utility-typer är kraftfulla verktyg som avsevärt kan förbättra typsäkerheten, återanvändbarheten och underhållbarheten i din kod. Genom att bemästra dessa utility-typer kan du skriva mer robusta och uttrycksfulla TypeScript-applikationer. Denna guide har täckt de mest väsentliga TypeScript utility-typerna, med praktiska exempel och användbara insikter för att hjälpa dig att införliva dem i dina projekt.

Kom ihåg att experimentera med dessa utility-typer och utforska hur de kan användas för att lösa specifika problem i din egen kod. När du blir mer bekant med dem kommer du att finna dig själv använda dem mer och mer för att skapa renare, mer underhållbara och mer typsäkra TypeScript-applikationer. Oavsett om du bygger webbapplikationer, server-side-applikationer eller något däremellan, erbjuder utility-typer en värdefull uppsättning verktyg för att förbättra ditt utvecklingsflöde och kvaliteten på din kod. Genom att utnyttja dessa inbyggda verktyg för typmanipulering kan du låsa upp den fulla potentialen hos TypeScript och skriva kod som är både uttrycksfull och robust.