Nederlands

Beheers de utility types van TypeScript: krachtige tools voor typetransformaties die de herbruikbaarheid van code en de typeveiligheid in uw applicaties verbeteren.

TypeScript Utility Types: Ingebouwde Tools voor Typemanipulatie

TypeScript is een krachtige taal die statische typering naar JavaScript brengt. Een van de belangrijkste kenmerken is de mogelijkheid om types te manipuleren, waardoor ontwikkelaars robuustere en beter onderhoudbare code kunnen schrijven. TypeScript biedt een set ingebouwde utility types die veelvoorkomende typetransformaties vereenvoudigen. Deze utility types zijn van onschatbare waarde voor het verbeteren van de typeveiligheid, het verhogen van de herbruikbaarheid van code en het stroomlijnen van uw ontwikkelingsworkflow. Deze uitgebreide gids verkent de meest essentiële TypeScript utility types, met praktische voorbeelden en bruikbare inzichten om u te helpen ze onder de knie te krijgen.

Wat zijn TypeScript Utility Types?

Utility types zijn vooraf gedefinieerde type-operatoren die bestaande types omzetten in nieuwe types. Ze zijn ingebouwd in de TypeScript-taal en bieden een beknopte en declaratieve manier om veelvoorkomende typemanipulaties uit te voeren. Het gebruik van utility types kan de hoeveelheid boilerplate-code aanzienlijk verminderen en uw typedefinities expressiever en gemakkelijker te begrijpen maken.

Zie ze als functies die op types werken in plaats van op waarden. Ze nemen een type als input en retourneren een gewijzigd type als output. Dit stelt u in staat om complexe typerelaties en -transformaties met minimale code te creëren.

Waarom Utility Types gebruiken?

Er zijn verschillende overtuigende redenen om utility types in uw TypeScript-projecten op te nemen:

Essentiële TypeScript Utility Types

Laten we enkele van de meest gebruikte en nuttige utility types in TypeScript verkennen. We behandelen hun doel, syntaxis en geven praktische voorbeelden om hun gebruik te illustreren.

1. Partial<T>

Het Partial<T> utility type maakt alle eigenschappen van type T optioneel. Dit is handig wanneer u een nieuw type wilt maken dat enkele of alle eigenschappen van een bestaand type heeft, maar u niet wilt dat alle eigenschappen verplicht aanwezig zijn.

Syntaxis:

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

Voorbeeld:

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

type OptionalUser = Partial<User>; // Alle eigenschappen zijn nu optioneel

const partialUser: OptionalUser = {
 name: "Alice", // Alleen de 'name'-eigenschap wordt opgegeven
};

Gebruiksscenario: Het bijwerken van een object met slechts bepaalde eigenschappen. Stel u bijvoorbeeld een formulier voor om een gebruikersprofiel bij te werken. U wilt niet van gebruikers eisen dat ze elk veld tegelijk bijwerken.

2. Required<T>

Het Required<T> utility type maakt alle eigenschappen van type T verplicht. Het is het tegenovergestelde van Partial<T>. Dit is handig wanneer u een type heeft met optionele eigenschappen en u wilt verzekeren dat alle eigenschappen aanwezig zijn.

Syntaxis:

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

Voorbeeld:

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

type CompleteConfig = Required<Config>; // Alle eigenschappen zijn nu verplicht

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

Gebruiksscenario: Afdwingen dat alle configuratie-instellingen zijn opgegeven voordat een applicatie wordt gestart. Dit kan helpen om runtime-fouten te voorkomen die worden veroorzaakt door ontbrekende of ongedefinieerde instellingen.

3. Readonly<T>

Het Readonly<T> utility type maakt alle eigenschappen van type T alleen-lezen. Dit voorkomt dat u per ongeluk de eigenschappen van een object wijzigt nadat het is gemaakt. Dit bevordert onveranderlijkheid en verbetert de voorspelbaarheid van uw code.

Syntaxis:

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

Voorbeeld:

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

type ImmutableProduct = Readonly<Product>; // Alle eigenschappen zijn nu alleen-lezen

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

// product.price = 29.99; // Fout: Kan niet toewijzen aan 'price' omdat het een alleen-lezen eigenschap is.

Gebruiksscenario: Het creëren van onveranderlijke datastructuren, zoals configuratieobjecten of data transfer objects (DTO's), die na creatie niet gewijzigd mogen worden. Dit is vooral handig in functionele programmeerparadigma's.

4. Pick<T, K extends keyof T>

Het Pick<T, K extends keyof T> utility type creëert een nieuw type door een set eigenschappen K te kiezen uit type T. Dit is handig wanneer u slechts een subset van de eigenschappen van een bestaand type nodig heeft.

Syntaxis:

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

Voorbeeld:

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

type EmployeeNameAndDepartment = Pick<Employee, "name" | "department">; // Kies alleen naam en afdeling

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

Gebruiksscenario: Het creëren van gespecialiseerde data transfer objects (DTO's) die alleen de benodigde gegevens voor een specifieke operatie bevatten. Dit kan de prestaties verbeteren en de hoeveelheid gegevens die over het netwerk wordt verzonden verminderen. Stel u voor dat u gebruikersgegevens naar de client stuurt, maar gevoelige informatie zoals salaris uitsluit. U kunt Pick gebruiken om alleen `id` en `name` te sturen.

5. Omit<T, K extends keyof any>

Het Omit<T, K extends keyof any> utility type creëert een nieuw type door een set eigenschappen K weg te laten uit type T. Dit is het tegenovergestelde van Pick<T, K extends keyof T> en is handig wanneer u bepaalde eigenschappen van een bestaand type wilt uitsluiten.

Syntaxis:

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

Voorbeeld:

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

type EventSummary = Omit<Event, "description" | "location">; // Laat beschrijving en locatie weg

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

Gebruiksscenario: Het creëren van vereenvoudigde versies van datamodellen voor specifieke doeleinden, zoals het weergeven van een samenvatting van een evenement zonder de volledige beschrijving en locatie. Dit kan ook worden gebruikt om gevoelige velden te verwijderen voordat gegevens naar een client worden verzonden.

6. Exclude<T, U>

Het Exclude<T, U> utility type creëert een nieuw type door uit T alle types uit te sluiten die toewijsbaar zijn aan U. Dit is handig wanneer u bepaalde types uit een union type wilt verwijderen.

Syntaxis:

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

Voorbeeld:

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

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

const fileType: DocumentFileTypes = "document";

Gebruiksscenario: Het filteren van een union type om specifieke types te verwijderen die in een bepaalde context niet relevant zijn. U zou bijvoorbeeld bepaalde bestandstypen willen uitsluiten van een lijst met toegestane bestandstypen.

7. Extract<T, U>

Het Extract<T, U> utility type creëert een nieuw type door uit T alle types te extraheren die toewijsbaar zijn aan U. Dit is het tegenovergestelde van Exclude<T, U> en is handig wanneer u specifieke types uit een union type wilt selecteren.

Syntaxis:

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

Voorbeeld:

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

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

const value: NonNullablePrimitives = "hello";

Gebruiksscenario: Het selecteren van specifieke types uit een union type op basis van bepaalde criteria. U zou bijvoorbeeld alle primitieve types willen extraheren uit een union type dat zowel primitieve types als objecttypes bevat.

8. NonNullable<T>

Het NonNullable<T> utility type creëert een nieuw type door null en undefined uit te sluiten van type T. Dit is handig wanneer u wilt verzekeren dat een type niet null of undefined kan zijn.

Syntaxis:

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

Voorbeeld:

type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>; // string

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

Gebruiksscenario: Afdwingen dat een waarde niet null of undefined is voordat u er een bewerking op uitvoert. Dit kan helpen bij het voorkomen van runtime-fouten veroorzaakt door onverwachte null- of undefined-waarden. Denk aan een scenario waarin u het adres van een gebruiker moet verwerken en het cruciaal is dat het adres niet null is voordat een bewerking wordt uitgevoerd.

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

Het ReturnType<T extends (...args: any) => any> utility type extraheert het return-type van een functietype T. Dit is handig wanneer u het type wilt weten van de waarde die een functie retourneert.

Syntaxis:

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

Voorbeeld:

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) {
 // ...
}

Gebruiksscenario: Het bepalen van het type van de waarde die door een functie wordt geretourneerd, vooral bij het omgaan met asynchrone operaties of complexe functiesignaturen. Hiermee kunt u ervoor zorgen dat u de geretourneerde waarde correct behandelt.

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

Het Parameters<T extends (...args: any) => any> utility type extraheert de parametertypes van een functietype T als een tuple. Dit is handig wanneer u de types wilt weten van de argumenten die een functie accepteert.

Syntaxis:

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

Voorbeeld:

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

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

function logUser(...args: CreateUserParams) {
 console.log("Creating user with:", args);
}

Gebruiksscenario: Het bepalen van de types van de argumenten die een functie accepteert, wat handig kan zijn voor het maken van generieke functies of decorators die moeten werken met functies van verschillende signaturen. Het helpt de typeveiligheid te waarborgen bij het dynamisch doorgeven van argumenten aan een functie.

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

Het ConstructorParameters<T extends abstract new (...args: any) => any> utility type extraheert de parametertypes van een constructorfunctietype T als een tuple. Dit is handig wanneer u de types wilt weten van de argumenten die een constructor accepteert.

Syntaxis:

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

Voorbeeld:

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

Gebruiksscenario: Vergelijkbaar met Parameters, maar specifiek voor constructorfuncties. Het helpt bij het maken van factories of dependency injection-systemen waar u klassen dynamisch moet instantiëren met verschillende constructorsignaturen.

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

Het InstanceType<T extends abstract new (...args: any) => any> utility type extraheert het instantie-type van een constructorfunctietype T. Dit is handig wanneer u het type wilt weten van het object dat een constructor creëert.

Syntaxis:

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

Voorbeeld:

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

Gebruiksscenario: Het bepalen van het type van het object dat door een constructor wordt gemaakt, wat handig is bij het werken met overerving of polymorfisme. Het biedt een type-veilige manier om te verwijzen naar de instantie van een klasse.

13. Record<K extends keyof any, T>

Het Record<K extends keyof any, T> utility type construeert een objecttype waarvan de eigenschapssleutels K zijn en de eigenschapswaarden T. Dit is handig voor het maken van dictionary-achtige types waarbij u de sleutels van tevoren kent.

Syntaxis:

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

Voorbeeld:

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",
};

Gebruiksscenario: Het creëren van dictionary-achtige objecten waarbij u een vaste set sleutels heeft en wilt verzekeren dat alle sleutels waarden van een specifiek type hebben. Dit komt vaak voor bij het werken met configuratiebestanden, datamappings of opzoektabellen.

Aangepaste Utility Types

Hoewel de ingebouwde utility types van TypeScript krachtig zijn, kunt u ook uw eigen aangepaste utility types maken om specifieke behoeften in uw projecten aan te pakken. Hiermee kunt u complexe typetransformaties inkapselen en hergebruiken in uw codebase.

Voorbeeld:

// Een utility type om de sleutels van een object te krijgen die een specifiek type hebben
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"

Best Practices voor het Gebruik van Utility Types

Conclusie

TypeScript utility types zijn krachtige tools die de typeveiligheid, herbruikbaarheid en onderhoudbaarheid van uw code aanzienlijk kunnen verbeteren. Door deze utility types onder de knie te krijgen, kunt u robuustere en expressievere TypeScript-applicaties schrijven. Deze gids heeft de meest essentiële TypeScript utility types behandeld, met praktische voorbeelden en bruikbare inzichten om u te helpen ze in uw projecten op te nemen.

Vergeet niet te experimenteren met deze utility types en te onderzoeken hoe ze kunnen worden gebruikt om specifieke problemen in uw eigen code op te lossen. Naarmate u er vertrouwder mee raakt, zult u merken dat u ze steeds vaker gebruikt om schonere, beter onderhoudbare en meer type-veilige TypeScript-applicaties te maken. Of u nu webapplicaties, server-side applicaties of iets daartussenin bouwt, utility types bieden een waardevolle set tools om uw ontwikkelingsworkflow en de kwaliteit van uw code te verbeteren. Door gebruik te maken van deze ingebouwde tools voor typemanipulatie, kunt u het volledige potentieel van TypeScript ontsluiten en code schrijven die zowel expressief als robuust is.