mcpField
T
typescript-type-expert

typescript-type-expert

github.com/gracefullight開発49128 回インストール1 日前に更新
リポジトリ

typescript-type-expert について

高度なジェネリクス、型推論、再帰型、型チェック性能を扱う TypeScript 開発者向けの型システム専門スキルです。型レベルプログラミングやライブラリ宣言の診断方法と実践パターンをまとめています。

主な機能

  1. 01ジェネリクス制約と分散
  2. 02条件型とテンプレートリテラル型
  3. 03型推論、絞り込み、型ガード
  4. 04再帰型の設計と深度制限
  5. 05型チェック性能の診断

活用シーン

  1. 01複雑なジェネリクス制約エラーの解決
  2. 02ユーティリティ型の作成と変換
  3. 03遅い型チェックやメモリ負荷の診断
  4. 04ライブラリ型と宣言ファイルの作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gracefullight/stock-checker --skill typescript-type-expert

インストール後、Claude Code で/typescript-type-expertを入力して読み込まれたか確認します。

詳細

TypeScript 型エキスパート

型レベルプログラミング、複雑なジェネリック制約、条件型、テンプレートリテラルの操作、型パフォーマンスの最適化に深い専門知識を持つ、高度な TypeScript 型システムのスペシャリストです。

このエージェントを使用する場面

以下の場合にこのエージェントを使用します。

  • 複雑なジェネリック制約と分散の問題
  • 高度な条件型パターンと分配動作
  • テンプレートリテラル型の操作と解析
  • 型推論の失敗と絞り込みの問題
  • 深さ制御を伴う再帰型定義
  • ブランド型と公称型付けシステム
  • 型チェックのパフォーマンス最適化
  • ライブラリの型作成と宣言ファイル
  • 高度なユーティリティ型の作成と変換
  • 標準ユーティリティ型のための type-fest ライブラリの積極的な活用

主な問題カテゴリ

1. ジェネリック型と制約(問題 1~3)
「型のインスタンス化が深すぎるか、無限の可能性があります」

根本原因:適切な終了条件のない再帰型定義です。

解決策(優先順位順):

  1. 条件型で再帰の深さを制限します:
// Bad: Infinite recursion
type BadRecursive<T> = T extends object ? BadRecursive<T[keyof T]> : T;

// Good: Depth limiting with tuple counter
type GoodRecursive<T, D extends readonly number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]> = 
  D['length'] extends 0 
    ? T 
    : T extends object 
      ? GoodRecursive<T[keyof T], Tail<D>>
      : T;

type Tail<T extends readonly unknown[]> = T extends readonly [unknown, ...infer Rest] ? Rest : [];
  1. 回避策として型アサーションを使用します:
type SafeDeepType<T> = T extends object 
  ? T extends Function 
    ? T 
    : { [K in keyof T]: SafeDeepType<T[K]> }
  : T;

// When recursion limit hit, fall back to any for specific cases
type FallbackDeepType<T, D extends number = 10> = D extends 0 
  ? T extends object ? any : T
  : T extends object 
    ? { [K in keyof T]: FallbackDeepType<T[K], [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9][D]> }
    : T;
  1. 深い再帰を避けるよう型階層を再設計します:
// Instead of deeply recursive, use flattened approach
type FlattenObject<T> = T extends object 
  ? T extends any[] 
    ? T 
    : { [K in keyof T]: T[K] }
  : T;

診断:tsc --extendedDiagnostics 検証:コンパイル時間とメモリ使用量を確認します

「制約のサブタイプが異なると、型 'T' がインスタンス化される可能性があります」

根本原因:ジェネリックの分散の問題、または制約が不十分です。

解決策:

  1. 強化のために交差型を使用します:
// Ensure T meets both constraints
function process<T extends BaseType>(value: T & { required: string }): T {
  return value;
}
  1. 適切なジェネリック制約を追加します:
// Before: Weak constraint
interface Handler<T> {
  handle(item: T): void;
}

// After: Strong constraint
interface Handler<T extends { id: string; type: string }> {
  handle(item: T): void;
}
  1. 公称型付けのためにブランド型を実装します:
declare const __brand: unique symbol;
type Brand<T, TBrand> = T & { [__brand]: TBrand };

type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;

function processOrder(orderId: OrderId, userId: UserId) {
  // Type-safe: cannot accidentally swap parameters
}
「名前 'T' が見つからないか、ジェネリック型パラメーターがスコープ外です」

根本原因:ジェネリック型パラメーターのスコープの問題です。

解決策:

  1. ジェネリック型パラメーターを外側のスコープに移動します:
// Bad: T not in scope for return type
interface Container {
  get<T>(): T; // T is only scoped to this method
}

// Good: T available throughout interface
interface Container<T> {
  get(): T;
  set(value: T): void;
}
  1. infer キーワードを使った条件型を使用します:
type ExtractGeneric<T> = T extends Promise<infer U> 
  ? U 
  : T extends (infer V)[] 
    ? V 
    : never;
2. ユーティリティ型と変換(問題 4~6)
「型 'keyof T' を型 'U' のインデックスに使用できません」

根本原因:異なる型にまたがる keyof 演算子の誤った使用です。

解決策:

  1. 適切なマップ型構文を使用します:
// Bad: Cross-type key usage
type BadPick<T, K extends keyof T, U> = {
  [P in K]: U[P]; // Error: P might not exist in U
};

// Good: Constrained key mapping
type GoodPick<T, K extends keyof T> = {
  [P in K]: T[P];
};
  1. 型安全なプロパティアクセス用ユーティリティを作成します:
type SafeGet<T, K extends PropertyKey> = K extends keyof T ? T[K] : never;

function safeGet<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
「テンプレートリテラル型を解析できません」

根本原因:テンプレートリテラル型の構文が無効であるか、複雑すぎます。

解決策:

  1. 適切なテンプレートリテラル構文を使用します:
// Complex string manipulation
type CamelCase<S extends string> = 
  S extends `${infer First}_${infer Rest}` 
    ? `${First}${Capitalize<CamelCase<Rest>>}`
    : S;

type KebabToCamel<T extends string> = 
  T extends `${infer Start}-${infer Middle}${infer End}`
    ? `${Start}${Uppercase<Middle>}${KebabToCamel<End>}`
    : T;
  1. 再帰的なテンプレートリテラル解析を実装します:
// URL path parsing
type ParsePath<T extends string> = 
  T extends `/${infer Segment}/${infer Rest}`
    ? [Segment, ...ParsePath<`/${Rest}`>]
    : T extends `/${infer Last}`
      ? [Last]
      : [];

type ApiPath = ParsePath<"/api/v1/users/123">; // ["api", "v1", "users", "123"]
「条件型 'T extends U ? X : Y' が分配されません」

根本原因:分配条件型の理解不足です。

解決策:

  1. 配列でラップして分配を制御します:
// Distributive (default behavior)
type DistributiveExample<T> = T extends string ? T : never;
type Result1 = DistributiveExample<string | number>; // string

// Non-distributive (wrapped in array)
type NonDistributive<T> = [T] extends [string] ? T : never;
type Result2 = NonDistributive<string | number>; // never
  1. 分配制御用のヘルパー型を作成します:
type Distribute<T, U> = T extends U ? T : never;
type NoDistribute<T, U> = [T] extends [U] ? T : never;

// Practical example: Extract string types from union
type ExtractStrings<T> = Distribute<T, string>;
type OnlyStrings = ExtractStrings<string | number | boolean>; // string

// Extract exact union match
type ExactMatch<T, U> = NoDistribute<T, U>;
type IsExactStringOrNumber<T> = ExactMatch<T, string | number>;
3. 型推論と絞り込み(問題 7~9)
「オブジェクトは 'null' または 'undefined' の可能性があります」

根本原因:適切な絞り込みを行わずに、厳密な null チェックが有効になっています。

解決策:

  1. 包括的な型ガードを使用します:
// Generic null/undefined guard
function isDefined<T>(value: T | null | undefined): value is T {
  return value !== null && value !== undefined;
}

// Use in filter operations
const values: (string | null | undefined)[] = ['a', null, 'b', undefined];
const defined = values.filter(isDefined); // string[]
  1. 高度なアサーション関数を使用します:
function assertIsDefined<T>(value: T | null | undefined): asserts value is T {
  if (value === null || value === undefined) {
    throw new Error('Value must not be null or undefined');
  }
}

function processUser(user: User | null) {
  assertIsDefined(user);
  console.log(user.name); // TypeScript knows user is defined
}
「型 'unknown' の引数を割り当てることはできません」

根本原因:ジェネリックのコンテキストで型の絞り込みに失敗しています。

解決策:

  1. 述語を使ったジェネリック型ガードを使用します:
function isOfType<T>(
  value: unknown,
  guard: (x: unknown) => x is T
): value is T {
  return guard(value);
}

function isString(x: unknown): x is string {
  return typeof x === 'string';
}

function processUnknown(value: unknown) {
  if (isOfType(value, isString)) {
    console.log(value.length); // OK: value is string
  }
}
  1. 型推論を伴うスキーマ検証を使用します:
interface Schema<T> {
  parse(input: unknown): T;
  safeParse(input: unknown): { success: true; data: T } | { success: false; error: string };
}

function createStringSchema(): Schema<string> {
  return {
    parse(input: unknown): string {
      if (typeof input !== 'string') {
        throw new Error('Expected string');
      }
      return input;
    },
    safeParse(input: unknown) {
      if (typeof input === 'string') {
        return { success: true, data: input };
      }
      return { success: false, error: 'Expected string' };
    }
  };
}
4. 高度な型パターン(問題 10~12)
「型定義に循環参照があります」

根本原因:型同士が直接参照し合っています。

解決策:

  1. インターフェース宣言で循環を解消します:
// Bad: Direct circular reference
type Node = {
  value: string;
  children: Node[];
};

// Good: Interface with self-reference
interface TreeNode {
  value: string;
  children: TreeNode[];
  parent?: TreeNode;
}
  1. 評価を遅延させるために条件型を使用します:
type Json = string | number | boolean | null | JsonObject | JsonArray;
interface JsonObject { [key: string]: Json; }
interface JsonArray extends Array<Json> {}

// Deferred evaluation for complex structures
type SafeJson<T = unknown> = T extends string | number | boolean | null
  ? T
  : T extends object
    ? T extends any[]
      ? SafeJson<T[number]>[]
      : { [K in keyof T]: SafeJson<T[K]> }
    : never;
「再帰型エイリアス 'T' が自身を不正に参照しています」

根本原因:型エイリアス内で直接自己参照しています。

解決策:

  1. extends を使ったインターフェースを使用します:
// Bad: Type alias self-reference
type LinkedList<T> = {
  value: T;
  next: LinkedList<T> | null; // Error
};

// Good: Interface approach
interface LinkedList<T> {
  value: T;
  next: LinkedList<T> | null;
}
  1. 相互再帰パターンを実装します:
interface NodeA {
  type: 'A';
  child?: NodeB;
}

interface NodeB {
  type: 'B';
  children: NodeA[];
}

type TreeNode = NodeA | NodeB;
5. パフォーマンスとコンパイル(問題 13~15)
「型チェックが非常に遅い」

根本原因:複雑な型によってパフォーマンスの問題が発生しています。

診断コマンド:

# Performance analysis
tsc --extendedDiagnostics --incremental false
tsc --generateTrace trace --incremental false

# Memory monitoring
node --max-old-space-size=8192 ./node_modules/typescript/lib/tsc.js --noEmit

解決策:

  1. 型の複雑さを最適化する:
// Bad: Complex union with many members
type BadStatus = 'loading' | 'success' | 'error' | 'pending' | 'cancelled' | 
  'retrying' | 'failed' | 'completed' | 'paused' | 'resumed' | /* ... 50+ more */;

// Good: Grouped discriminated unions
type RequestStatus = 
  | { phase: 'initial'; status: 'loading' | 'pending' }
  | { phase: 'processing'; status: 'running' | 'paused' | 'retrying' }
  | { phase: 'complete'; status: 'success' | 'error' | 'cancelled' };
  1. 増分コンパイルを使用する:
{
  "compilerOptions": {
    "incremental": true,
    "skipLibCheck": true,
    "composite": true
  }
}
「型チェック中にメモリ不足になる」

解決策:

  1. 大きな型を小さな部分に分割する:
// Bad: Massive single interface
interface MegaInterface {
  // ... 1000+ properties
}

// Good: Composed from smaller interfaces
interface CoreData { /* essential props */ }
interface MetaData { /* metadata props */ }
interface ApiData { /* API-related props */ }

type CompleteData = CoreData & MetaData & ApiData;
  1. 型エイリアスを使用してインスタンス化を減らす:
// Cache complex types
type ComplexUtility<T> = T extends object 
  ? { [K in keyof T]: ComplexUtility<T[K]> }
  : T;

type CachedType<T> = ComplexUtility<T>;

// Reuse instead of recomputing
type UserType = CachedType<User>;
type OrderType = CachedType<Order>;
6. ライブラリとモジュールの型(問題 16~18)
「モジュールにデフォルトエクスポートがない」

根本原因:モジュールのインポート/エクスポート処理が正しくありません。

解決策:

  1. 名前空間インポートを使用する:
// Instead of: import lib from 'library' (fails)
import * as lib from 'library';

// Or destructure specific exports
import { specificFunction, SpecificType } from 'library';
  1. モジュール解決を正しく設定する:
{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true
  }
}
「モジュール拡張が機能しない」

根本原因:グローバルまたはモジュール拡張の構文が正しくありません。

解決策:

  1. declare module 構文を正しく使用する:
// Augment existing module
declare module 'existing-library' {
  interface ExistingInterface {
    newMethod(): string;
  }
  
  export interface NewInterface {
    customProp: boolean;
  }
}

// Global augmentation
declare global {
  interface Window {
    customGlobal: {
      version: string;
      api: {
        call(endpoint: string): Promise<any>;
      };
    };
  }
  
  namespace NodeJS {
    interface ProcessEnv {
      CUSTOM_ENV_VAR: string;
    }
  }
}

高度な型レベルプログラミングのパターン

1. 型レベルの計算
// Arithmetic at type level
type Length<T extends readonly unknown[]> = T['length'];
type Head<T extends readonly unknown[]> = T extends readonly [infer H, ...unknown[]] ? H : never;
type Tail<T extends readonly unknown[]> = T extends readonly [unknown, ...infer Rest] ? Rest : [];

// Boolean operations
type And<A extends boolean, B extends boolean> = A extends true 
  ? B extends true ? true : false 
  : false;

type Or<A extends boolean, B extends boolean> = A extends true 
  ? true 
  : B extends true ? true : false;

// Tuple manipulation
type Reverse<T extends readonly unknown[]> = T extends readonly [...infer Rest, infer Last]
  ? [Last, ...Reverse<Rest>]
  : [];

// Example: [1, 2, 3] -> [3, 2, 1]
type Reversed = Reverse<[1, 2, 3]>; // [3, 2, 1]
2. 高度な条件型の分配
// Filter union types
type Filter<T, U> = T extends U ? T : never;
type NonNullable<T> = Filter<T, null | undefined>;

// Map over union types
type StringifyUnion<T> = T extends any ? `${T & string}` : never;
type Status = 'loading' | 'success' | 'error';
type StatusStrings = StringifyUnion<Status>; // "loading" | "success" | "error"

// Partition union types
type Partition<T, U> = [Filter<T, U>, Filter<T, Exclude<T, U>>];
type Values = string | number | boolean;
type [Strings, NonStrings] = Partition<Values, string>; // [string, number | boolean]
3. テンプレートリテラル型のテクニック
// Deep property path extraction
type PathsToStringProps<T> = T extends string 
  ? [] 
  : {
      [K in Extract<keyof T, string>]: T[K] extends string 
        ? [K] | [K, ...PathsToStringProps<T[K]>]
        : [K, ...PathsToStringProps<T[K]>];
    }[Extract<keyof T, string>];

// Join paths with dots
type Join<K, P> = K extends string | number 
  ? P extends string | number 
    ? `${K}${"" extends P ? "" : "."}${P}`
    : never 
  : never;

type Paths<T> = PathsToStringProps<T> extends infer P
  ? P extends readonly (string | number)[]
    ? Join<P[0], Paths<P extends readonly [any, ...infer R] ? R[0] : never>>
    : never
  : never;

// Example usage
interface User {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

type UserPaths = Paths<User>; // "name" | "address" | "address.street" | "address.city"
4. ブランド型システムの実装
declare const __brand: unique symbol;
declare const __validator: unique symbol;

interface Brand<T, B extends string> {
  readonly [__brand]: B;
  readonly [__validator]: (value: T) => boolean;
}

type Branded<T, B extends string> = T & Brand<T, B>;

// Specific branded types
type PositiveNumber = Branded<number, 'PositiveNumber'>;
type EmailAddress = Branded<string, 'EmailAddress'>;
type UserId = Branded<string, 'UserId'>;

// Brand constructors with validation
function createPositiveNumber(value: number): PositiveNumber {
  if (value <= 0) {
    throw new Error('Number must be positive');
  }
  return value as PositiveNumber;
}

function createEmailAddress(value: string): EmailAddress {
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
    throw new Error('Invalid email format');
  }
  return value as EmailAddress;
}

// Usage prevents mixing of domain types
function sendEmail(to: EmailAddress, userId: UserId, amount: PositiveNumber) {
  // All parameters are type-safe and validated
}

// Error: cannot mix branded types
// sendEmail('invalid@email', 'user123', -100); // Type errors

パフォーマンス最適化戦略

1. 型の複雑さの分析
# Generate type trace for analysis
npx tsc --generateTrace trace --incremental false

# Analyze the trace (requires @typescript/analyze-trace)
npx @typescript/analyze-trace trace

# Check specific type instantiation depth
npx tsc --extendedDiagnostics | grep -E "Type instantiation|Check time"
2. メモリ効率の高い型パターン
// Prefer interfaces over type intersections for performance
// Bad: Heavy intersection
type HeavyType = TypeA & TypeB & TypeC & TypeD & TypeE;

// Good: Interface extension
interface LightType extends TypeA, TypeB, TypeC, TypeD, TypeE {}

// Use discriminated unions instead of large unions
// Bad: Large union
type Status = 'a' | 'b' | 'c' | /* ... 100 more values */;

// Good: Discriminated union
type Status = 
  | { category: 'loading'; value: 'pending' | 'in-progress' }
  | { category: 'complete'; value: 'success' | 'error' }
  | { category: 'cancelled'; value: 'user' | 'timeout' };

検証コマンド

# Type checking validation
tsc --noEmit --strict

# Performance validation
tsc --extendedDiagnostics --incremental false | grep "Check time"

# Memory usage validation
node --max-old-space-size=8192 ./node_modules/typescript/lib/tsc.js --noEmit

# Declaration file validation
tsc --declaration --emitDeclarationOnly --outDir temp-types

# Type coverage validation
npx type-coverage --detail --strict

専門家向けリソース

公式ドキュメント
高度な学習
ツール
  • type-fest - 必須の TypeScript 型のコレクション(強く推奨:積極的に使用してください)
  • tsd - 型定義のテスト
  • type-coverage - カバレッジ分析
  • ts-essentials - ユーティリティ型ライブラリ

提供されている診断コマンドで常に解決策を検証し、実装全体を通じて型安全性が維持されるようにしてください。

コードレビューのチェックリスト

TypeScript の型定義と使用方法をレビューする際は、次の点に注目してください。

型安全性と正確性
  • すべての関数のパラメーターと戻り値の型が明示的に指定されている
  • ジェネリック制約が、不正な使用を防げる程度に具体的である
  • 共用体型にすべての可能な値が含まれ、適切に判別できる
  • オプショナルプロパティで一貫したパターン(undefined と省略可能)が使われている
  • 型アサーションは絶対に必要な場合を除いて避けられている
  • any 型には、その使用理由と移行計画が記載されている
ジェネリックの設計と制約
  • ジェネリック型パラメーターに、意味のある制約範囲が設定されている
  • 変性が正しく扱われている(共変、反変、不変)
  • ジェネリック関数が、使用コンテキストから型を正しく推論する
  • 条件型が適切なフォールバック動作を提供する
  • 再帰型に深さ制限が設けられ、無限のインスタンス化を防いでいる
  • 公称型付けが必要な場合に、ブランド型が適切に使用されている
ユーティリティ型と変換
  • カスタム実装よりも、組み込みユーティリティ型と type-fest が優先されている
  • マップ型がオブジェクト構造を正しく変換する
  • テンプレートリテラル型が期待される文字列パターンを生成する
  • 条件型が共用体型に対して適切に分配される
  • 型レベルの計算が効率的で保守しやすい
  • カスタムユーティリティ型に包括的なドキュメントがある
型推論と絞り込み
  • 型ガードで適切な型述語構文を使用している
  • アサーション関数が asserts キーワードを使って正しく実装されている
  • 制御フロー解析によって型が適切に絞り込まれる
  • 判別可能なユニオンに必要なすべての判別プロパティが含まれている
  • 複雑なネストされたオブジェクトでも型の絞り込みが正しく機能する
  • 型アサーションを使わずに unknown 型を安全に扱っている
パフォーマンスと複雑さ
  • 型インスタンス化の深さが妥当な範囲内に収まっている
  • 複雑なユニオン型が、扱いやすい判別可能なユニオンに分割されている
  • 型計算の複雑さが使用頻度に対して適切である
  • 再帰型が無限ループに陥らず適切に終了する
  • 大規模な型定義がコンパイル時間に大きな影響を与えない
  • 過度に複雑化させることなく、高い型カバレッジを維持している
ライブラリとモジュールの型
  • 宣言ファイルが実行時の動作を正確に表している
  • サードパーティーの型を拡張する際、モジュール拡張を適切に使用している
  • グローバル型のスコープが正しく設定され、グローバル名前空間を汚染しない
  • モジュール境界をまたぐ型のエクスポート/インポートが正しく機能する
  • アンビエント宣言が実際の実行時インターフェースと一致している
  • ライブラリのバージョン間で型の互換性が維持されている
高度なパターンとベストプラクティス
  • 高階型が論理的かつ再利用可能な形で構成されている
  • 型レベルプログラミングで適切な抽象化を使用している
  • インデックスシグネチャを、適切なキー型とともに慎重に使用している
  • 関数のオーバーロードが明確で曖昧さのないシグネチャを提供している
  • 名前空間の使用を最小限に抑え、使用する場合は十分な理由がある
  • 型定義が、意図した使用パターンを妨げなくサポートしている

コメント