【TypeScript】ユーザー定義の型ガード関数で型を絞り込もう

create
2023年04月04日
update
2023年04月04日

🍩はじめに

プログラム書いてるときに「この変数は〇〇なのに TypeScript さんがいい加減に推論してくれない……」ってなることがある。
null じゃないのは分かってるのにとか、Union 型の一部なのは分かってるのにとか。

そういうときは any 型にしたり as で強制型変換したりで誤魔化すことが多いんだけど、それらよりはユーザー定義の型ガード関数を使っていこうという話。

使用上の注意

型ガード関数の正しさは TypeScript さんが保証してくれないため、間違ったコードで型を簡単に偽装できてしまう。
つまり、型ガード関数が安全かどうかはコードを書いた人の責任!

Table 1. 環境
App Version

TypeScript

4.9.5

TypeScript Plyaground

🏰型ガード関数(type guard function

ユーザー定義の型ガード関数とも呼ばれる、戻り値の型指定が x is T になっている[1]やつ。
実際に返す値は boolean 値であり、if 文の条件式に使って型を絞り込む。

Example 1. ユーザー定義の型ガード関数例
ユーザー定義の型ガード関数の宣言
// type Hoge = { hoge: string }
function isHoge(value: unknown): value is Hoge {
  if (typeof value !== 'object' || value == null) {
    return false
  }

  return 'hoge' in value && typeof value.hoge === 'string'
}
使用例
const test1 = (x: unknown) => {
  if (isHoge(x)) {
    // Hoge 型に絞り込めたのでプロパティにアクセスできる
    console.log(x.hoge)
  } else {
    console.warn(`x is not Hoge.`)
  }
}

test1({ hoge: 'hogehoge' }) // -> 'hogehoge'
test1({ hoge: 'success', foo: 'not display' }) // -> 'success'
test1(12) // -> x is not Hoge.
test1({ bar: 'bar' }) // -> x is not Hoge.
unknown 型

unknown型安全な any とも呼ばれ、他の型への代入やプロパティへのアクセスができない不明な型として扱われる。
これによってコンパイル時に誤ったプロパティへのアクセスに気づけるようになる。

unknown 型の変数は型ガードで型を絞り込んでから利用する。

🚨アサーション関数(assertion function

戻り値の型指定が asserts x is T の形になっているやつ。
上記の型ガード関数とは違い、例外が発生するか否かで判定される。
正常終了すれば以降のコードブロック内において型が絞り込まれる。

例外が発生するため、もともとエラーハンドリングを行っているコードやテストコード内でよく使われる。

Example 2. アサーション関数の例
アサーション関数の宣言
// type Hoge = { hoge: string }
function assertHoge(value: unknown): asserts value is Hoge {
  if (typeof value !== 'object' || value == null) {
    throw new Error(`"${value}" is not Hoge type.`)
  }

  if (!('hoge' in value && typeof value.hoge === 'string')) {
    throw new Error(`"${value}" does not have a hoge property.`)
  }
}
使用例
const test2 = (x: unknown) => {
  assertHoge(x)
  // Hoge 型に絞り込めたのでプロパティにアクセスできる
  console.log(x.hoge)
}

test2({ hoge: 'hogehoge' }) // -> 'hogehoge'
test2({ hoge: 'success', foo: 'not display' }) // -> 'success'
test2(12) // -> Error!
test2({ bar: 'bar' }) // -> Error!
assert モジュール

node 環境下では assert モジュールが用意されており、Null チェックや文字列パターンなどの簡単な検証であればわざわざアサーション関数を宣言しなくてもいい。

本番環境に向けては unassertassert 文を削除するのがいいっぽい。

🛠️トラブルシューティング

Assertions require every name in the call target to be declared with an explicit type annotation.

アサーション関数をアロー関数で定義してしまっているのが原因。
詳細は以下の記事通り。
📘 この直し方→Assertions require every name in the call target to be declared with an explicit type annotation. - Qiita


1. type predicate