conditional-type-checks für TypeScript: Komplettguide mit Beispielen

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Die Bibliothek conditional-type-checks für TypeScript: Typen testen leicht gemacht

TypeScript ist eine mächtige Sprache mit einem komplexen Typsystem, das bedingte Typen, Mapping, Typinferenz und vieles mehr umfasst. Doch Entwickler stehen oft vor einem Problem: Wie lässt sich überprüfen, ob die Typen genau wie erwartet funktionieren? Herkömmliche Unit-Tests prüfen Werte zur Laufzeit, können aber die Korrektheit der Typen zur Kompilierzeit nicht garantieren. Genau hier kommt die Bibliothek conditional-type-checks ins Spiel.

Diese Bibliothek bietet eine Reihe von Hilfsmitteln für das statische Testen von TypeScript-Typen. Sie ermöglicht es, Tests zu schreiben, die prüfen, ob ein bestimmter Typ den Erwartungen entspricht: Ist er ein Subtyp eines anderen? Sind zwei Typen gleich? Kann ein Wert einem anderen zugewiesen werden? Alle Prüfungen finden zur Kompilierzeit statt und haben keinen Einfluss auf die Laufzeit.

Warum ist das nützlich? Erstens für das Refactoring komplexer Typen – Sie können sicher sein, dass Änderungen das erwartete Verhalten nicht beeinträchtigen. Zweitens zur Dokumentation Ihrer Absichten – Typtests dienen als lebendige Spezifikation. Drittens für Bibliotheken, die komplexe Typen bereitstellen – Ihre Nutzer können auf deren Korrektheit vertrauen. Die Bibliothek ist besonders hilfreich bei der Arbeit mit bedingten Typen (extends, infer), Template-Literal-Typen und rekursiven Typen.

Installation

Die Bibliothek wird über npm als Entwicklungsabhängigkeit installiert:

npm install --save-dev conditional-type-checks

Oder über yarn:

yarn add --dev conditional-type-checks

Es sind keine zusätzlichen TypeScript-Konfigurationen erforderlich – die Bibliothek besteht vollständig aus Typen und generiert keinen JavaScript-Code.

Schnellstart

Erstellen Sie eine Datei type-tests.ts und schreiben Sie einen einfachen Test:

import { IsExact } from 'conditional-type-checks';

type TestType = string;
type ExpectedType = string;

// Prüfen, ob die Typen exakt übereinstimmen
type TestPass = IsExact<TestType, ExpectedType>; // true

// Bei Nichtübereinstimmung gibt es einen Kompilierfehler
// type TestFail = IsExact<TestType, number>; // false

Damit der Test tatsächlich etwas prüft, verwenden Sie die Konstruktion expectType oder einfach eine Zuweisung mit Prüfung:

import { assert } from 'conditional-type-checks';

type MyType = { name: string; age: number };

// Prüfen, ob MyType ein Objekt mit den Feldern name und age ist
type Check = assert<IsExact<MyType, { name: string; age: number }>>; // OK

Wenn die Prüfung fehlschlägt, gibt TypeScript einen Kompilierfehler mit der Meldung Type 'false' does not satisfy the constraint 'true' aus.

Wichtige Methoden und Funktionen

Die Bibliothek bietet mehrere zentrale Hilfsmittel. Sehen wir uns jedes im Detail an.

1. IsExact<T, U>

Signatur: type IsExact<T, U> = ...

Funktion: Prüft, ob die Typen T und U exakt identisch sind. Gibt true zurück, wenn die Typen identisch sind, andernfalls false. Berücksichtigt alle TypeScript-Aspekte wie readonly, optional und andere Modifikatoren.

import { IsExact } from 'conditional-type-checks';

type A = { readonly x: number };
type B = { x: number };

type Test1 = IsExact<A, B>; // false – readonly unterscheidet sich
type Test2 = IsExact<string, string>; // true
type Test3 = IsExact<string | number, number | string>; // true – Reihenfolge irrelevant
type Test4 = IsExact<{ a: string }, { a: string; b?: number }>; // false

2. IsAny<T>

Signatur: type IsAny<T> = ...

Funktion: Prüft, ob T vom Typ any ist. Dies ist besonders nützlich, um versehentliche Verwendung von any in Ihrem Code zu erkennen.

import { IsAny } from 'conditional-type-checks';

type Test1 = IsAny<any>; // true
type Test2 = IsAny<string>; // false
type Test3 = IsAny<unknown>; // false

3. IsNever<T>

Signatur: type IsNever<T> = ...

Funktion: Prüft, ob T vom Typ never ist. Nützlich, um zu testen, ob bedingte Typen korrekt zu never aufgelöst werden.

import { IsNever } from 'conditional-type-checks';

type Test1 = IsNever<never>; // true
type Test2 = IsNever<string>; // false
type Test3 = IsNever<string | never>; // false – string | never wird zu string

4. IsUnknown<T>

Signatur: type IsUnknown<T> = ...

Funktion: Prüft, ob T vom Typ unknown ist. Hilfreich, um sicherzustellen, dass Ihre Typen korrekt mit unknown umgehen.

import { IsUnknown } from 'conditional-type-checks';

type Test1 = IsUnknown<unknown>; // true
type Test2 = IsUnknown<any>; // false
type Test3 = IsUnknown<string>; // false

5. IsUnion<T>

Signatur: type IsUnion<T> = ...

Funktion: Prüft, ob T ein Union-Typ ist (also aus mehr als einem Typ besteht). Nützlich für Tests mit bedingten Typen, die sich je nach Union-Mitgliedern unterschiedlich verhalten.

import { IsUnion } from 'conditional-type-checks';

type Test1 = IsUnion<string | number>; // true
type Test2 = IsUnion<string>; // false
type Test3 = IsUnion<never>; // false

6. Has<T, U>

Signatur: type Has<T, U> = ...

Funktion: Prüft, ob T den Typ U als Bestandteil enthält (z. B. ob ein Union-Typ einen bestimmten Typ enthält).

import { Has } from 'conditional-type-checks';

type Test1 = Has<string | number, string>; // true
type Test2 = Has<string | number, boolean>; // false
type Test3 = Has<{ a: string }, { a: string }>; // true

7. assert<T extends true>

Signatur: type assert<T extends true> = ...

Funktion: Eine Hilfsfunktion, die einen Kompilierfehler auslöst, wenn das übergebene Typ-Argument nicht true ist. Ideal, um Typprüfungen in Ihren Tests zu erzwingen.

import { assert, IsExact } from 'conditional-type-checks';

type MyType = { name: string; age: number };

// Diese Prüfung besteht
type Check1 = assert<IsExact<MyType, { name: string; age: number }>>;

// Diese Prüfung würde einen Fehler auslösen
// type Check2 = assert<IsExact<MyType, { name: string }>>; // Fehler!

Praktische Anwendungsbeispiele

Sehen wir uns einige reale Szenarien an, in denen conditional-type-checks besonders nützlich ist.

Beispiel 1: Testen von bedingten Typen

import { IsExact, assert } from 'conditional-type-checks';

// Ein bedingter Typ, der den Rückgabetyp einer Funktion extrahiert
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;

// Tests
type Test1 = assert<IsExact<ReturnTypeOf<() => string>, string>>;
type Test2 = assert<IsExact<ReturnTypeOf<(x: number) => boolean>, boolean>>;
type Test3 = assert<IsExact<ReturnTypeOf<string>, never>>; // string ist keine Funktion

Beispiel 2: Testen von API-Typen

import { IsExact, assert } from 'conditional-type-checks';

// Angenommen, Sie haben eine API-Funktion
declare function fetchUser(id: number): Promise<{ name: string; email: string }>;

// Testen Sie den Rückgabetyp
type FetchUserReturn = ReturnType<typeof fetchUser>;
type Test = assert<IsExact<FetchUserReturn, Promise<{ name: string; email: string }>>>;

Beispiel 3: Testen von generischen Komponenten

import { IsExact, assert } from 'conditional-type-checks';

// Eine generische Funktion
function identity<T>(value: T): T {
    return value;
}

// Testen Sie die Typinferenz
type Result1 = ReturnType<typeof identity<string>>;
type Test1 = assert<IsExact<Result1, string>>;

type Result2 = ReturnType<typeof identity<number>>;
type Test2 = assert<IsExact<Result2, number>>;

Häufige Fehler und Best Practices

Hier sind einige Tipps, um das Beste aus conditional-type-checks herauszuholen:

  • Tests immer zur Kompilierzeit ausführen: Da die Bibliothek nur Typen prüft, müssen Sie Ihre Tests nicht ausführen – ein erfolgreicher Kompilierungsvorgang bedeutet, dass alle Tests bestanden sind.
  • Kombinieren Sie mit assert: Verwenden Sie assert, um sicherzustellen, dass Ihre Prüfungen tatsächlich durchgeführt werden. Ohne assert könnte ein fehlgeschlagener Test unbemerkt bleiben.
  • Testen Sie Randfälle: Testen Sie nicht nur den Erfolgsfall, sondern auch, was passiert, wenn Typen nicht übereinstimmen. So stellen Sie sicher, dass Ihre bedingten Typen korrekt funktionieren.
  • Dokumentieren Sie Ihre Typen: Nutzen Sie die Tests als lebendige Dokumentation. Jeder Test erklärt, wie ein Typ verwendet werden soll und was erwartet wird.

Fazit

Die Bibliothek conditional-type-checks ist ein unverzichtbares Werkzeug für jeden TypeScript-Entwickler, der mit komplexen Typen arbeitet. Sie ermöglicht es, Typen statisch zu testen, Fehler frühzeitig zu erkennen und die Codequalität zu verbessern. Ob Sie an einer Bibliothek arbeiten, die komplexe generische Typen bereitstellt, oder einfach nur sicherstellen möchten, dass Ihre eigenen Typen korrekt sind – diese Bibliothek macht die Arbeit mit TypeScript-Typen zuverlässiger und angenehmer.

Probieren Sie es noch heute aus und integrieren Sie Typtests in Ihre Entwicklungsworkflow. Ihre zukünftigen Ich (und Ihre Kollegen) werden es Ihnen danken!

Empfehlungen