背景 オプショナルなプロパティを持つオブジェクトの型定義において、TypeScriptの型システムでは?演算子と| undefined型の組み合わせがよく使われる。これらには微妙な違いがある。
例えば、以下のツイートで紹介されているものが分かりやすかったので、改めて違いを整理してみる。
if I may be so bold, I'd like to propose some much-needed nomenclature pic.twitter.com/tU230ozkQV
— Colin McDonnell (@colinhacks) February 24, 2025 // name is "key optional"
type User = { name ? : string };
// name is "value optional"
type User = { name : string | undefined };
// name is "key-value optional"
type User = { name ? : string | undefined }; これら3つの型定義は、オプショナルの扱い方が異なる。それぞれの特徴と使い方を見ていく。
1. name?: string - キーがオプショナル type User = { name ? : string }; この型定義では、nameプロパティはオブジェクトに存在しなくてもよい。存在する場合、その値はstring型でなければならない。
特徴 nameプロパティがオブジェクトに含まれない場合でもエラーにならない。プロパティが存在する場合、string型である必要があるが、undefinedを明示的に代入することも可能。 プロパティが存在しない場合と、name: undefinedが設定されている場合は異なる意味を持つ。 使用例 const user1 : User = {}; // OK: nameがない
const user2 : User = { name : 'Alice' }; // OK: nameが文字列
const user3 : User = { name : undefined }; // OK: nameがundefined 注意点 nameプロパティが存在しない場合、user.nameにアクセスするとundefinedが返る。ただし、これはプロパティが「存在しない」ことを意味し、name: undefinedとは区別される。この柔軟性が便利な一方で、意図しないundefinedの代入に注意が必要だ。
2. name: string | undefined - 値がオプショナル type User = { name : string | undefined }; この型定義では、nameプロパティは必ずオブジェクトに存在しなければならない。ただし、その値はstringまたはundefinedのどちらかでよい。
特徴 nameプロパティがオブジェクトに含まれていないと型エラーになる。プロパティが存在する場合は、その値がundefinedでも問題ない。 使用例 const user1 : User = { name : 'Alice' }; // OK: nameが文字列
const user2 : User = { name : undefined }; // OK: nameがundefined
const user3 : User = {}; // エラー: nameプロパティが必須 注意点 nameプロパティは必須であるため省略できない。この型は、プロパティの存在を保証しつつ、その値が不明な場合を許容したいときに適している。
3. name?: string | undefined - キーと値の両方がオプショナル type User = { name ? : string | undefined }; この型定義は、nameプロパティがオブジェクトに存在しなくてもよく、存在する場合でもその値がstringまたはundefinedでよいという、制約が最も緩い定義である。
特徴 nameプロパティがなくてもエラーにならない。プロパティが存在する場合、その値はstringまたはundefinedのどちらでもよい。 使用例 const user1 : User = {}; // OK: nameがない
const user2 : User = { name : 'Alice' }; // OK: nameが文字列
const user3 : User = { name : undefined }; // OK: nameがundefined 注意点 この型は最も柔軟性が高く、プロパティの不在、値がstring、値がundefinedのすべてのケースを許容する。そのため、制約を最小限にしたい場合、それが適しているが、意図しない動作を見逃すリスクもある。
比較と使い分け 以下に、3つの型定義の違いを表でまとめる。
実際の使用シナリオ ユーザープロフィールで名前が任意入力の場合。入力しない選択肢を許すが、入力するなら文字列を期待する。 データベースから取得したデータで、名前が必ず記録されるが値が不明な場合にundefinedを使う。 APIレスポンスでは名前を省略できる可能性があり、存在してもundefinedである場合を許容する。 まとめ TypeScriptでオプショナルなプロパティを扱う際、?演算子はプロパティの存在をオプショナルにし、| undefinedは値のオプショナル性を表す。これらを組み合わせることで、状況に応じた柔軟な型定義が可能だ。フロントエンドエンジニアとして、APIやデータモデルの設計時にこれらの違いを理解し、データの実際の要件に合った型を選択することが重要である。例えば、プロパティが常に存在するが値が不明な場合はstring | undefinedを、プロパティ自体が省略可能な場合は?を使うべきだ。正確な型定義は、バグを減らし、コードの意図を明確に伝える助けとなる。