【入門編】関数の引数における「Exact Optional Property Types」の有効化と挙動の理解 – TypeScript コア・型システムの基礎解析バイブル

こんにちは。TypeScriptの深淵を覗き込み、その型システムの整合性に魅了されている皆さん。

今日は、TypeScriptの「型定義の厳密さ」を一段階引き上げる、少しマニアックながらも極めて重要な設定、`exactOptionalPropertyTypes` についてお話しします。

「オプショナル(省略可能)なプロパティって、結局 `undefined` を代入してもいいんじゃないの?」

そう思ったこと、一度はありませんか?実は、この設定を理解すると、JavaScript特有の曖昧さと、TypeScriptが目指す型安全性の「境界線」がはっきりと見えるようになります。一緒に深掘りしていきましょう。

—

1. そもそも「オプショナル」の何が問題なのか?

TypeScriptにおいて、プロパティや引数を `?` でオプショナルにすると、その型は自動的に `T | undefined` になります。

interface User {
name: string;
age?: number; // ここは number | undefined と同義
}

const user: User = { name: “Alice” };

これまでは、`age?: number` と書くと、以下の両方が許容されていました。

1. プロパティ自体を省略する: `{ name: “Alice” }`
2. `undefined` を明示的に渡す: `{ name: “Alice”, age: undefined }`

これ、一見同じに見えますよね? でも、実行時の挙動を考えると「プロパティが存在しない状態」と「プロパティは存在するが値が `undefined` である状態」は、`Object.keys()` や `for…in` ループなどで微妙な差を生みます。

この「曖昧さ」をコンパイラレベルで排除するのが `exactOptionalPropertyTypes: true` です。

—

2. exactOptionalPropertyTypes の魔法

このオプションを `tsconfig.json` で有効にすると、TypeScriptは「省略」と「`undefined` 代入」を明確に区別するようになります。

有効にした場合の設定例

`tsconfig.json` の `compilerOptions` に以下を追加します。

{
“compilerOptions”: {
“exactOptionalPropertyTypes”: true
}
}

何が変わるのか?

この設定を有効にすると、`age?: number` と定義した場所には、`undefined` を明示的に代入することが禁止されます。

interface User {
name: string;
age?: number;
}

// ✅ OK: プロパティを省略する(これが本来のオプショナルの姿)
const user1: User = { name: “Alice” };

// ❌ エラー!: exactOptionalPropertyTypes が有効だと、
// ‘undefined’ を明示的に代入することは許されません。
const user2: User = { name: “Alice”, age: undefined };
// Error: Type ‘undefined’ is not assignable to type ‘number’
// with ‘exactOptionalPropertyTypes’ enabled.

—

3. なぜこの厳格さが必要なのか?

「別に `undefined` を渡してもいいじゃないか」と感じるかもしれません。しかし、現実の大規模開発では、この「曖昧さ」がバグの温床になります。

具体例:設定オブジェクトの合成

例えば、複数の設定をマージする関数を考えてみましょう。

function updateConfig(config: { timeout?: number }) {
// もし config.timeout が存在しないことを期待しているコードだと…
if (“timeout” in config) {
console.log(“タイムアウトが設定されています”);
}
}

// exactOptionalPropertyTypes がないと…
updateConfig({ timeout: undefined });
// “timeout” in config は true になるため、意図しない分岐に入ってしまう!

`undefined` を渡すことを許すと、`in` 演算子や `Object.keys()` を使った判定が裏切られます。「値がないこと」と「値が未定義であること」を区別したいという設計者の意図を、コンパイラが強制的に守ってくれるようになる。これがこの設定の真価です。

—

4. 陥りやすいポイントと解決策

このオプションを有効にすると、既存のコードでいくつかエラーが出るかもしれません。特に「`undefined` を明示的に代入して初期化したい」というパターンです。

もし「どうしても `undefined` を渡したいんだ!」という場合は、型定義を明示的に `number | undefined` と書く必要があります。

interface Config {
// ? を外して、型として undefined を許容する
timeout: number | undefined;
}

const c: Config = { timeout: undefined }; // これならOK

`?` を使うのは「そのプロパティは存在しなくても良い(省略可能)」という時だけ。存在するかどうかは確定しているが値が `undefined` になる可能性があるなら、型として明示する。この使い分けが、TypeScriptの型システムにおける「プロ」の流儀です。

—

まとめ:TypeScriptを掌握するために

`exactOptionalPropertyTypes` は、ただの厳しい制限ではありません。「コードの意図」と「実行時の挙動」を一致させるための強力なツールです。

1. `?` は「省略」を意味する。
2. `undefined` 代入を許したいなら `| undefined` を明示する。

この二つを意識するだけで、あなたの書くTypeScriptコードの信頼性は劇的に向上します。最初は少し窮屈に感じるかもしれませんが、コンパイラが「曖昧なコード」を許してくれない環境に身を置くことこそが、中級者から上級者へ駆け上がるための最短ルートです。

ここをクリアしたあなたは、もうTypeScriptの型システムの「重み」を理解し始めています。自信を持って、より堅牢なコードを書いていきましょう!

何か分からないことがあれば、いつでもまた聞きに来てくださいね。応援しています。

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