オプショナルプロパティを使用するときの注意点

📅 2025.06.21📝 2025.07.21

気軽に使えるオプションプロパティ

以下のような既存の型に新しいプロパティを追加したいとします。

type User = {
	id: number
	name: string;
	// avatorUrl 追加したい
}

この時に ? を使用してプロパティをオプショナルに指定できます。こうすることで、avatorUrlがいらない既存の関数やコンポーネントなどの既存のコードに影響を与えずにプロパティを追加することができます。

type User = {
	id: number;
	name: string:
	avatorUrl?: string
}

オプショナルプロパティのデメリット

値の渡し忘れ

例えばavatorUrlが実際にはUI表示に必須だった場合でも、オプショナルにしてしまうとTypeScriptの型チェックが働かないため意図しない挙動となってしまいます。

組み合わせ爆発

オプショナルプロパティが複数存在する場合、その組み合わせは指数的に増加します。
例:オプショナルが10個 → 2¹⁰ = 1024通り
すべての組み合わせを網羅するのは辛いですね

型同士の相互作用

たとえば、「プロパティ A がある場合は B も必要」といった相関関係や、相互に排他的関係があるものが多いと思います。このように1つの型の中で複数の状態は可読性が低く、バグを起こしやすいコードの原因になってしまいます。

解決策

判別可能ユニオンを使用してモデリングする

上の型同士の相互作用の項目に関連しますが、TypeScriptのメリットを享受するために有効な状態のみ表現する型をつくりましょう。これにより状態が明確になり、コードが読みやすくバグを未然に防ぐ設計になります。
以下、LLM が吐いたコード例です。

Bad

以下のような型だと全てのプロパティの状態を考慮しなくてはならず、バグが入りやすくなります。例えばisLoading がtureでdataが存在する場合、何を表示すれば良いんでしょうか...

type LoadState = {
  isLoading: boolean;
  data?: string;
  error?: string;
};

Good

判別可能ユニオンを使用して、それぞれの状態をモデリングしています。状態が明確なので、コードは書きやすくなり余計なことを考えなくて済みます。

type LoadState =
  | { status: 'loading' }
  | { status: 'success'; data: string }
  | { status: 'error'; errorMessage: string };

const Content = (state: LoadState) => {
  switch (state.status) {
    case 'loading':
      return <p>読み込み中</p>;
    case 'success':
      return <p>{state.data}</p>;
    case 'error':
      return <p>{state.errorMessage}</p>;
  }
};

オプションプロパティを使う場面

とはいえオプションプロパティを使用べき場面、使わざるを得ない場面も当然あります。

  • APIの型を表す場合
  • 巨大な設定項目
  • オプションなプロパティ(例:ミドルネームなど)

まとめ

  • オプションプロパティのデメリットを理解しておく
  • オプションプロパティを追加する前に必須にできないかを考える