TypeScriptで「関数を返す関数」を極める:ジェネリクスによる型伝播の魔術
こんにちは。TypeScriptの深淵へようこそ。
コードを書いているとき、「あれ、この関数を返すと型が`any`になっちゃうな…」と頭を抱えたことはありませんか?
TypeScriptは非常に賢い言語ですが、「関数を返す関数(高階関数)」という構造に直面すると、途端に型推論の糸が絡まってしまうことがあります。今日は、この絡まった糸を解きほぐし、型情報を完全に維持したまま関数を伝播させる「ジェネリクスの極意」を伝授します。
—
1. なぜ「型」が消えてしまうのか?
まずは、やってしまいがちな「型が死ぬ」パターンを見てみましょう。
function createMultiplier(factor: number) {
// ここで返す関数の型が「関数」という抽象的なものだけに限定されがち
return (num: number) => num factor;
}
const double = createMultiplier(2);
// doubleの型推論は成功するが、複雑なジェネリクスを使うと崩壊する
一見問題なさそうですが、例えば「引数に渡した型を、戻り値の関数の引数としてそのまま使いたい」といった場合、単純な定義ではTypeScriptは「どの型がどこに対応しているか」を追跡できなくなります。
—
2. ジェネリクスで「型のバケツリレー」を行う
型情報を失わないための鍵は、「ジェネリクスを関数の入り口と出口の両方に配置すること」です。
イメージとしては、「入り口で受け取った型のバケツを、関数の内部を通って、最後に戻り値の関数に手渡す」というバケツリレーを想像してください。
// Tという型パラメータを、「関数を作る関数」の引数で定義する
function createTransformer
// 返される関数の中で、そのTを再利用する
return (transform: (val: T) => T): T => {
return transform(initialValue);
};
}
// 実行!
const transformString = createTransformer(“Hello”);
// transformStringの引数は、自動的に (val: string) => string と推論される
const result = transformString((s) => s.toUpperCase());
console.log(result); // “HELLO”
ここがポイント!
- `createTransformer
` で、この関数のスコープ全体に `T` という型を宣言しました。 - `transform: (val: T) => T` とすることで、「最初に渡した型 `T` と同じ型を扱う関数しか受け付けない」という制約をコンパイラに伝えています。
- これにより、TypeScriptは「あ、これは文字列専用の変換器だな」と瞬時に理解し、強力な補完を提供してくれるのです。
—
3. よくある落とし穴:推論の限界
初心者が陥りやすいのが、「型を明示しすぎて推論を殺す」ケースです。
// 悪い例:戻り値を具体的な型で固定してしまう
function badFactory
// 戻り値の型を固定すると、Tの情報がどこかへ消えてしまう
return (n) => n + 1;
}
このように、戻り値の型を `(n: number) => number` と明示的に書いてしまうと、TypeScriptは「ああ、この関数は中身が何であれ、とにかくこの型で返すんだな」と納得してしまい、`T` との関係性を捨ててしまいます。
「推論はコンパイラに任せる」のが、TypeScriptマスターへの第一歩です。型定義はなるべく「制約」のために使い、推論を阻害する「固定」は避けるのが鉄則ですよ。
—
4. 現場で使える「型伝播」の設計思想
実際の現場では、これに「オプショナル引数」が絡むことがよくあります。
function createConfigurator
// 戻り値の関数は、一部の設定を上書きできるかもしれない
return (override?: Partial
return { …defaultConfig, …override };
};
}
const config = createConfigurator({ port: 8080, host: ‘localhost’ });
// 補完が効く!portやhostが自動的に型として認識される
const myConfig = config({ port: 3000 });
`Partial
—
最後に:型を「コードのドキュメント」にする
TypeScriptの型定義は、単なるエラーチェックの道具ではありません。「このコードがどうあるべきか」を未来の自分やチームメイトに伝える、最高のドキュメントです。
関数を返す関数を書くときは、常に「この型はどこから来て、どこへ行くのか?」を意識してみてください。ジェネリクスというバケツリレーを正しく設計できれば、あなたの書くTypeScriptコードは劇的に美しく、そして堅牢なものに変わります。
ここをクリアできれば、もうTypeScriptの基礎はバッチリです。自信を持って、次の深い領域へ進んでいきましょう!
何か詰まったら、いつでも戻ってきてくださいね。応援しています。