Nederlands

Ontgrendel de kracht van TypeScript utility types om schonere, beter onderhoudbare en type-veilige code te schrijven. Ontdek praktische toepassingen met praktijkvoorbeelden voor ontwikkelaars wereldwijd.

TypeScript Utility Types Meesteren: Een Praktische Gids voor Wereldwijde Ontwikkelaars

TypeScript biedt een krachtige set ingebouwde utility types die de type-veiligheid, leesbaarheid en onderhoudbaarheid van uw code aanzienlijk kunnen verbeteren. Deze utility types zijn in wezen vooraf gedefinieerde type-transformaties die u kunt toepassen op bestaande types, waardoor u geen herhalende en foutgevoelige code hoeft te schrijven. Deze gids verkent diverse utility types met praktische voorbeelden die herkenbaar zijn voor ontwikkelaars over de hele wereld.

Waarom Utility Types Gebruiken?

Utility types bieden een oplossing voor veelvoorkomende scenario's van type-manipulatie. Door ze te gebruiken, kunt u:

Kern Utility Types

Partial<T>

Partial<T> construeert een type waarbij alle eigenschappen van T optioneel worden gemaakt. Dit is met name handig wanneer u een type wilt creëren voor gedeeltelijke updates of configuratieobjecten.

Voorbeeld:

Stel u voor dat u een e-commerceplatform bouwt met klanten uit diverse regio's. U heeft een Customer-type:


interface Customer {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  phoneNumber: string;
  address: {
    street: string;
    city: string;
    country: string;
    postalCode: string;
  };
  preferences?: {
    language: string;
    currency: string;
  }
}

Wanneer u de informatie van een klant bijwerkt, wilt u misschien niet alle velden verplichten. Partial<Customer> stelt u in staat een type te definiëren waarin alle eigenschappen van Customer optioneel zijn:


type PartialCustomer = Partial<Customer>;

function updateCustomer(id: string, updates: PartialCustomer): void {
  // ... implementatie om de klant met de opgegeven ID bij te werken
}

updateCustomer("123", { firstName: "John", lastName: "Doe" }); // Geldig
updateCustomer("456", { address: { city: "London" } }); // Geldig

Readonly<T>

Readonly<T> construeert een type waarbij alle eigenschappen van T readonly worden, waardoor wijziging na initialisatie wordt voorkomen. Dit is waardevol voor het waarborgen van onveranderlijkheid (immutability).

Voorbeeld:

Neem een configuratieobject voor uw wereldwijde applicatie:


interface AppConfig {
  apiUrl: string;
  theme: string;
  supportedLanguages: string[];
  version: string; // Added version
}

const config: AppConfig = {
  apiUrl: "https://api.example.com",
  theme: "dark",
  supportedLanguages: ["en", "fr", "de", "es", "zh"],
  version: "1.0.0"
};

Om onbedoelde wijziging van de configuratie na initialisatie te voorkomen, kunt u Readonly<AppConfig> gebruiken:


type ReadonlyAppConfig = Readonly<AppConfig>;

const readonlyConfig: ReadonlyAppConfig = {
  apiUrl: "https://api.example.com",
  theme: "dark",
  supportedLanguages: ["en", "fr", "de", "es", "zh"],
  version: "1.0.0"
};

// readonlyConfig.apiUrl = "https://newapi.example.com"; // Fout: Kan niet toewijzen aan 'apiUrl' omdat het een alleen-lezen eigenschap is.

Pick<T, K>

Pick<T, K> construeert een type door de set eigenschappen K uit T te kiezen, waarbij K een union van string literal types is die de eigenschapsnamen vertegenwoordigen die u wilt opnemen.

Voorbeeld:

Stel, u heeft een Event-interface met verschillende eigenschappen:


interface Event {
  id: string;
  title: string;
  description: string;
  location: string;
  startTime: Date;
  endTime: Date;
  organizer: string;
  attendees: string[];
}

Als u voor een specifieke weergavecomponent alleen de title, location en startTime nodig heeft, kunt u Pick gebruiken:


type EventSummary = Pick<Event, "title" | "location" | "startTime">;

function displayEventSummary(event: EventSummary): void {
  console.log(`Event: ${event.title} at ${event.location} on ${event.startTime}`);
}

Omit<T, K>

Omit<T, K> construeert een type door de set eigenschappen K uit T uit te sluiten, waarbij K een union van string literal types is die de eigenschapsnamen vertegenwoordigen die u wilt uitsluiten. Dit is het tegenovergestelde van Pick.

Voorbeeld:

Gebruikmakend van dezelfde Event-interface, als u een type wilt maken voor het aanmaken van nieuwe evenementen, wilt u misschien de id-eigenschap uitsluiten, die doorgaans door de backend wordt gegenereerd:


type NewEvent = Omit<Event, "id">;

function createEvent(event: NewEvent): void {
  // ... implementatie om een nieuw evenement aan te maken
}

Record<K, T>

Record<K, T> construeert een objecttype waarvan de eigenschapssleutels K zijn en de eigenschapswaarden T. K kan een union van string literal types, number literal types of een symbool zijn. Dit is perfect voor het maken van woordenboeken of 'maps'.

Voorbeeld:

Stel u voor dat u vertalingen voor de gebruikersinterface van uw applicatie moet opslaan. U kunt Record gebruiken om een type voor uw vertalingen te definiëren:


type Translations = Record<string, string>;

const enTranslations: Translations = {
  "hello": "Hello",
  "goodbye": "Goodbye",
  "welcome": "Welcome to our platform!"
};

const frTranslations: Translations = {
  "hello": "Bonjour",
  "goodbye": "Au revoir",
  "welcome": "Bienvenue sur notre plateforme !"
};

function translate(key: string, language: string): string {
  const translations = language === "en" ? enTranslations : frTranslations; //Vereenvoudigd
  return translations[key] || key; // Val terug op de sleutel als er geen vertaling wordt gevonden
}

console.log(translate("hello", "en")); // Output: Hello
console.log(translate("hello", "fr")); // Output: Bonjour
console.log(translate("nonexistent", "en")); // Output: nonexistent

Exclude<T, U>

Exclude<T, U> construeert een type door uit T alle union members uit te sluiten die toewijsbaar zijn aan U. Het is handig voor het filteren van specifieke types uit een union.

Voorbeeld:

U heeft misschien een type dat verschillende evenementtypes vertegenwoordigt:


type EventType = "concert" | "conference" | "workshop" | "webinar";

Als u een type wilt maken dat "webinar"-evenementen uitsluit, kunt u Exclude gebruiken:


type PhysicalEvent = Exclude<EventType, "webinar">;

// PhysicalEvent is nu "concert" | "conference" | "workshop"

function attendPhysicalEvent(event: PhysicalEvent): void {
  console.log(`Attending a ${event}`);
}

// attendPhysicalEvent("webinar"); // Fout: Argument van type '"webinar"' is niet toewijsbaar aan parameter van type '"concert" | "conference" | "workshop"'.

attendPhysicalEvent("concert"); // Geldig

Extract<T, U>

Extract<T, U> construeert een type door uit T alle union members te extraheren die toewijsbaar zijn aan U. Dit is het tegenovergestelde van Exclude.

Voorbeeld:

Gebruikmakend van dezelfde EventType, kunt u het webinar-evenementtype extraheren:


type OnlineEvent = Extract<EventType, "webinar">;

// OnlineEvent is nu "webinar"

function attendOnlineEvent(event: OnlineEvent): void {
  console.log(`Attending a ${event} online`);
}

attendOnlineEvent("webinar"); // Geldig
// attendOnlineEvent("concert"); // Fout: Argument van type '"concert"' is niet toewijsbaar aan parameter van type '"webinar"'.

NonNullable<T>

NonNullable<T> construeert een type door null en undefined uit T uit te sluiten.

Voorbeeld:


type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>;

// DefinitelyString is nu string

function processString(str: DefinitelyString): void {
  console.log(str.toUpperCase());
}

// processString(null); // Fout: Argument van type 'null' is niet toewijsbaar aan parameter van type 'string'.
// processString(undefined); // Fout: Argument van type 'undefined' is niet toewijsbaar aan parameter van type 'string'.
processString("hello"); // Geldig

ReturnType<T>

ReturnType<T> construeert een type dat bestaat uit het return-type van functie T.

Voorbeeld:


function greet(name: string): string {
  return `Hello, ${name}!`;
}

type Greeting = ReturnType<typeof greet>;

// Greeting is nu string

const message: Greeting = greet("World");

console.log(message);

Parameters<T>

Parameters<T> construeert een tuple-type uit de types van de parameters van een functietype T.

Voorbeeld:


function logEvent(eventName: string, eventData: object): void {
  console.log(`Event: ${eventName}`, eventData);
}

type LogEventParams = Parameters<typeof logEvent>;

// LogEventParams is nu [eventName: string, eventData: object]

const params: LogEventParams = ["user_login", { userId: "123", timestamp: Date.now() }];

logEvent(...params);

ConstructorParameters<T>

ConstructorParameters<T> construeert een tuple- of array-type uit de types van de parameters van een constructor-functietype T. Het leidt de types af van de argumenten die aan de constructor van een klasse moeten worden doorgegeven.

Voorbeeld:


class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return "Hello, " + this.greeting;
  }
}


type GreeterParams = ConstructorParameters<typeof Greeter>;

// GreeterParams is nu [message: string]

const paramsGreeter: GreeterParams = ["World"];
const greeterInstance = new Greeter(...paramsGreeter);

console.log(greeterInstance.greet()); // Outputs: Hello, World

Required<T>

Required<T> construeert een type dat bestaat uit alle eigenschappen van T die als verplicht zijn ingesteld. Het maakt alle optionele eigenschappen verplicht.

Voorbeeld:


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

type RequiredUserProfile = Required<UserProfile>;

// RequiredUserProfile is nu { name: string; age: number; email: string; }

const completeProfile: RequiredUserProfile = {
  name: "Alice",
  age: 30,
  email: "alice@example.com"
};

// const incompleteProfile: RequiredUserProfile = { name: "Bob" }; // Fout: Eigenschap 'age' ontbreekt in type '{ name: string; }' maar is verplicht in type 'Required'.

Geavanceerde Utility Types

Template Literal Types

Template literal types stellen u in staat om nieuwe string literal types te construeren door bestaande string literal types, number literal types en meer te combineren. Dit maakt krachtige, op strings gebaseerde type-manipulatie mogelijk.

Voorbeeld:


type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/users` | `/api/products`;

type RequestURL = `${HTTPMethod} ${APIEndpoint}`;

// RequestURL is nu "GET /api/users" | "POST /api/users" | "PUT /api/users" | "DELETE /api/users" | "GET /api/products" | "POST /api/products" | "PUT /api/products" | "DELETE /api/products"

function makeRequest(url: RequestURL): void {
  console.log(`Making request to ${url}`);
}

makeRequest("GET /api/users"); // Geldig
// makeRequest("INVALID /api/users"); // Fout

Conditionele Types

Conditionele types stellen u in staat types te definiëren die afhankelijk zijn van een voorwaarde, uitgedrukt als een type-relatie. Ze gebruiken het infer-sleutelwoord om type-informatie te extraheren.

Voorbeeld:


type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

// Als T een Promise is, dan is het type U; anders is het type T.

async function fetchData(): Promise<number> {
  return 42;
}


type Data = UnwrapPromise<ReturnType<typeof fetchData>>;

// Data is nu number

function processData(data: Data): void {
  console.log(data * 2);
}

processData(await fetchData());

Praktische Toepassingen en Real-World Scenario's

Laten we complexere, real-world scenario's verkennen waarin utility types uitblinken.

1. Formulierafhandeling

Bij het omgaan met formulieren heeft u vaak scenario's waarin u de initiële formulierwaarden, de bijgewerkte formulierwaarden en de definitief ingediende waarden moet representeren. Utility types kunnen u helpen deze verschillende staten efficiënt te beheren.


interface FormData {
  firstName: string;
  lastName: string;
  email: string;
  country: string; // Verplicht
  city?: string; // Optioneel
  postalCode?: string;
  newsletterSubscription?: boolean;
}

// Initiële formulierwaarden (optionele velden)
type InitialFormValues = Partial<FormData>;

// Bijgewerkte formulierwaarden (sommige velden kunnen ontbreken)
type UpdatedFormValues = Partial<FormData>;

// Verplichte velden voor indiening
type RequiredForSubmission = Required<Pick<FormData, 'firstName' | 'lastName' | 'email' | 'country'>>;

// Gebruik deze types in uw formuliercomponenten
function initializeForm(initialValues: InitialFormValues): void { }
function updateForm(updates: UpdatedFormValues): void {}
function submitForm(data: RequiredForSubmission): void {}


const initialForm: InitialFormValues = { newsletterSubscription: true };

const updateFormValues: UpdatedFormValues = {
    firstName: "John",
    lastName: "Doe"
};

// const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test" }; // FOUT: 'country' ontbreekt
const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test", country: "USA" }; //OK


2. API Data Transformatie

Bij het consumeren van data van een API moet u de data mogelijk transformeren naar een ander formaat voor uw applicatie. Utility types kunnen u helpen de structuur van de getransformeerde data te definiëren.


interface APIResponse {
  user_id: string;
  first_name: string;
  last_name: string;
  email_address: string;
  profile_picture_url: string;
  is_active: boolean;
}

// Transformeer de API-respons naar een beter leesbaar formaat
type UserData = {
  id: string;
  fullName: string;
  email: string;
  avatar: string;
  active: boolean;
};

function transformApiResponse(response: APIResponse): UserData {
  return {
    id: response.user_id,
    fullName: `${response.first_name} ${response.last_name}`,
    email: response.email_address,
    avatar: response.profile_picture_url,
    active: response.is_active
  };
}

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


// U kunt het type zelfs afdwingen door:

function saferTransformApiResponse(response: APIResponse): UserData {
    const {user_id, first_name, last_name, email_address, profile_picture_url, is_active} = response;
    const transformed: UserData = {
        id: user_id,
        fullName: `${first_name} ${last_name}`,
        email: email_address,
        avatar: profile_picture_url,
        active: is_active
    };

    return transformed;
}

3. Omgaan met Configuratieobjecten

Configuratieobjecten zijn gebruikelijk in veel applicaties. Utility types kunnen u helpen de structuur van het configuratieobject te definiëren en ervoor te zorgen dat het correct wordt gebruikt.


interface AppSettings {
  theme: "light" | "dark";
  language: string;
  notificationsEnabled: boolean;
  apiUrl?: string; // Optionele API URL voor verschillende omgevingen
  timeout?: number;  //Optioneel
}

// Standaardinstellingen
const defaultSettings: AppSettings = {
  theme: "light",
  language: "en",
  notificationsEnabled: true
};

// Functie om gebruikersinstellingen samen te voegen met standaardinstellingen
function mergeSettings(userSettings: Partial<AppSettings>): AppSettings {
  return { ...defaultSettings, ...userSettings };
}

// Gebruik de samengevoegde instellingen in uw applicatie
const mergedSettings = mergeSettings({ theme: "dark", apiUrl: "https://customapi.example.com" });
console.log(mergedSettings);

Tips voor Effectief Gebruik van Utility Types

  • Begin eenvoudig: Start met basis utility types zoals Partial en Readonly voordat u overstapt op complexere.
  • Gebruik beschrijvende namen: Geef uw type-aliassen betekenisvolle namen om de leesbaarheid te verbeteren.
  • Combineer utility types: U kunt meerdere utility types combineren om complexe type-transformaties te bereiken.
  • Maak gebruik van editor-ondersteuning: Profiteer van de uitstekende editor-ondersteuning van TypeScript om de effecten van utility types te verkennen.
  • Begrijp de onderliggende concepten: Een solide begrip van het type-systeem van TypeScript is essentieel voor effectief gebruik van utility types.

Conclusie

TypeScript utility types zijn krachtige hulpmiddelen die de kwaliteit en onderhoudbaarheid van uw code aanzienlijk kunnen verbeteren. Door deze utility types effectief te begrijpen en toe te passen, kunt u schonere, meer type-veilige en robuustere applicaties schrijven die voldoen aan de eisen van een wereldwijd ontwikkelingslandschap. Deze gids heeft een uitgebreid overzicht gegeven van veelvoorkomende utility types en praktische voorbeelden. Experimenteer ermee en verken hun potentieel om uw TypeScript-projecten te verbeteren. Vergeet niet om leesbaarheid en duidelijkheid prioriteit te geven bij het gebruik van utility types, en streef er altijd naar om code te schrijven die gemakkelijk te begrijpen en te onderhouden is, ongeacht waar uw mede-ontwikkelaars zich bevinden.

TypeScript Utility Types Meesteren: Een Praktische Gids voor Wereldwijde Ontwikkelaars | MLOG