【TypeScript】型ガード(Type Guard)の概要、typeofとinstanceofの利用例

JavaScript

型ガード(Type Guard)について

型ガード(Type Guard)とはif文やcase文をはじめとした条件分岐で変数の型を判別し、ブロック内の変数の型を絞り込む機能のことをいいます。

型ガードを利用するとブロック内の変数の型が特定されるため、型のキャスト(型を明示して変換すること)が不要になります。

複数の型を持つ可能性のあるUnion型の変数に対して「この型の場合はこの処理をする」といったことを実現したい場合に型ガードが利用されます。

型の判別する演算子にはtypeofinstanceofがあります。typeofはプリミティブ型に、instanceofはクラスに対して使います。

型ガードの具体的な利用例

型ガードの利用例について紹介します。利用するTypeScriptのバージョンは4.3.5です。

typeofを利用した型ガード

「文字列であれば大文字に変換、数値であれば数値を文字列にして返す」という関数を作成するとします。

以下のコードは文字列の長さを条件分岐に利用して実装した例です。
この場合、value.lengthvalue.toUpperCase()を実行する際のvalueがnumber型のケースもあるためコンパイルエラーになってしまいます。

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

上記のメソッドをtypeofによる型ガードを利用して実装した例が以下になります。
typeofによってvalueの型が特定されるため意図した挙動になります。

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"

instanceofを利用した型ガード

CatクラスとDuckクラスがあったとします。
鳴き声を表すプロパティがCatクラスではmeow、Duckクラスではquackで定義されています。

class Cat {
  name: string
  meow: string
  constructor(name: string, meow: string) {
    this.name = name
    this.meow = meow
  }
}

class Duck {
  name: string
  quack: string
  constructor(name: string, quack: string) {
    this.name = name
    this.quack = quack
  }
}

Catクラスであればmeow、Duckクラスであればquackを取得するsayメソッドを実装するとします。

以下のコードはmeowプロパティの有無を条件分岐に利用して実装した例です。
この場合、animalをCatクラスかDuckクラスか特定できないため、animal.meowanimal.quackでコンパイルエラーが発生します。

const say = (animal: Cat | Duck): string => {
  if (animal.meow !== undefined) { // Cat | Duck と推論されるためエラー
    return animal.meow // Cat | Duck と推論されるためエラー
  } else {
    return animal.quack // Cat | Duck と推論されるためエラー
  }
}

上記のメソッドをinstanceofによる型ガードを利用して実装した例が以下になります。
instanceofによってanimalのクラスが特定されるため意図した挙動になります。

const say = (animal: Cat | Duck): string => {
  if (animal instanceof Cat) {
    return animal.meow // Catクラスと推定される
  } else {
    return animal.quack // Duckクラスと推定される
  }
}


const tama = new Cat('Tama', 'meow!!meow!!')
const shiro = new Duck('Shiro', 'quack!!quack!!')

console.log(say(tama))
// "meow!!meow!!"

console.log(say(shiro))
// "quack!!quack!!"

参考: nullableな変数(nullになり得る変数)を扱う場合の型の絞り込み

「文字列であれば大文字に変換、nullであれば空文字を返す」という関数を作成するとします。

nullableな変数の型の絞り込みはtypeofを利用せずともnullチェックで実現できます。
nullチェックによる型の絞り込み例は以下の通りです。

const getUpperCase = (value: string | null): string => {
  if (value != null) {
    return value.toUpperCase(); // string型と推論される
  } else {
    return ''; // null型と推論される
  }
}

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

console.log(getUpperCase(null))
// ""

さらに上記のコードは以下のように簡潔に記述できます。

const getUpperCase = (value: string | null): string => {
  return value !=null && value.toUpperCase(); || '';
}

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

console.log(getUpperCase(null))
// ""

型ガードの適用範囲について

string | numberというUnion型を具体例に、if文における型ガードの適用範囲について紹介します。

if文で型ガードを利用した場合、if節の中のみ型ガードが適用される

if節の中のみ型ガードが適用され、if文の外は型ガードの適用範囲外になります。

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

if文で型ガードを利用した場合、else節には型ガードによって必然的に決まる型が適用される

if文でtypeof value === 'string'とした場合、else節には必然的に決まるnullが変数に適用されます。

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

if文で型ガードを利用かつif節でreturnした場合、if文の外には型ガードによって必然的に決まる型が適用される

「if文でtypeof value === 'string'という条件がある、かつif節でreturnされている」場合、if文の外は必然的にnullに対する処理になるため、変数はnullとして扱われます。

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

さいごに

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

参考記事