Nederlands

Een uitgebreide gids voor TypeScript's krachtige Mapped Types en Conditional Types, met praktische voorbeelden en geavanceerde toepassingen voor het bouwen van robuuste en type-veilige applicaties.

TypeScript's Mapped Types en Conditional Types de Baas Worden

TypeScript, een superset van JavaScript, biedt krachtige functies voor het bouwen van robuuste en onderhoudbare applicaties. Onder deze functies vallen Mapped Types en Conditional Types op als essentiële hulpmiddelen voor geavanceerde type-manipulatie. Deze gids biedt een uitgebreid overzicht van deze concepten, waarbij de syntaxis, praktische toepassingen en geavanceerde use cases worden verkend. Of u nu een ervaren TypeScript-ontwikkelaar bent of net aan uw reis begint, dit artikel zal u voorzien van de kennis om deze functies effectief te benutten.

Wat zijn Mapped Types?

Mapped Types stellen u in staat nieuwe types te creëren door bestaande te transformeren. Ze itereren over de eigenschappen van een bestaand type en passen op elke eigenschap een transformatie toe. Dit is met name handig voor het creëren van variaties op bestaande types, zoals het optioneel of alleen-lezen maken van alle eigenschappen.

Basissyntaxis

De syntaxis voor een Mapped Type is als volgt:

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

Praktische Voorbeelden

Eigenschappen Alleen-Lezen Maken

Stel dat u een interface heeft die een gebruikersprofiel vertegenwoordigt:

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

U kunt een nieuw type creëren waarin alle eigenschappen alleen-lezen zijn:

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

Nu heeft ReadOnlyUserProfile dezelfde eigenschappen als UserProfile, maar ze zijn allemaal alleen-lezen.

Eigenschappen Optioneel Maken

Op dezelfde manier kunt u alle eigenschappen optioneel maken:

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

OptionalUserProfile heeft alle eigenschappen van UserProfile, maar elke eigenschap is optioneel.

Eigenschapstypes Wijzigen

U kunt ook het type van elke eigenschap wijzigen. U kunt bijvoorbeeld alle eigenschappen transformeren naar strings:

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

In dit geval zullen alle eigenschappen in StringifiedUserProfile van het type string zijn.

Wat zijn Conditional Types?

Conditional Types stellen u in staat types te definiëren die afhankelijk zijn van een voorwaarde. Ze bieden een manier om type-relaties uit te drukken op basis van of een type aan een bepaalde beperking voldoet. Dit is vergelijkbaar met een ternaire operator in JavaScript, maar dan voor types.

Basissyntaxis

De syntaxis voor een Conditional Type is als volgt:

T extends U ? X : Y

Praktische Voorbeelden

Bepalen of een Type een String is

Laten we een type creëren dat string retourneert als het invoertype een string is, en anders number:

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

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

Type Extraheren uit een Union

U kunt conditional types gebruiken om een specifiek type uit een union type te extraheren. Bijvoorbeeld om non-nullable types te extraheren:

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

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

Hier, als T null of undefined is, wordt het type never, wat vervolgens wordt uitgefilterd door de vereenvoudiging van union types in TypeScript.

Types Afleiden (Infer)

Conditional types kunnen ook worden gebruikt om types af te leiden met het infer sleutelwoord. Hiermee kunt u een type extraheren uit een complexere type-structuur.

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

In dit voorbeeld extraheert ReturnType het return-type van een functie. Het controleert of T een functie is die willekeurige argumenten aanneemt en een type R retourneert. Zo ja, dan retourneert het R; anders retourneert het any.

Mapped Types en Conditional Types Combineren

De ware kracht van Mapped Types en Conditional Types komt voort uit hun combinatie. Dit stelt u in staat om zeer flexibele en expressieve type-transformaties te creëren.

Voorbeeld: Deep Readonly

Een veelvoorkomende toepassing is het creëren van een type dat alle eigenschappen van een object, inclusief geneste eigenschappen, alleen-lezen maakt. Dit kan worden bereikt met een recursief conditional 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>;

Hier past DeepReadonly de readonly-modifier recursief toe op alle eigenschappen en hun geneste eigenschappen. Als een eigenschap een object is, roept het recursief DeepReadonly aan op dat object. Anders past het simpelweg de readonly-modifier toe op de eigenschap.

Voorbeeld: Eigenschappen Filteren op Type

Stel dat u een type wilt creëren dat alleen eigenschappen van een specifiek type bevat. U kunt Mapped Types en Conditional Types combineren om dit te bereiken.

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

In dit voorbeeld itereert FilterByType over de eigenschappen van T en controleert of het type van elke eigenschap U uitbreidt. Zo ja, dan wordt de eigenschap opgenomen in het resulterende type; anders wordt deze uitgesloten door de sleutel te mappen naar never. Let op het gebruik van "as" om sleutels opnieuw te mappen. Vervolgens gebruiken we `Omit` en `keyof StringProperties` om de string-eigenschappen uit de oorspronkelijke interface te verwijderen.

Geavanceerde Toepassingen en Patronen

Naast de basisvoorbeelden kunnen Mapped Types en Conditional Types worden gebruikt in meer geavanceerde scenario's om zeer aanpasbare en type-veilige applicaties te creëren.

Distributieve Conditional Types

Conditional types zijn distributief wanneer het type dat wordt gecontroleerd een union type is. Dit betekent dat de voorwaarde op elk lid van de union afzonderlijk wordt toegepast, en de resultaten vervolgens worden gecombineerd tot een nieuw union type.

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

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

In dit voorbeeld wordt ToArray toegepast op elk lid van de union string | number afzonderlijk, wat resulteert in string[] | number[]. Als de voorwaarde niet distributief was geweest, zou het resultaat (string | number)[] zijn geweest.

Gebruik van Utility Types

TypeScript biedt verschillende ingebouwde utility types die gebruikmaken van Mapped Types en Conditional Types. Deze utility types kunnen worden gebruikt als bouwstenen voor complexere type-transformaties.

Deze utility types zijn krachtige hulpmiddelen die complexe type-manipulaties kunnen vereenvoudigen. U kunt bijvoorbeeld Pick en Partial combineren om een type te creëren dat alleen bepaalde eigenschappen optioneel maakt:

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

In dit voorbeeld heeft OptionalDescriptionProduct alle eigenschappen van Product, maar de eigenschap description is optioneel.

Gebruik van Template Literal Types

Template Literal Types stellen u in staat types te creëren op basis van string literals. Ze kunnen worden gebruikt in combinatie met Mapped Types en Conditional Types om dynamische en expressieve type-transformaties te creëren. U kunt bijvoorbeeld een type creëren dat alle eigenschapsnamen voorziet van een specifiek voorvoegsel:

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

In dit voorbeeld heeft PrefixedSettings de eigenschappen data_apiUrl en data_timeout.

Best Practices en Overwegingen

Conclusie

Mapped Types en Conditional Types zijn krachtige functies in TypeScript die u in staat stellen zeer flexibele en expressieve type-transformaties te creëren. Door deze concepten te beheersen, kunt u de typeveiligheid, onderhoudbaarheid en algehele kwaliteit van uw TypeScript-applicaties verbeteren. Van eenvoudige transformaties zoals het optioneel of alleen-lezen maken van eigenschappen tot complexe recursieve transformaties en conditionele logica, bieden deze functies de hulpmiddelen die u nodig heeft om robuuste en schaalbare applicaties te bouwen. Blijf deze functies verkennen en ermee experimenteren om hun volledige potentieel te ontsluiten en een vaardigere TypeScript-ontwikkelaar te worden.

Terwijl u uw TypeScript-reis voortzet, vergeet dan niet gebruik te maken van de overvloed aan beschikbare bronnen, waaronder de officiële TypeScript-documentatie, online communities en open-sourceprojecten. Omarm de kracht van Mapped Types en Conditional Types, en u zult goed uitgerust zijn om zelfs de meest uitdagende type-gerelateerde problemen aan te pakken.

TypeScript's Mapped Types en Conditional Types de Baas Worden | MLOG