conditional-types-over-overloads について
関連する入力に応じて戻り値が変わる場合に、オーバーロードより条件型を優先するための TypeScript スキルです。ユニオンへの分配、実装方法、個別のオーバーロードが適切な場面を説明します。
主な機能
- 01関連する戻り値の条件型表現
- 02ユニオン入力への対応
- 03オーバーロードの比較
- 04実装方法の指針
活用シーン
- 01正確なジェネリック関数シグネチャの設計
- 02追加のオーバーロードなしでユニオン入力に対応
- 03オーバーロードが多い宣言の見直し
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill conditional-types-over-overloadsインストール後、Claude Code で/conditional-types-over-overloadsを入力して読み込まれたか確認します。
詳細
オーバーロードシグネチャより条件型を優先する
概要
関数の戻り値の型が入力の型に依存する場合、オーバーロードシグネチャを使いたくなるかもしれません。しかし、多くの場合、条件型のほうが優れた解決策になります。オーバーロードは個別にチェックされますが、条件型はユニオン型に対して分配され、単一の式として解析できます。そのため、ユニオン型の入力をより柔軟に扱え、保守しやすい型宣言を実現できます。
条件型とオーバーロードを使い分ける方法を理解することは、柔軟で正確な型シグネチャを書くうえで重要です。
このスキルを使う場面
- 関数の戻り値の型が入力の型に依存する
- 関数がユニオン型を受け取る
- 複数のオーバーロードシグネチャを検討している
- 入力に応じて正確な戻り値の型が必要
- 型を変換する型ユーティリティを構築している
絶対原則
オーバーロードシグネチャより条件型を優先してください。条件型はユニオン型に対して分配され、より正確で保守しやすい型宣言を実現します。
検出
次のようなパターンに注意してください。
// RED FLAGS - Overloads that should be conditionals
declare function process(x: string): string;
declare function process(x: number): number;
declare function process(x: boolean): boolean;
// Missing union case!
// Or: Multiple overloads for what should be one conditional type
declare function transform(input: A): X;
declare function transform(input: B): Y;
declare function transform(input: C): Z;
オーバーロードの問題点
オーバーロードは個別にチェックされ、ユニオン型をうまく扱えません。
// Overload approach
declare function double(x: number): number;
declare function double(x: string): string;
// Works for individual types
const n = double(12); // number
const s = double('x'); // string
// FAILS for unions
function f(x: string | number) {
return double(x);
// ~~~~~~~~~
// Error: No overload matches this call
}
条件型による解決策
条件型はユニオン型に対して自動的に分配されます。
// Conditional type approach
declare function double<T extends string | number>(
x: T
): T extends string ? string : number;
// Works for individual types
const n = double(12); // number
const s = double('x'); // string
// ALSO works for unions!
function f(x: string | number) {
return double(x); // string | number - correct!
}
分配の仕組み
T が string | number の場合、TypeScript は次のように評価します。
(string | number) extends string ? string : number
→ (string extends string ? string : number) |
(number extends string ? string : number)
→ string | number
実例
// Event handler with different payloads
type EventMap = {
click: { x: number; y: number };
keypress: { key: string; code: string };
load: { timestamp: number };
};
// BAD: Multiple overloads needed
declare function onEvent(
type: 'click',
handler: (payload: { x: number; y: number }) => void
): void;
declare function onEvent(
type: 'keypress',
handler: (payload: { key: string; code: string }) => void
): void;
// ... more overloads for each event type
// GOOD: Single conditional type
declare function onEvent<T extends keyof EventMap>(
type: T,
handler: (payload: EventMap[T]) => void
): void;
// Usage
onEvent('click', (e) => {
console.log(e.x, e.y); // e is { x: number; y: number }
});
onEvent('keypress', (e) => {
console.log(e.key); // e is { key: string; code: string }
});
実装方針
条件型の戻り値を持つ関数を実装する場合は、単一のオーバーロードを使用してください。
// External signature with conditional type
declare function double<T extends string | number>(
x: T
): T extends string ? string : number;
// Implementation with simpler type
function double(x: string | number): string | number {
return typeof x === 'string' ? x + x : x + x;
}
オーバーロードが適切な場合
次のような場合は、オーバーロードのほうがわかりやすいことがあります。
- 関数が完全に異なる 2 つの関数として動作する
- ユニオン型のケースが現実的にありそうにない
- パラメーターの数や形状が異なる
// Node's readFile - distinct use cases
// Using callbacks vs Promises are truly different patterns
readFile(path, callback); // callback version
readFile(path, options); // Promise version
// Better as separate functions or clear overloads
プレッシャーに対抗するための手順
簡潔さを理由にオーバーロードを使うよう求められた場合は、次の点を確認してください。
- ユニオン型のケースをテストする:この関数がユニオン型の入力を受け取ることはありますか?
- 分配について検討する:型をユニオン型に対して分配させたいですか?
- 保守性を確認する:後からオーバーロードを追加する必要がありますか?
- 単一のオーバーロードを使う:実装では、内部でよりシンプルな型を使います
注意すべき点
| アンチパターン | 問題点 |
|---|---|
| 関連する型に対する多数のオーバーロード | 保守が難しく、ユニオン型のケースを見落とします |
| 分配されるべきオーバーロード | ユニオン型の入力で型チェックに失敗します |
| コピー&ペーストしたオーバーロード | DRY 原則に反します |
| ユニオン型のオーバーロードがない | 利用者に型アサーションの使用を強います |
よくある正当化
「オーバーロードのほうが読みやすい」
実際には:単一の条件型のほうが、5 個以上のオーバーロードよりシンプルなことがよくあります。複雑さの原因は条件式ではなく、シグネチャの数です。
「ユニオン型を扱う必要はありません」
実際には:TypeScript ではユニオン型がよく使われます。利用者が関数にユニオン型を渡すと、オーバーロードでは対応できません。
「必要になったらユニオン型のオーバーロードを追加します」
実際には:条件型はユニオン型を自動的に処理します。ユニオン型のオーバーロードを手動で追加すると、間違いが起きやすく、記述も冗長になります。
クイックリファレンス
| アプローチ | ユニオン型への対応 | 保守性 | 使用する場面 |
|---|---|---|---|
| オーバーロード | いいえ(手動) | 低い(多数のシグネチャ) | 本当に異なる関数 |
| 条件型 | はい(自動) | 高い(単一のシグネチャ) | 関連する戻り値の型 |
| ユニオン型の戻り値 | はい | シンプル | 正確さが不要な場合 |
要点
条件型はユニオン型に対して分配され、オーバーロードより正確で保守しやすい型宣言を実現します。関数が本当に異なる操作を表す場合に限り、オーバーロードを使用してください。
参考資料
- Dan Vanderkam 著『Effective TypeScript』第 2 版
- 項目 52:オーバーロードシグネチャより条件型を優先する