【実務・中級編】 strictFunctionTypesによる関数型の厳格化 – TypeScript実践ガイド

TypeScriptの闇に光を:`strictFunctionTypes`で関数型の「緩さ」を叩き直す

やあ、フロントエンド開発の現場へようこそ。

君たちが普段何気なく書いているTypeScriptのコード。「コンパイルが通るから大丈夫」と過信していないだろうか? 実はTypeScriptには、デフォルトのままでは見逃してしまう「型システムの落とし穴」がいくつか存在する。

今回は、その中でも特に重要で、かつ多くの現場で設定漏れがちな`strictFunctionTypes`について深掘りしよう。これがオフのままだと、君の書いた堅牢なはずのコードが、実行時に呆気なく崩れ去ることになる。

—

1. なぜ「関数」は危険なのか?:反変性と共変性の話

TypeScriptの型システムにおいて、関数の引数は「反変性(Contravariance)」という性質を持つ。これが、多くのエンジニアを悩ませるポイントだ。

簡単に言えば、「より広い型(親)を受け取れる関数は、より狭い型(子)を受け取れる関数として代入しても安全ではない」というルールだ。

もし`strictFunctionTypes`がオフだと、TypeScriptはこのチェックを甘くする。結果として、「本来受け取れないはずのオブジェクト」を関数に渡せてしまい、実行時にプロパティアクセスで`undefined`を叩く……という「型があるのに動かない」という悲劇が起きる。

ブラウザの裏側で起きていること

ブラウザのJavaScriptエンジン(V8など)は、TypeScriptの型情報など見ていない。TypeScriptで「通ってしまった」バグは、そのまま純粋なJavaScriptとして実行される。つまり、型チェックで弾くべきだった不正な操作が、そのままランタイムエラーとして直撃するわけだ。型定義を信じきったエンジニアが、なぜエラーが出るのかデバッグに数時間を溶かす……これが現場のリアルな泥臭さだ。

—

2. 実践:`strictFunctionTypes`の効果を体感する

まずは、この設定がオフのときに起きる「危険なコード」を見てみよう。

// 設定が false だと、本来の型安全性が損なわれる
interface Animal { name: string; }
interface Dog extends Animal { bark: () => void; }

// 犬だけを扱う関数
type DogHandler = (dog: Dog) => void;

const handleAnimal: (animal: Animal) => void = (animal) => {
console.log(animal.name);
};

// 【危険】strictFunctionTypes: false ならエラーにならない!
// 本来「犬」を期待している場所に「動物なら何でもいい関数」を代入している
const myHandler: DogHandler = handleAnimal;

// 実行時、もし Dog 型を期待して dog.bark() を呼んだら?
// animal には bark なんてメソッドはないので、即座に例外が発生する。

`strictFunctionTypes: true`にすると、TypeScriptは「`Dog`型を期待している場所に、`Animal`型を要求する関数を代入するのは危険だ(`Animal`は`Dog`のプロパティをすべて持っている保証がないから)」と、しっかりコンパイルエラーを出して止めてくれる。

—

3. 実務での設定方法:tsconfig.jsonの最適解

現場で新規プロジェクトを立ち上げる際、あるいは既存プロジェクトの負債を返済する際は、`tsconfig.json`の`compilerOptions`で以下のように設定する。

{
“compilerOptions”: {
“strict”: true, // 基本はここを true にする
// strict を true にすれば自動的に有効になるが、明示的に書くなら以下
“strictFunctionTypes”: true
}
}

「`strict: true`にすると他のエラーも一気に出てきて修正が大変だ」という声はよく聞く。だが、「後からバグを直すコスト」と「今、型定義を正しくするコスト」を天秤にかけてほしい。 圧倒的に後者の方が安い。

—

4. シニアからのアドバイス:現場を生き抜くために

`strictFunctionTypes`を有効にすると、今まで通っていたコードがコンパイルエラーになることが必ずある。それは「君のコードが悪い」のではなく、「今まで見えていなかった潜在的なバグが可視化された」という祝祭だ。

以下の手順で進めるのが、チームの混乱を最小限にするコツだ。

1. 一気にやらない: `strict: true`への移行は、まずは`strictFunctionTypes`だけでもピンポイントで有効にして、影響範囲を特定する。
2. 型ガードを導入する: コンパイルエラーになった箇所は、`if (isDog(animal))` のような型ガードで型を絞り込む。
3. Genericsを正しく使う: 関数の代入でエラーが出る場合、多くはGenericsの型定義が曖昧なことが原因だ。`T extends Animal`のように制約を見直そう。

最後に

TypeScriptは単なるツールじゃない。君が書いたコードの意図をブラウザに伝えるための、最強の「通訳」だ。その通訳に嘘をつかせないこと。それが、フロントエンドスペシャリストとしての第一歩だ。

今のプロジェクトの`tsconfig.json`を確認してみてほしい。もしここが空欄なら……さっそく明日、チームに提案してみよう。それが、より堅牢なプロダクトへの最短距離だ。

コメント

タイトルとURLをコピーしました