Heim  >  Artikel  >  Web-Frontend  >  Umfangreiche Ausnahmen zur Kompilierungszeit in TypeScript unter Verwendung nicht konstruierbarer Typen

Umfangreiche Ausnahmen zur Kompilierungszeit in TypeScript unter Verwendung nicht konstruierbarer Typen

Susan Sarandon
Susan SarandonOriginal
2024-10-29 18:40:02972Durchsuche

Rich Compile-Time Exceptions in TypeScript Using Unconstructable Types

Das Typsystem von TypeScript ist leistungsstark, aber seine Fehlermeldungen können manchmal kryptisch und schwer zu verstehen sein. In diesem Artikel untersuchen wir ein Muster, das nicht konstruierbare Typen verwendet, um klare, beschreibende Ausnahmen zur Kompilierungszeit zu erstellen. Dieser Ansatz hilft, Laufzeitfehler zu verhindern, indem er ungültige Zustände mit hilfreichen Fehlermeldungen nicht darstellbar macht.

Das Muster: Nicht konstruierbare Typen mit benutzerdefinierten Nachrichten

Lassen Sie uns zunächst das Kernmuster aufschlüsseln:

// Create a unique symbol for our type exception
declare const TypeException: unique symbol;

// Basic type definitions
type Struct = Record<string, any>;
type Funct<T, R> = (arg: T) => R;
type Types<T> = keyof T & string;
type Sanitize<T> = T extends string ? T : never;

// The core pattern for type-level exceptions
export type Unbox<T extends Struct> = {
    [Type in Types<T>]: T[Type] extends Funct<any, infer Ret>
        ? (arg: Ret) => any
        : T[Type] extends Struct
        ? {
              [TypeException]: `Variant <${Sanitize<Type>}> is of type <Union>. Migrate logic to <None> variant to capture <${Sanitize<Type>}> types.`;
          }
        : (value: T[Type]) => any;
};

Wie es funktioniert

  1. TypeException ist ein einzigartiges Symbol, das als Sonderschlüssel für unsere Fehlermeldungen fungiert
  2. Wenn wir auf einen ungültigen Typstatus stoßen, geben wir einen Objekttyp mit einer TypeException-Eigenschaft zurück
  3. Dieser Typ ist zur Laufzeit nicht konstruierbar, was TypeScript dazu zwingt, unsere benutzerdefinierte Fehlermeldung anzuzeigen
  4. Die Fehlermeldung kann Typinformationen enthalten, die Vorlagenliterale verwenden

Beispiel 1: Variantenbehandlung mit benutzerdefinierten Fehlern

Hier ist ein Beispiel, das zeigt, wie dieses Muster mit Variantentypen verwendet wird:

type DataVariant = 
    | { type: 'text'; content: string }
    | { type: 'number'; value: number }
    | { type: 'complex'; nested: { data: string } };

type VariantHandler = Unbox<{
    text: (content: string) => void;
    number: (value: number) => void;
    complex: { // This will trigger our custom error
        [TypeException]: `Variant <complex> is of type <Union>. Migrate logic to <None> variant to capture <complex> types.`
    };
}>;

// This will show our custom error at compile time
const invalidHandler: VariantHandler = {
    text: (content) => console.log(content),
    number: (value) => console.log(value),
    complex: (nested) => console.log(nested) // Error: Type has unconstructable signature
};

Beispiel 2: Rekursive Typvalidierung

Hier ist ein komplexeres Beispiel, das zeigt, wie das Muster mit rekursiven Typen verwendet wird:

type TreeNode<T> = {
    value: T;
    children?: TreeNode<T>[];
};

type TreeHandler<T> = Unbox<{
    leaf: (value: T) => void;
    node: TreeNode<T> extends Struct
        ? {
              [TypeException]: `Cannot directly handle node type. Use leaf handler for individual values.`;
          }
        : never;
}>;

// Usage example - will show custom error
const invalidTreeHandler: TreeHandler<string> = {
    leaf: (value) => console.log(value),
    node: (node) => console.log(node) // Error: Cannot directly handle node type
};

Beispiel 3: Typstatusvalidierung

So können wir das Muster verwenden, um gültige Typzustandsübergänge zu erzwingen:

type LoadingState<T> = {
    idle: null;
    loading: null;
    error: Error;
    success: T;
};

type StateHandler<T> = Unbox<{
    idle: () => void;
    loading: () => void;
    error: (error: Error) => void;
    success: (data: T) => void;
    // Prevent direct access to state object
    state: LoadingState<T> extends Struct
        ? {
              [TypeException]: `Cannot access state directly. Use individual handlers for each state.`;
          }
        : never;
}>;

// This will trigger our custom error
const invalidStateHandler: StateHandler<string> = {
    idle: () => {},
    loading: () => {},
    error: (e) => console.error(e),
    success: (data) => console.log(data),
    state: (state) => {} // Error: Cannot access state directly
};

Wann dieses Muster verwendet werden sollte

Dieses Muster ist besonders nützlich, wenn:

  1. Sie müssen bestimmte Typkombinationen zur Kompilierungszeit verhindern
  2. Sie möchten klare, beschreibende Fehlermeldungen für Typverletzungen bereitstellen
  3. Sie erstellen komplexe Typhierarchien, in denen bestimmte Vorgänge eingeschränkt werden sollten
  4. Sie müssen Entwickler mit hilfreichen Fehlermeldungen zu korrekten Nutzungsmustern anleiten

Technische Details

Lassen Sie uns die interne Funktionsweise des Musters aufschlüsseln:

// Create a unique symbol for our type exception
declare const TypeException: unique symbol;

// Basic type definitions
type Struct = Record<string, any>;
type Funct<T, R> = (arg: T) => R;
type Types<T> = keyof T & string;
type Sanitize<T> = T extends string ? T : never;

// The core pattern for type-level exceptions
export type Unbox<T extends Struct> = {
    [Type in Types<T>]: T[Type] extends Funct<any, infer Ret>
        ? (arg: Ret) => any
        : T[Type] extends Struct
        ? {
              [TypeException]: `Variant <${Sanitize<Type>}> is of type <Union>. Migrate logic to <None> variant to capture <${Sanitize<Type>}> types.`;
          }
        : (value: T[Type]) => any;
};

Vorteile gegenüber herkömmlichen Ansätzen

  1. Fehlermeldungen löschen: Anstelle der Standardtypfehler von TypeScript erhalten Sie benutzerdefinierte Meldungen, die genau erklären, was schief gelaufen ist
  2. Sicherheit zur Kompilierungszeit: Alle Fehler werden während der Entwicklung abgefangen, nicht zur Laufzeit
  3. Selbstdokumentierend: Fehlermeldungen können Anweisungen zur Behebung des Problems enthalten
  4. Typsicher: Behält die vollständige Typsicherheit bei und bietet gleichzeitig ein besseres Entwicklererlebnis
  5. Keine Laufzeitkosten: Alle Überprüfungen erfolgen zur Kompilierungszeit ohne Laufzeitaufwand

Abschluss

Die Verwendung nicht konstruierbarer Typen mit benutzerdefinierten Fehlermeldungen ist ein leistungsstarkes Muster zum Erstellen selbstdokumentierender Typbeschränkungen. Es nutzt das Typsystem von TypeScript, um zur Kompilierungszeit klare Anleitungen bereitzustellen und Entwicklern dabei zu helfen, Probleme zu erkennen und zu beheben, bevor sie zu Laufzeitproblemen werden.

Dieses Muster ist besonders wertvoll beim Aufbau komplexer Typsysteme, bei denen bestimmte Kombinationen ungültig sein sollten. Indem wir ungültige Zustände nicht darstellbar machen und klare Fehlermeldungen bereitstellen, können wir wartbareren und entwicklerfreundlicheren TypeScript-Code erstellen.

Das obige ist der detaillierte Inhalt vonUmfangreiche Ausnahmen zur Kompilierungszeit in TypeScript unter Verwendung nicht konstruierbarer Typen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn