気軽に使えるオプションプロパティ
以下のような既存の型に新しいプロパティを追加したいとします。
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のメリットを享受するために有効な状態のみ表現する型をつくりましょう。これにより状態が明確になり、コードが読みやすくバグを未然に防ぐ設計となります。
以下のような型だと全てのプロパティの状態を考慮しなくてはならず、バグが入りやすくなります。例えばisLoading
がtureでdata
が存在する場合、何を表示すれば良いんでしょうか...
type LoadState = {
isLoading: boolean;
data?: string;
error?: string;
};
以下のコードは判別可能ユニオンを使用して、それぞれの状態をモデリングしています。状態が明確なので、コードは書きやすくなり余計なことを考えなくて済みます。
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の型を表す場合
- 巨大な設定項目
- オプションなプロパティ(例:ミドルネームなど)
まとめ
- オプションプロパティのデメリットを理解しておく
- オプションプロパティを追加する前に必須にできないかを考える
参考