ホームページ  >  記事  >  ウェブフロントエンド  >  構築不可能な型を使用した TypeScript での豊富なコンパイル時例外

構築不可能な型を使用した TypeScript での豊富なコンパイル時例外

Susan Sarandon
Susan Sarandonオリジナル
2024-10-29 18:40:02972ブラウズ

Rich Compile-Time Exceptions in TypeScript Using Unconstructable Types

TypeScript の型システムは強力ですが、エラー メッセージが難解で理解しにくい場合があります。この記事では、構築不可能な型を使用して明確で説明的なコンパイル時例外を作成するパターンを検討します。このアプローチは、有用なエラー メッセージで無効な状態を表現できないようにすることで、実行時エラーを防ぐのに役立ちます。

パターン: カスタム メッセージを含む構築不可能な型

まず、核となるパターンを分析してみましょう:

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

仕組み

  1. TypeException は、エラー メッセージの特別なキーとして機能する一意のシンボルです
  2. 無効な型状態に遭遇した場合、TypeException プロパティを持つオブジェクト型を返します
  3. この型は実行時に構築できないため、TypeScript にカスタム エラー メッセージが表示されます
  4. エラー メッセージには、テンプレート リテラルを使用して型情報を含めることができます

例 1: カスタム エラーによるバリアントの処理

バリアント型でこのパターンを使用する方法を示す例を次に示します。

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

例 2: 再帰的な型検証

これは、再帰型でパターンを使用する方法を示す、より複雑な例です。

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

例 3: 型状態の検証

パターンを使用して有効な型の状態遷移を強制する方法は次のとおりです。

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

このパターンを使用する場合

このパターンは、次の場合に特に役立ちます。

  1. コンパイル時に特定の型の組み合わせを防ぐ必要があります
  2. 型違反について明確で説明的なエラー メッセージを提供したいと考えています
  3. 特定の操作を制限する必要がある複雑なタイプの階層を構築しています
  4. 役立つエラー メッセージを表示して、開発者を正しい使用パターンに導く必要があります

技術的な詳細

パターンが内部的にどのように機能するかを詳しく見てみましょう:

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

従来のアプローチに勝る利点

  1. エラー メッセージをクリア: TypeScript のデフォルトの型エラーの代わりに、何が問題になったのかを正確に説明するカスタム メッセージが表示されます
  2. コンパイル時の安全性: すべてのエラーは実行時ではなく開発中に捕捉されます
  3. 自己文書化: エラー メッセージには、問題の解決方法に関する指示が含まれる場合があります
  4. タイプ セーフ: 開発者エクスペリエンスを向上させながら、完全なタイプ セーフを維持します
  5. 実行時コストゼロ: すべてのチェックは実行時のオーバーヘッドなしでコンパイル時に行われます

結論

カスタム エラー メッセージで構築不可能な型を使用することは、自己文書化型制約を作成するための強力なパターンです。 TypeScript の型システムを活用してコンパイル時に明確なガイダンスを提供し、開発者が実行時の問題になる前に問題を見つけて修正できるようにします。

このパターンは、特定の組み合わせが無効である必要がある複雑な型システムを構築する場合に特に役立ちます。無効な状態を表現できないようにし、明確なエラー メッセージを提供することで、より保守しやすく開発者にとって使いやすい TypeScript コードを作成できます。

以上が構築不可能な型を使用した TypeScript での豊富なコンパイル時例外の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。