値が未定義であることと値がundefinedであることは厳密には動作が異なります。
上記はサバイバルTypeScriptに書かれている説明です。
理解しておくことで不必要な処理を省略できたり、予期せぬエラーに悩まされたりせずに済むと思うので頭の片隅に記憶いただけると良いかと思います!
「Optional Property」と「Union型」って?
この記事を読んでいる方にあえて説明する必要はないと思いましたが、一応「Optional Property」と「Union型」について触れておきます。
Optional Property
オブジェクトの型を定義する際に任意のプロパティであることを伝えるための記述方法です。
type User = {
name: string
tel: number
mail?: string
プロパティ名の後ろに「?」をつけることでOptional Propertyとして定義できます!
Union型
2つ以上の型を「|(パイプ記号)」でつなげて定義した記述します。
type User = {
namse: string
tel: number
mail: string | undefined
}
【本題】Optional PropertyとUnion型で定義するundefinedは違う
前提として「TypeScriptに、「exactOptionalPropertyTypes」というコンパイラオプションを設定していないとエラーが発生しませんのでご留意ください。
設定方法は、tsconfig
ファイルに対して以下の記述を追記します。
{
"compilerOptions": {
+ "exactOptionalPropertyTypes": true,
}
}
コンパイラオプションを設定した状態だと下記のuser2
でエラーが発生します。
type User = {
name: string;
gender?: 'Man' | 'Woman';
};
const user1: User = {
name: 'Tanimoto',
gender: 'Man',
};
/*
型 '{ name: string; gender: undefined; }' を、
'exactOptionalPropertyTypes: true'が指定されている型'User'に割り当てることはできません。
ターゲットのプロパティの型に'undefined' を追加することを検討してください。
プロパティ 'gender' の型に互換性がありません。
型 'undefined' を型 '"Man" | "Woman"' に割り当てることはできません。
*/
const user2: User = {
name: 'Tanimoto2',
gender: undefined,
};
const user3: User = {
name: 'Tanimoto3',
};
上記のエラーはUser
のgender
プロパティに対してundefined
を明示的に許容させることで解消できます。
type User = {
name: string;
- gender?: 'Man' | 'Woman';
+ gender?: 'Man' | 'Woman' | undefined;
};
なにが起こったのか?
gender
にundefined
を渡したuser2
はエラーが出たのに対し、
gender
を空にしたuser3
はエラーが出ませんでした。
exactOptionalPropertyTypes
を有効にしたことで、
「undefined
であること」と「プロパティが存在しないこと」の違いを厳格に区別するようになりました!
何がうれしいのか?
関数で名前付きパラメータと名前なしパラメーターの時の挙動を揃えられることが挙げられます。
type User = {
nickname?: string;
age?: number;
};
function myFunction(info?: User) {
const hoge = info?.nickname ?? '';
const hoge2 = info?.age ?? '40';
}
myFunction({ nickname: 'teru' }); // OK
myFunction({ nickname: 'teru', age: 19 }); // OK
myFunction({ nickname: 'teru', age: undefined }); // Error
age
はundefined
を許容していないのでerror
になります。
では、上記を回避するためにはどうすればいい考えてみます。
let { test } = props
myFunction({
nickname: 'teru',
...(test !== undefined ? { age: test } : {}),
});
エラーを回避しようと思うとundefined
でないことを確認する処理が必要になります…。
function myFunc2(hoge: number = 0, hoge2: string = ''): void {
console.log(hoge, hoge2);
}
myFunc2(19); // 19 ''
myFunc2(19, undefined) // 19 ''
値を渡されなかった時とundefined
の時で出力が区別されません。
少し極端な例かもしれませんが、名前付き引数と名前なし引数の場合どちらでも共通した挙動を実現するためにもOptionalPropertyにundefined
を指定しておくのが良いと思います!
まとめ
Optional PropertyとUnion型で定義するundefined
には違いがあることを知りました。
また、コンパイラのオプションを1つ指定しないだけで、実装ミスになってしまうことを再認識しました。
コンパイラオプションについても理解を深めていきたいと思います。
参考資料