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.
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; };
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 };
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 };
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 };
Dieses Muster ist besonders nützlich, wenn:
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; };
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!