やあ。今日は少しマニアックだが、TypeScriptの型システムの中でも「ここを理解しているか否かで、大規模開発の堅牢性が決まる」と言っても過言ではないテーマ、`strictFunctionTypes`について深掘りしていくよ。
ドキュメントを読んでも「関数の反変性がどうのこうの」と小難しいことが書いてあって、結局何が嬉しいのかピンと来ていない人も多いんじゃないかな?いい機会だから、実務の現場で起きがちな事故を例に、なぜこの設定が「必須」なのかを紐解いていこう。
—
そもそも「関数型」の代入って何が危険なのか?
TypeScriptの型チェックにおいて、`strictFunctionTypes`がオフ(false)だと、関数の引数は「共変(covariant)」として扱われるという性質がある。これが実務でどれほど恐ろしいか、まずはコードで見てみよう。
// strictFunctionTypes: false の世界で起きる悲劇
interface Animal {
name: string;
}
interface Dog extends Animal {
bark: () => void;
}
// 犬専用のハンドラー
function handleDog(dog: Dog) {
dog.bark(); // ここでDog特有のメソッドを呼び出す
}
// 汎用的なAnimalを受け取る関数型
type AnimalHandler = (animal: Animal) => void;
// 本来なら互換性がないはずの関数を代入できてしまう!
const myHandler: AnimalHandler = handleDog;
// ここで何が起きるか?
const cat: Animal = { name: “Tama” };
myHandler(cat); // ランタイムエラー発生!catにはbarkメソッドがないため爆発する
このコード、`strictFunctionTypes`がオフだとコンパイルが通ってしまう。なぜなら、TypeScriptが「`Animal`を引数に取れるなら、`Dog`しか受け取れない関数を代入しても大丈夫でしょ?」と甘い判断を下してしまうからだ。
これを共変(Covariance)と呼ぶけれど、関数の引数においてこれはバグの温床でしかない。
strictFunctionTypesが解決する「反変性」という魔法
`strictFunctionTypes: true`に設定すると、TypeScriptは「関数の引数は反変(Contravariance)でなければならない」というルールを強制するようになる。
簡単に言えば、「より広い型を受け取れる関数は、より狭い型を受け取る関数として振る舞える」ということだ。
- `Dog`しか扱えない関数に、`Animal`を渡そうとするのは危険(NG)
- `Animal`を扱える関数なら、`Dog`を渡されても問題なく処理できる(OK)
この論理を厳格にチェックすることで、先ほどの「猫に吠えろと命令してエラーになる」ような悲劇を、コンパイル時点で防げるようになるんだ。
実務で役立つ「綺麗な」サンプルコード
実際にチーム開発で直面しやすいイベントハンドラーの型定義を例に、安全な設計を見てみよう。
// 実務で推奨される安全な型定義の設計
interface BaseEvent {
timestamp: number;
}
interface ClickEvent extends BaseEvent {
x: number;
y: number;
}
// 1. 引数を絞り込みすぎない「広めの型」を受け取る関数を定義する
type EventHandler
// 2. ClickEvent専用の処理
const handleClick: EventHandler
console.log(`Clicked at ${event.x}, ${event.y}`);
};
// 3. strictFunctionTypesがtrueなら、以下は型エラーになり守られる
// もし、BaseEventしか保証されていない場所でClickEvent用の関数を使おうとすると、
// コンパイラが「それは危険だよ」と止めてくれる
const handleGeneric: EventHandler
console.log(`Event at ${event.timestamp}`);
};
// この代入は安全(BaseEventを扱える関数は、ClickEventも扱えるため)
const safeAssign: EventHandler
ブラウザの裏側とTypeScriptの矜持
ブラウザのJavaScriptエンジンは、基本的に「動けばOK」の寛容な世界だ。しかし、僕らがTypeScriptを使う理由は、その「寛容さ」が生むランタイムエラーを、開発段階で徹底的に排除することにある。
`strictFunctionTypes`をオンにするということは、「JavaScriptの動的な性質に、数学的に正しい型安全の鎧を着せる」という行為なんだ。最初は型エラーが頻発して苦しいかもしれない。でも、そのエラーこそが、将来の君が深夜にデバッグで泣かなくて済むための「先行投資」だと思ってほしい。
今すぐやるべきこと
君のプロジェクトの`tsconfig.json`を開いて、以下の設定を確認してくれ。
{
“compilerOptions”: {
“strict”: true, // これがtrueなら自動的に含まれる
// もし個別に設定しているなら必ずtrueにする
“strictFunctionTypes”: true
}
}
もし`strict`が`false`になっているなら、まずはそこから見直そう。大規模なフロントエンド開発において、この設定をオフにする理由は、もはや「古いレガシーコードを動かすため」以外には存在しないと言っても過言じゃない。
型安全は、単なる規約じゃない。君が書いたコードが、未来の自分やチームメイトを助けるためのドキュメントであり、盾なんだ。ぜひ、この「反変性」の概念を武器にして、堅牢なアプリケーションを設計していってくれ。
何かまた壁にぶつかったら、いつでも聞きに来るといい。応援しているよ。

コメント