typescript-master について
typescript-masterは、TypeScriptの型システムに関する課題を扱うスキルです。型設計や型エラーの調査、ジェネリクスや型推論に取り組む開発者向けに、プロジェクト設定の確認から型の絞り込みやテストまで段階的に支援します。
主な機能
- 01型設計とエラーの問題を分類
- 02tsconfigとプロジェクト制約を確認
- 03ジェネリクスと条件型のパターンを適用
- 04型ガードで型を絞り込む
- 05型テストとチェック用スクリプトを提供
活用シーン
- 01再利用可能で型安全なジェネリクスを設計
- 02型の不一致や推論の問題を診断
- 03TypeScriptプロジェクトの厳格な型設定を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tomlord1122/ tomtom-skill --skill typescript-masterインストール後、Claude Code で/typescript-masterを入力して読み込まれたか確認します。
同梱ファイルscripts/type-check.sh
詳細
TypeScript 言語エキスパート
ジェネリクス、条件型、マップ型、型推論、高度なパターンなど、TypeScript の型システムを習得するためのエキスパートアシスタントです。
思考プロセス
有効化されたら、TypeScript の型に関する課題を解決するため、次の構造化された思考アプローチに従います。
ステップ 1:問題の分類
目標: どのような種類の TypeScript の課題かを理解します。
確認する主な質問:
- 型設計の問題ですか?(新しい型の作成、データのモデリング)
- 型エラーの問題ですか?(デバッグ、型の不一致の修正)
- 型推論の問題ですか?(ジェネリクス、条件型)
- 型安全性の問題ですか?(実行時検証、絞り込み)
- ライブラリの型定義の問題ですか?(サードパーティの型、宣言ファイル)
判断ポイント: 適切なアプローチを選ぶために分類します。
- 型設計 → ドメインを正しくモデリングすることに注力します
- 型エラー → 型の不一致が発生した原因をたどります
- 型推論 → ジェネリクスと制約を設計します
- 型安全性 → 型ガードを使って実行時検証を追加します
- ライブラリの型定義 → @types パッケージを確認し、宣言を作成します
ステップ 2:コンテキストの分析
目標: TypeScript の環境と制約を理解します。
確認する主な質問:
- 使用している TypeScript のバージョンは何ですか?(5.0 以降には新機能があります)
- 厳格さのレベルはどの程度ですか?(strict、noUncheckedIndexedAccess など)
- モジュールシステムは何ですか?(ESM、CommonJS、バンドラー)
- 既存の型のパターンに従う必要はありますか?
実施事項:
- コンパイラーオプションについて
tsconfig.jsonを確認します package.jsonで TypeScript のバージョンを特定します- コードベース内の既存の型のパターンを確認します
バージョン固有の機能:
| 機能 | 最低バージョン |
|---|---|
| ジェネリクス | 2.0 |
| 条件型 | 2.8 |
| テンプレートリテラル型 | 4.1 |
| Const 型パラメーター | 5.0 |
| satisfies 演算子 | 4.9 |
ステップ 3:型設計の原則
目標: 解決策に TypeScript のベストプラクティスを適用します。
思考の枠組み - 基本原則:
-
注釈より推論を優先する
- 可能な場合は TypeScript に推論させます
- 公開 API では関数のパラメーターと戻り値の型を注釈します
satisfiesを使って、型を広げずにチェックします
-
anyよりunknownを使うunknownは使用前に型チェックを行うよう求めますanyはすべての型チェックを無効にします- 例外:本当に動的なシナリオ(まれです)
-
バリアントには判別可能なユニオンを使う
- リテラル型の判別子を使います
- 網羅性チェックが可能になります
- コードの内容が自己説明的になります
-
型を広げず、絞り込む
- リテラル型には
as constを優先します - 型ガードを使って型を絞り込みます
- 不必要な型アサーションは避けます
- リテラル型には
型設計に関する質問:
- 「これを記述する最小限の型は何ですか?」
- 「TypeScript に推論させられますか。それとも注釈が必要ですか?」
- 「このユニオンは網羅的ですか?」
ステップ 4:エラーの診断
目標: 型エラーを体系的に診断します。
思考の枠組み:
- 「TypeScript が期待している型と、受け取った型は何ですか?」
- 「予期しない型はどこで発生しましたか?」
- 「これは構造的な不一致ですか、それとも名目的な不一致ですか?」
よくあるエラーパターン:
| エラーパターン | 考えられる原因 | 解決策 |
|---|---|---|
| 型 'X' を型 'Y' に割り当てることはできません | プロパティの不足または型の誤り | 不足しているプロパティを追加し、型を修正します |
| プロパティが存在しません | オプショナルなプロパティまたは誤ったユニオンメンバー | 型ガードを追加し、オプショナルかどうかを確認します |
| 型 'X' に呼び出しシグネチャがありません | 関数型ではありません | 関数の型を確認します |
| 引数を 'never' に割り当てることはできません | 処理されていないユニオンを網羅しました | 不足しているケースハンドラーを追加します |
デバッグ戦略:
- 変数にカーソルを合わせて、推論された型を確認します
- 型が割り当てられた箇所までさかのぼります
- 暗黙的な
anyがないか確認します(noImplicitAny を有効にします) - 問題を切り分けるため、一時的に
// @ts-expect-errorを使います
ステップ 5:ジェネリック型の設計
目標: 再利用可能で型安全なジェネリック関数と型を設計します。
思考の枠組み:
- 「この関数を通じて、どのような型情報が受け渡されますか?」
- 「型パラメーターにはどのような制約が必要ですか?」
- 「現在よりも多くの情報を推論できますか?」
ジェネリック設計パターン:
| パターン | ユースケース | 例 |
|---|---|---|
| アイデンティティジェネリック | 入力型を保持する | <T>(x: T) => T |
| 制約付きジェネリック | 対象を一部に限定する | <T extends object> |
| マップ型 | すべてのプロパティを変換する | { [K in keyof T]: ... } |
| 条件型 | 型レベルで分岐する | T extends U ? X : Y |
| infer キーワード | ネストされた型を取り出す | T extends Promise<infer U> ? U : T |
ジェネリックのベストプラクティス:
- パラメーターには意味のある名前を付けます(単なる
TではなくTItem) - 意図が伝わる制約を追加します
- 適切な場合はデフォルト値を指定します(
<T = string>) - エッジケース(空の配列、undefined など)でテストします
ステップ 6:型の絞り込み戦略
目標: 実行時の操作で型を安全に絞り込みます。
思考の枠組み:
- 「実行時にこれが型 X だと、どうすれば分かりますか?」
- 「最も信頼できる確認方法は何ですか?」
- 「この絞り込みは TypeScript で機能しますか?」
絞り込み手法:
| 手法 | 最適な用途 | 例 |
|---|---|---|
| typeof | プリミティブ | typeof x === 'string' |
| instanceof | クラスのインスタンス | x instanceof Date |
| in 演算子 | プロパティの存在確認 | 'name' in x |
| 判別子 | タグ付きユニオン | x.type === 'success' |
| カスタムガード | 複雑なオブジェクト | function isUser(x): x is User |
| アサーション関数 | 無効な場合に例外をスローする | asserts x is User |
型ガードのパターン:
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
typeof (obj as User).id === 'string'
);
}
ステップ 7:高度な型パターン
目標: 複雑なシナリオに高度なパターンを適用します。
思考の枠組み:
- 「これを実現するユーティリティ型はありますか?」
- 「既存の型を組み合わせられますか?」
- 「型は読みやすく、保守しやすいですか?」
高度なパターン:
| パターン | 目的 |
|---|---|
| テンプレートリテラル型 | 型レベルでの文字列操作 |
| 再帰型 | ツリー構造、ネストされたオブジェクト |
| ブランド型 | プリミティブに公称型付けを適用する |
| ビルダーパターンの型 | 型情報を蓄積する |
ブランド型の例:
type UserId = string & { readonly __brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
function fetchUser(id: UserId) { /* ... */ }
// Type error: string is not UserId
fetchUser("abc"); // Error!
fetchUser(createUserId("abc")); // OK
ステップ 8:検証とテスト
目標: 型が正しく、保守しやすいことを確認します。
型テストの戦略:
@ts-expect-errorを使用して、無効なコードがエラーになることをテストします- アサーションを含む型テスト用ファイルを作成します
- 型アサーションには
Expect<Equal<A, B>>ユーティリティを使用します
型テストのパターン:
// Type tests (no runtime code)
type cases = [
Expect<Equal<ReturnType<typeof fn>, string>>,
Expect<Equal<Parameters<typeof fn>, [number, string]>>,
];
// Compile-time assertion utility
type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
保守性チェックリスト:
- 型が JSDoc で説明されています
- 複雑な型が小さな要素に分割されています
- 型名が説明的です
- エクスポートされた型に明示的な説明があります
使用方法
型チェックを実行する
bash /mnt/skills/user/typescript-master/scripts/type-check.sh [project-dir] [strict-mode]
引数:
project-dir- プロジェクトディレクトリ(デフォルト:現在のディレクトリ)strict-mode- 厳密なチェックを有効にする:true/false(デフォルト:true)
例:
bash /mnt/skills/user/typescript-master/scripts/type-check.sh
bash /mnt/skills/user/typescript-master/scripts/type-check.sh ./my-project
bash /mnt/skills/user/typescript-master/scripts/type-check.sh ./my-project false
チェック項目:
- TypeScript のコンパイル(noEmit)
- 厳密な型チェック
- 未使用の変数/インポート
- tsconfig の推奨事項
ドキュメントリソース
公式ドキュメント:
- TypeScript ハンドブック:
https://www.typescriptlang.org/docs/handbook/ - Type Challenges:
https://github.com/type-challenges/type-challenges
型システムの基礎
ユーティリティ型
// Built-in utilities
type Partial<T> = { [P in keyof T]?: T[P] };
type Required<T> = { [P in keyof T]-?: T[P] };
type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Record<K extends keyof any, T> = { [P in K]: T };
カスタムユーティリティ型
// Deep Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// Strict Omit (only known keys)
type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// Make specific keys required
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
// Nullable
type Nullable<T> = T | null;
ジェネリックのパターン
制約付きジェネリック
// Key constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Type constraint
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
infer による推論
// Extract return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Extract promise value
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// Extract array element
type ArrayElement<T> = T extends (infer E)[] ? E : never;
// Extract function parameters
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
条件型
基本的な条件型
type IsString<T> = T extends string ? true : false;
type IsArray<T> = T extends any[] ? true : false;
分配条件型
// Distributes over union
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]
// Prevent distribution
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDist<string | number>; // (string | number)[]
判別可能なユニオン
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function handleResult<T>(result: Result<T>): T | null {
if (result.success) {
return result.data; // TypeScript knows data exists
} else {
console.error(result.error); // TypeScript knows error exists
return null;
}
}
型ガード
// Custom type guard
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
typeof (obj as User).id === 'string'
);
}
// Assertion function
function assertNonNull<T>(
value: T,
message?: string
): asserts value is NonNullable<T> {
if (value === null || value === undefined) {
throw new Error(message ?? 'Value is null or undefined');
}
}
テンプレートリテラル型
type EventName = 'click' | 'focus' | 'blur';
type Handler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
type PropKey<T extends string> = `get${Capitalize<T>}` | `set${Capitalize<T>}`;
type NameProps = PropKey<'name'>; // "getName" | "setName"
推奨 tsconfig
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true,
"moduleResolution": "bundler",
"verbatimModuleSyntax": true
}
}
ユーザーに結果を提示する
TypeScript の解決策を提示する場合:
- 明示的な型注釈より型推論を優先します
anyではなくunknownを使用します- 判別可能なユニオンを活用します
- 型テストの例を示します
- TypeScript のバージョンごとの機能に言及します(5.0+:const 型パラメーター)
トラブルシューティング
「型 'X' を型 'Y' に割り当てられません」
- プロパティの不足を確認します
- null 許容の扱いを確認します
- リテラル型と拡大された型の違いを確認します
「型にプロパティが存在しません」
- アクセスする前に型ガードを追加します
- プロパティが省略可能かどうかを確認します
- ユニオン型の絞り込みを確認します
「型が複雑すぎます」
- 小さな型に分割します
- 中間的な型エイリアスを使用します
- ジェネリクスを単純化できないか検討します