Le système de types de TypeScript est puissant, mais ses messages d'erreur peuvent parfois être énigmatiques et difficiles à comprendre. Dans cet article, nous explorerons un modèle qui utilise des types non constructibles pour créer des exceptions claires et descriptives au moment de la compilation. Cette approche permet d'éviter les erreurs d'exécution en rendant les états non valides non représentables avec des messages d'erreur utiles.
Tout d'abord, décomposons le modèle de base :
// 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; };
Voici un exemple montrant comment utiliser ce modèle avec des types de variantes :
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 };
Voici un exemple plus complexe montrant comment utiliser le modèle avec des types récursifs :
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 };
Voici comment utiliser le modèle pour appliquer des transitions d'état de type valides :
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 };
Ce modèle est particulièrement utile lorsque :
Décomposons le fonctionnement interne du modèle :
// 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; };
L'utilisation de types non constructibles avec des messages d'erreur personnalisés est un modèle puissant pour créer des contraintes de type auto-documentées. Il exploite le système de types de TypeScript pour fournir des conseils clairs au moment de la compilation, aidant ainsi les développeurs à détecter et à résoudre les problèmes avant qu'ils ne deviennent des problèmes d'exécution.
Ce modèle est particulièrement utile lors de la construction de systèmes de types complexes où certaines combinaisons devraient être invalides. En rendant les états invalides irreprésentables et en fournissant des messages d'erreur clairs, nous pouvons créer un code TypeScript plus maintenable et plus convivial pour les développeurs.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!