【TypeScript】typeof、inを利用したオブジェクトの型判定・型ガード(Type Guard)の方法

JavaScript

おさらい: プリミティブ型の型ガードの実装方法

JavaScriptのデータ型にはプリミティブ型とオブジェクトがあります。
プリミティブ型の型ガードは、プリミティブ型を判別するtypeof演算子を利用することで実装できます。

以下はtypeof演算子を利用してstring型とnumber型を判別する例です。

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"

プリミティブ型の詳細解説は【JavaScript】データ型(プリミティブ型とオブジェクト)、リテラルに関する基礎知識で紹介しています。

型ガードの詳細解説は【TypeScript】型ガード(Type Guard)の概要、typeofとinstanceofの利用例で紹介しています。

オブジェクトの型ガード

以下ではオブジェクトの型ガードの実装方法について紹介します。

利用するサンプルコード

CatType型とDuckType型が存在しています。
このとき、CatType型のオブジェクトであればmeowプロパティを、DuckType型のオブジェクトであればquackプロパティを参照するメソッドを考えます。

type CatType = {
  name: string
  meow: string
}

type DuckType = {
  name: string
  quack: string
}

type AnimalType = CatType | DuckType

// tama は CatType か DuckType のどちらか
const tama: AnimalType = { name: 'Tama', meow: 'meow!!meow!!' }

// ahiru は CatType か DuckType のどちらか
const ahiru: AnimalType = { name: 'Ahiru', quack: 'quack!!quack!!') }

// 以下のような結果になるsayメソッドを作成したい
say(tama) // meow!!meow!!
say(ahiru) // quack!!quack!!

方法1: オブジェクトの各プロパティの型をtypeofで確認する

オブジェクトの各プロパティの型をtypeofで判定し、型の絞り込みをする方法です。
具体的なコードは以下の通りです。

const say = (animal: any): void => {
  if (typeof animal.name === "string" && typeof animal.meow === "string") {
    // nameとmeowプロパティの型が意図したものであるならCatType型なはず
    console.log(animal.meow)
  } else if (typeof animal.name === 'string' && typeof animal.quack === 'string') {
    // nameとquackプロパティの型が意図したものであるならDuckType型なはず
    console.log(animal.quack)
  }
}

ユーザー定義型ガードを利用して書き直すと以下のようになります。
ユーザー定義型ガードの詳細解説はTypeScriptのis(Type Predicate)を使ってユーザー定義型ガードを実装するで紹介しています。

// trueならCatType型と推定される
const isCatType = (animal: any): animal is CatType =>
  typeof animal.name === "string" && typeof animal.meow === "string"

// trueならDuckType型と推定される
const isDuckType = (animal: any): animal is DuckType =>
  typeof animal.name === 'string' && typeof animal.quack === 'string'


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

なお、sayメソッドの引数をany型以外で受けたいのであれば以下のように型アサーション(as)を利用する必要があります。

type AnimalType = CatType | DuckType

const say = (animal: AnimalType): void => {
  let cat = animal as CatType
  let duck = animal as DuckType
  if (typeof cat.name === "string" && typeof cat.meow === "string") {
    console.log(cat.meow)
  } else if ( typeof duck.name === 'string' && typeof duck.quack === 'string') {
    console.log(duck.quack)
  }
}

方法2: オブジェクトのプロパティの有無をinで確認する

JavaScriptのinを利用することでオブジェクトのプロパティの有無が確認できます。
プロパティの種類をinを活用して判別することで型ガードを実装できます。
inの詳細解説は【TypeScript】inを利用して型の絞り込みを行う方法で紹介しています。

今回の場合ですと、namemeowをプロパティに持つオブジェクトはCatType型、namequackをプロパティに持つオブジェクトはDuckType型となります。

const say = (animal: AnimalType): void => {
  if ('name' in animal && 'meow' in animal) {
    // CatType型と推定される
    console.log(animal.meow)
  } else if ('name' in animal && 'quack' in animal) {
    // DuckType型と推定される
    console.log(animal.quack)
  }
}

ユーザー定義型ガードを利用して書き直すと以下のようになります。

// trueならCatType型と推定される
const isCatType = (animal: AnimalType): animal is CatType =>
  'name' in animal && 'meow' in animal

// trueならDuckType型と推定される
const isDuckType = (animal: AnimalType): animal is DuckType =>
  'name' in animal && 'quack' in animal

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

さいごに

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

参考