0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ちょっとTypeScriptに詳しくなるTips。Optional PropertyとUnion型で定義するundefined

Posted at

値が未定義であることと値が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',
};

上記のエラーはUsergenderプロパティに対してundefinedを明示的に許容させることで解消できます。

type User = {
  name: string;
-  gender?: 'Man' | 'Woman';
+  gender?: 'Man' | 'Woman' | undefined;
};

なにが起こったのか?

genderundefinedを渡したuser2はエラーが出たのに対し、
genderを空にしたuser3はエラーが出ませんでした。

exactOptionalPropertyTypesを有効にしたことで、
undefinedであること」と「プロパティが存在しないこと」の違いを厳格に区別するようになりました!

何がうれしいのか?

関数で名前付きパラメータと名前なしパラメーターの時の挙動を揃えられることが挙げられます。

OptionalPropertyのageに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

ageundefinedを許容していないのでerrorになります。
では、上記を回避するためにはどうすればいい考えてみます。

年齢の値が親コンポーネントからpropsで渡ってきた場面を想定
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つ指定しないだけで、実装ミスになってしまうことを再認識しました。

コンパイラオプションについても理解を深めていきたいと思います。

参考資料

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?