ジェネリクスの型引数を明示すると戻り値の型チェックが効く
function firstOf<T>(items: T[]): Tのようなジェネリック関数は、呼び出し時に指定した型引数(例: firstOf<number>)に応じて戻り値の型が決まります。その型と違う型の変数で受け取ろうとするとエラーになります。
エラーメッセージの読み方
main.ts(4,7): error TS2322: Type 'number' is not assignable to type 'string'.
main.ts- ファイル名
4- 行番号
7- 列番号 — この位置でtscが検査に失敗しました
TS2322- エラーコード — 検索するとTypeScriptの解説が見つかります
Type 'number' is not assignable to type 'string'.- 内容 — 期待していたもの、または受け付けられなかったもの
このエラーが出る典型パターン
パターン1
1 function firstOf<T>(items: T[]): T { 2 return items[0]; 3 } 4 const result: string = firstOf<number>([1, 2, 3]); ^ 5 console.log(result);
firstOf<number>を指定した時点で戻り値の型はnumberに決まります。string型の変数では受け取れません。
直し方: string を number にします。
パターン2
1 function lastOf<T>(items: T[]): T { 2 return items[items.length - 1]; 3 } 4 const result: boolean = lastOf<string>(["a", "b"]); ^ 5 console.log(result);
lastOf<string>によって戻り値はstring型に決まります。boolean型の変数へは代入できません。
直し方: boolean を string にします。
パターン3
1 function wrap<T>(value: T): T[] { 2 return [value]; 3 } 4 const result: string[] = wrap<number>(5); ^ 5 console.log(result);
wrap<number>の戻り値はnumber[]です。string[]型の変数では受け取れません。
直し方: string[] を number[] にします。
パターン4
1 function pickAt<T>(items: T[], idx: number): T { 2 return items[idx]; 3 } 4 const result: string = pickAt<number>([1, 2, 3], 0); ^ 5 console.log(result);
pickAt<number>を指定した時点で戻り値の型はnumberに決まります。
直し方: string を number にします。
パターン5
1 function pair<T>(value: T): [T, T] { 2 return [value, value]; 3 } 4 const result: [number, number] = pair<string>("a"); ^ 5 console.log(result);
pair<string>の戻り値は[string, string]です。[number, number]型の変数では受け取れません。
直し方: [number, number] を [string, string] にします。
よくある誤解
「ジェネリクスは型を問わず何でも受け入れる便利な仕組み」という理解は半分だけ正しいです。関数の中身は確かに型を問いませんが、呼び出し時に決まった型引数は、戻り値を受け取る側でも厳密にチェックされます。
実務での勘所
実務でジェネリック関数を呼ぶとき、firstOf<number>(...)のように型引数を明示的に書く場面は実はそれほど多くありません。多くの場合、tscは渡した引数の型からTを自動的に推論してくれます。firstOf([1, 2, 3])と書けば、配列の要素がnumberであることから自動的にT = numberと推論されます。型引数を明示するのは、渡す引数だけからは推論しきれない場合や、意図的に特定の型を強制したい場合に限られます。