Deutsch

Nutzen Sie die Leistungsfähigkeit der TypeScript Utility Types, um saubereren, wartbareren und typsicheren Code zu schreiben. Entdecken Sie praktische Anwendungen mit realen Beispielen für Entwickler weltweit.

TypeScript Utility Types meistern: Ein praktischer Leitfaden für globale Entwickler

TypeScript bietet eine leistungsstarke Reihe integrierter Utility Types, die die Typsicherheit, Lesbarkeit und Wartbarkeit Ihres Codes erheblich verbessern können. Diese Utility Types sind im Wesentlichen vordefinierte Typumwandlungen, die Sie auf bestehende Typen anwenden können, wodurch Sie sich das Schreiben von sich wiederholendem und fehleranfälligem Code sparen. Dieser Leitfaden wird verschiedene Utility Types mit praktischen Beispielen untersuchen, die bei Entwicklern auf der ganzen Welt Anklang finden.

Warum Utility Types verwenden?

Utility Types lösen gängige Szenarien der Typmanipulation. Indem Sie sie nutzen, können Sie:

Wichtige Utility Types

Partial<T>

Partial<T> konstruiert einen Typ, bei dem alle Eigenschaften von T optional gesetzt sind. Dies ist besonders nützlich, wenn Sie einen Typ für Teilaktualisierungen oder Konfigurationsobjekte erstellen möchten.

Beispiel:

Stellen Sie sich vor, Sie bauen eine E-Commerce-Plattform mit Kunden aus verschiedenen Regionen auf. Sie haben einen Customer-Typ:


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

Wenn Sie die Informationen eines Kunden aktualisieren, möchten Sie möglicherweise nicht alle Felder als erforderlich festlegen. Partial<Customer> ermöglicht es Ihnen, einen Typ zu definieren, bei dem alle Eigenschaften von Customer optional sind:


type PartialCustomer = Partial<Customer>;

function updateCustomer(id: string, updates: PartialCustomer): void {
  // ... Implementierung zum Aktualisieren des Kunden mit der gegebenen ID
}

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

Readonly<T>

Readonly<T> konstruiert einen Typ, bei dem alle Eigenschaften von T auf readonly gesetzt sind, was eine Änderung nach der Initialisierung verhindert. Dies ist wertvoll, um Unveränderlichkeit zu gewährleisten.

Beispiel:

Betrachten Sie ein Konfigurationsobjekt für Ihre globale Anwendung:


interface AppConfig {
  apiUrl: string;
  theme: string;
  supportedLanguages: string[];
  version: string; // Version hinzugefügt
}

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

Um eine versehentliche Änderung der Konfiguration nach der Initialisierung zu verhindern, können Sie Readonly<AppConfig> verwenden:


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"; // Error: Cannot assign to 'apiUrl' because it is a read-only property.

Pick<T, K>

Pick<T, K> konstruiert einen Typ, indem es die Menge der Eigenschaften K aus T auswählt, wobei K eine Vereinigung von String-Literal-Typen ist, die die Eigenschaftsnamen darstellen, die Sie einschließen möchten.

Beispiel:

Nehmen wir an, Sie haben ein Event-Interface mit verschiedenen Eigenschaften:


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

Wenn Sie nur den title, location und startTime für eine bestimmte Anzeigekomponente benötigen, können Sie Pick verwenden:


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

function displayEventSummary(event: EventSummary): void {
  console.log(`Ereignis: ${event.title} um ${event.location} am ${event.startTime}`);
}

Omit<T, K>

Omit<T, K> konstruiert einen Typ, indem es die Menge der Eigenschaften K aus T ausschließt, wobei K eine Vereinigung von String-Literal-Typen ist, die die Eigenschaftsnamen darstellen, die Sie ausschließen möchten. Dies ist das Gegenteil von Pick.

Beispiel:

Wenn Sie dieselbe Event-Schnittstelle verwenden und einen Typ zum Erstellen neuer Ereignisse erstellen möchten, möchten Sie möglicherweise die id-Eigenschaft ausschließen, die normalerweise vom Backend generiert wird:


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

function createEvent(event: NewEvent): void {
  // ... Implementierung zum Erstellen eines neuen Ereignisses
}

Record<K, T>

Record<K, T> konstruiert einen Objekttyp, dessen Eigenschaftsschlüssel K und dessen Eigenschaftswerte T sind. K kann eine Vereinigung von String-Literal-Typen, Zahl-Literal-Typen oder ein Symbol sein. Dies ist perfekt zum Erstellen von Dictionaries oder Maps.

Beispiel:

Stellen Sie sich vor, Sie müssen Übersetzungen für die Benutzeroberfläche Ihrer Anwendung speichern. Sie können Record verwenden, um einen Typ für Ihre Übersetzungen zu definieren:


type Translations = Record<string, string>;

const enTranslations: Translations = {
  "hello": "Hallo",
  "goodbye": "Auf Wiedersehen",
  "welcome": "Willkommen auf unserer Plattform!"
};

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; //Vereinfacht
  return translations[key] || key; // Fallback auf den Schlüssel, falls keine Übersetzung gefunden wird
}

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

Exclude<T, U>

Exclude<T, U> konstruiert einen Typ, indem es aus T alle Vereinigungselemente ausschließt, die U zugewiesen werden können. Es ist nützlich, um bestimmte Typen aus einer Vereinigung herauszufiltern.

Beispiel:

Sie könnten einen Typ haben, der verschiedene Ereignistypen darstellt:


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

Wenn Sie einen Typ erstellen möchten, der "Webinar"-Ereignisse ausschließt, können Sie Exclude verwenden:


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

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

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

// attendPhysicalEvent("webinar"); // Error: Argument of type '"webinar"' is not assignable to parameter of type '"concert" | "conference" | "workshop"'.

attendPhysicalEvent("concert"); // Gültig

Extract<T, U>

Extract<T, U> konstruiert einen Typ, indem es aus T alle Vereinigungselemente extrahiert, die U zugewiesen werden können. Dies ist das Gegenteil von Exclude.

Beispiel:

Mit demselben EventType können Sie den Webinar-Ereignistyp extrahieren:


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

// OnlineEvent is now "webinar"

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

attendOnlineEvent("webinar"); // Gültig
// attendOnlineEvent("concert"); // Error: Argument of type '"concert"' is not assignable to parameter of type '"webinar"'.

NonNullable<T>

NonNullable<T> konstruiert einen Typ, indem es null und undefined aus T ausschließt.

Beispiel:


type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>;

// DefinitelyString is now string

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

// processString(null); // Error: Argument of type 'null' is not assignable to parameter of type 'string'.
// processString(undefined); // Error: Argument of type 'undefined' is not assignable to parameter of type 'string'.
processString("hello"); // Gültig

ReturnType<T>

ReturnType<T> konstruiert einen Typ, der aus dem Rückgabetyp der Funktion T besteht.

Beispiel:


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

type Greeting = ReturnType<typeof greet>;

// Greeting is now string

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

console.log(message);

Parameters<T>

Parameters<T> konstruiert einen Tuple-Typ aus den Typen der Parameter eines Funktionstyps T.

Beispiel:


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

type LogEventParams = Parameters<typeof logEvent>;

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

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

logEvent(...params);

ConstructorParameters<T>

ConstructorParameters<T> konstruiert einen Tuple- oder Array-Typ aus den Typen der Parameter eines Konstruktorfunktionstyps T. Es leitet die Typen der Argumente ab, die an den Konstruktor einer Klasse übergeben werden müssen.

Beispiel:


class Greeter {
  greeting: string;

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

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


type GreeterParams = ConstructorParameters<typeof Greeter>;

// GreeterParams is now [message: string]

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

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

Required<T>

Required<T> konstruiert einen Typ, der aus allen Eigenschaften von T besteht, die als erforderlich festgelegt sind. Es macht alle optionalen Eigenschaften erforderlich.

Beispiel:


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

type RequiredUserProfile = Required<UserProfile>;

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

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

// const incompleteProfile: RequiredUserProfile = { name: "Bob" }; // Error: Property 'age' is missing in type '{ name: string; }' but required in type 'Required'.

Erweiterte Utility Types

Template-Literal-Typen

Template-Literal-Typen ermöglichen es Ihnen, neue String-Literal-Typen durch die Verknüpfung bestehender String-Literal-Typen, Zahl-Literal-Typen und mehr zu konstruieren. Dies ermöglicht eine leistungsstarke stringbasierte Typmanipulation.

Beispiel:


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

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

// RequestURL is now "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"); // Gültig
// makeRequest("INVALID /api/users"); // Error

Bedingte Typen

Bedingte Typen ermöglichen es Ihnen, Typen zu definieren, die von einer Bedingung abhängen, die als Typbeziehung ausgedrückt wird. Sie verwenden das Schlüsselwort infer, um Typinformationen zu extrahieren.

Beispiel:


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

// Wenn T ein Promise ist, dann ist der Typ U; andernfalls ist der Typ T.

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


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

// Data is now number

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

processData(await fetchData());

Praktische Anwendungen und reale Szenarien

Lassen Sie uns komplexere reale Szenarien erkunden, in denen Utility Types glänzen.

1. Formularbehandlung

Bei der Arbeit mit Formularen haben Sie oft Szenarien, in denen Sie die anfänglichen Formularwerte, die aktualisierten Formularwerte und die schließlich übermittelten Werte darstellen müssen. Utility Types können Ihnen helfen, diese verschiedenen Zustände effizient zu verwalten.


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

// Anfängliche Formularwerte (optionale Felder)
type InitialFormValues = Partial<FormData>;

// Aktualisierte Formularwerte (einige Felder könnten fehlen)
type UpdatedFormValues = Partial<FormData>;

// Erforderliche Felder für die Übermittlung
type RequiredForSubmission = Required<Pick<FormData, 'firstName' | 'lastName' | 'email' | 'country'>>;

// Verwenden Sie diese Typen in Ihren Formular-Komponenten
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" }; // ERROR: Missing 'country' 
const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test", country: "USA" }; //OK


2. API-Datentransformation

Beim Konsumieren von Daten aus einer API müssen Sie die Daten möglicherweise in ein anderes Format für Ihre Anwendung umwandeln. Utility Types können Ihnen helfen, die Struktur der transformierten Daten zu definieren.


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

// Transformieren der API-Antwort in ein besser lesbares Format
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));
}


// Sie können den Typ sogar erzwingen durch:

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. Umgang mit Konfigurationsobjekten

Konfigurationsobjekte sind in vielen Anwendungen üblich. Utility Types können Ihnen helfen, die Struktur des Konfigurationsobjekts zu definieren und sicherzustellen, dass es korrekt verwendet wird.


interface AppSettings {
  theme: "light" | "dark";
  language: string;
  notificationsEnabled: boolean;
  apiUrl?: string; // Optionale API-URL für verschiedene Umgebungen
  timeout?: number;  // Optional
}

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

// Funktion zum Zusammenführen von Benutzereinstellungen mit Standardeinstellungen
function mergeSettings(userSettings: Partial<AppSettings>): AppSettings {
  return { ...defaultSettings, ...userSettings };
}

// Verwenden Sie die zusammengeführten Einstellungen in Ihrer Anwendung
const mergedSettings = mergeSettings({ theme: "dark", apiUrl: "https://customapi.example.com" });
console.log(mergedSettings);

Tipps für den effektiven Einsatz von Utility Types

  • Einfach beginnen: Fangen Sie mit grundlegenden Utility Types wie Partial und Readonly an, bevor Sie zu komplexeren übergehen.
  • Deskriptive Namen verwenden: Geben Sie Ihren Typ-Aliasen aussagekräftige Namen, um die Lesbarkeit zu verbessern.
  • Utility Types kombinieren: Sie können mehrere Utility Types kombinieren, um komplexe Typumwandlungen zu erreichen.
  • Editor-Unterstützung nutzen: Profitieren Sie von der hervorragenden Editor-Unterstützung von TypeScript, um die Auswirkungen von Utility Types zu erkunden.
  • Die zugrunde liegenden Konzepte verstehen: Ein fundiertes Verständnis des Typensystems von TypeScript ist für den effektiven Einsatz von Utility Types unerlässlich.

Fazit

TypeScript Utility Types sind leistungsstarke Werkzeuge, die die Qualität und Wartbarkeit Ihres Codes erheblich verbessern können. Indem Sie diese Utility Types effektiv verstehen und anwenden, können Sie sauberere, typsichere und robustere Anwendungen schreiben, die den Anforderungen einer globalen Entwicklungslandschaft gerecht werden. Dieser Leitfaden hat einen umfassenden Überblick über gängige Utility Types und praktische Beispiele gegeben. Experimentieren Sie mit ihnen und erkunden Sie ihr Potenzial, Ihre TypeScript-Projekte zu verbessern. Denken Sie daran, die Lesbarkeit und Klarheit bei der Verwendung von Utility Types zu priorisieren und immer danach zu streben, Code zu schreiben, der leicht zu verstehen und zu warten ist, unabhängig davon, wo sich Ihre Kollegen befinden.