TypeScriptのis(Type Predicate)を使ってユーザー定義型ガードを実装する

JavaScript

ユーザー定義型ガードとis(Type Predicate)について

TypeScriptには型を絞り込む『型ガード』と呼ばれる機能があります。

前回、【TypeScript】型ガード(タイプガード)の概要。typeofとinstanceofの利用例typeofinstanceofを利用した型ガードの実装方法について紹介しました。
型ガードを実現するにはtypeofinstanceofといった演算子を利用する方法以外に、関数で定義する方法があります。

型ガードを関数で定義する場合は、関数の戻り値を引数 is 型とします。
戻り値が引数 is Tの関数は「trueなら引数は型T、falseなら型Tではない」という挙動になります。

『引数 is 型』のことを型述語 (Type Predicate) と呼びます。
また、戻り値が『引数 is 型』で定義された型ガードの役割をはたす関数のことをユーザー定義型ガードと呼びます。

ユーザー定義型ガードの実装方法

以下ではユーザー定義型ガードの実装方法について紹介します。利用するTypeScriptのバージョンは4.3.5です。

今回利用するサンプルコード

今回は「文字列であれば大文字に変換、数値であれば数値を文字列にして返す」という関数を具体例として利用します。

const convert = (value: string | number): string => {
  if (typeof value === 'string') {
    return value.toUpperCase(); // string型と推論される
  } else {
    return value.toString(); // number型と推論される
  }
}

console.log(convert('hoge'))
// "HOGE"

console.log(convert(123))
// "123"

typeofによる型ガードをユーザー定義型ガードに書き直します。

ダメな方法: 戻り値をbooleanにする

型ガードの箇所であるtypeof value === "string"の部分を取り出し、isStringというユーザー定義型ガードを実装しています。

一見問題なさそうですが、typeofによる型の絞り込みが適用されるのはisStringメソッド内だけですので、isStringは型ガードとして機能しません。 その結果、convertメソッド内で型を絞り込めずコンパイルエラーになります。

const isString = (value: string | number): boolean => {
  return typeof value === "string";
}

const convert = (value: string | number): string => {
  if (isString(value)) {
    return value.toUpperCase(); // string | number 型と推論されるためエラー
  } else {
    return value.toString();
  }
}

正しい方法: 戻り値をis(Type Predicate)にする

戻り値をis(Type Predicate)にすることで型ガードとして機能するメソッド(ユーザー定義型ガード)になります。

const isString = (value: string | number): value is string => {
  return typeof value === "string";
}

const convert = (value: string | number): string => {
  if (isString(value)) {
    return value.toUpperCase(); // string型と推論される
  } else {
    return value.toString(); // number型と推論される
  }
}

console.log(convert('hoge'))
// "HOGE"

console.log(convert(123))
// "123"

参考: オブジェクトのユーザー定義型ガード

オブジェクトの型は、例えばオブジェクトのプロパティの有無をinで確認することで判定できます。

以下はオブジェクトを判定するユーザー定義型ガードの例です。

type CatType = {
  name: string
  meow: string
}

type DuckType = {
  name: string
  quack: string
}

type AnimalType = CatType | DuckType

// trueならCatType型と推定される
const isCatType = (animal: AnimalType): animal is CatType =>
  // nameプロパティとmeowプロパティを持つオブジェクトはCatType型
  'name' in animal && 'meow' in animal

const say = (animal: AnimalType): void => {
  if (isCatType(animal)) {
    console.log(animal.meow)
  } else {
    console.log(animal.quack)
  }
}

const tama: AnimalType = { name: 'Tama', meow: 'meow!!meow!!' }
const ahiru: AnimalType = { name: 'Ahiru', quack: 'quack!!quack!!') }

say(tama) // meow!!meow!!
say(ahiru) // quack!!quack!!

オブジェクトの型ガードの詳細解説は【TypeScript】typeof、inを利用したオブジェクトの型判定・型ガード(Type Guard)の方法で紹介しています。

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考記事