零弐壱蜂

[TypeScript] オプショナルの`?`と`| undefined`の違い

7 min read

背景

オプショナルなプロパティを持つオブジェクトの型定義において、TypeScriptの型システムでは?演算子と| undefined型の組み合わせがよく使われる。これらには微妙な違いがある。

例えば、以下のツイートで紹介されているものが分かりやすかったので、改めて違いを整理してみる。

// 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つの型定義の違いを表でまとめる。

型定義プロパティの存在値の制約主なユースケース
name?: stringオプショナルstringまたはundefinedプロパティがなくてもよいが値は文字列を期待
name: string | undefined必須stringまたはundefinedプロパティは必須だが値が不明な場合を許容
name?: string | undefinedオプショナルstringまたはundefinedプロパティも値も完全にオプショナル

実際の使用シナリオ

  • name?: string: ユーザープロフィールで名前が任意入力の場合。入力しない選択肢を許すが、入力するなら文字列を期待する。
  • name: string | undefined: データベースから取得したデータで、名前が必ず記録されるが値が不明な場合にundefinedを使う。
  • name?: string | undefined: APIレスポンスで名前が省略される可能性があり、存在してもundefinedである場合を許容する。

まとめ

TypeScriptでオプショナルなプロパティを扱う際、?演算子はプロパティの存在をオプショナルにし、| undefinedは値のオプショナル性を表す。これらを組み合わせることで、状況に応じた柔軟な型定義が可能だ。フロントエンドエンジニアとして、APIやデータモデルの設計時にこれらの違いを理解し、適切な型を選択することが重要である。正確な型定義は、バグを減らし、コードの意図を明確に伝える助けとなる。

ページトップへ