【TypeScript】inを利用して型の絞り込みを行う方法

JavaScript

JavaScriptのin演算子について

JavaScriptのinを利用することでオブジェクトのプロパティの有無を判定できます。

const car = { make: 'Honda', model: 'Accord', year: 1998 };

console.log('make' in car);
// true

console.log('type' in car);
// false

inを利用した型の絞り込みの実装方法

『オブジェクトのプロパティの有無を判定できる』という機能を活用するとinで型の絞り込みができます。
以下ではinを利用した型の絞り込みの実装方法について紹介します。利用するTypeScriptのバージョンは4.3.5です。

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

Cat型とDuck型を用意します。鳴き声を表すプロパティがCat型ではmeow、Duck型ではquackで定義されています。
また、Cat型とDuck型のUnion型をAnimal型とします。

type Cat = {
  name: string;
  meow: string;
}

type Duck = {
  name: string;
  quack: string;
}

type Animal = Cat | Duck;

Cat型であればmeow、Duck型であればquackを取得するsayメソッドを実装する場合を考えます。

ダメな例: 型ガードにtypeofを利用する

typeofはプリミティブ型の型を判別する演算子です。
typeofはstring型やnumber型といったプリミティブ型に対しては利用できますが、TypeScriptで独自定義した型には適用できません。

以下のようにCat型かDuck型かを絞り込みたい場合にtypeofを利用すると'Cat' only refers to a type, but is being used as a value here.というエラーが出てしまいます。

const say = (animal: Animal): string => {
  if (typeof animal === Cat) { // 'Cat' only refers to a type, but is being used as a value here.
    return animal.meow // Animal型と推論されるためエラー
  } else {
    return animal.quack // Animal型と推論されるためエラー
  }
}

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

正しい方法: inを利用して型を特定する

inを利用して型に定義されているプロパティの有無をチェックすることで型の判別ができます。

今回の場合ですと、Cat型にのみ存在するmeowの有無をチェックすることでブロック内の変数の型を絞り込めます。

const say = (animal: Animal): string => {
  if ('meow' in animal) {
    return animal.meow // Cat型と推論される
  } else {
    return animal.quack // Duck型と推論される
  }
}

const tama: Cat = {
  name: 'Tama',
  meow: "meow!!meow!!"
}

const shiro: Duck = {
  name: 'Shiro',
  quack: "quack!!quack!!"
}

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

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

参考: プロパティの値で型を判断する場合、型の絞り込みはできない

Union型のメンバー(ここでいうCat型とDuck型)それぞれに対して、型の種類を表すプロパティを追加することで型の判別ができます。

たとえばkindというプロパティを追加し、Cat型であればcat、Duck型であればduckの文字列をセットします。これによりkindの値を手がかりに型の判別ができます。

ただしinを利用した時と違い、ブロック内の変数に対する型推論はAnimal型のままです。ですので、型アサーション(as)を利用して変数の型をキャストする必要があります。

type Cat = {
  kind: string; // 新しく追加
  name: string;
  meow: string;
}

type Duck = {
  kind: string; // 新しく追加
  name: string;
  quack: string;
}

type Animal = Cat | Duck;

const say = (animal: Animal): string => {
  if (animal.kind === 'cat') {
    return (animal as Cat).meow // Animal型と推論されるため、Cat型にキャストする。(キャストしないとDuck型はmeowを持たないとエラーで指摘される)
  } else {
    return (animal as Duck).quack // Animal型と推論されるため、Duck型にキャストする(キャストしないとCat型はquackを持たないとエラーで指摘される)
  }
}

const tama: Cat = {
  kind: 'cat',
  name: 'Tama',
  meow: "meow!!meow!!"
}

const shiro: Duck = {
  kind: 'duck',
  name: 'Shiro',
  quack: "quack!!quack!!"
}

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

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

なお、型アサーションの詳細解説はTypeScriptのas(型アサーション)の概要と使いどころで紹介しています。

さいごに

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

参考記事