こんにちは。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の型システムの「重み」を理解し始めています。自信を持って、より堅牢なコードを書いていきましょう!
何か分からないことがあれば、いつでもまた聞きに来てくださいね。応援しています。