【Typescript】isを使って型を絞り込む


はじめに

typescriptで開発をしていると複数の型の可能性があるものを絞り込むことは多々あると思います。
その際に使えるユーザー定義型ガードの使用方法を紹介します。
早速使用例をみてみましょう

使用例

以下のようなコードがあるとします。
一見、コンパイルが通りそうな気配がしますが、これだとエラーになります。
以下のような例だと引数foounknown型のままです。

const isBird = (animal: unknown): boolean => {
  return animal === "bird";
};

const example = (foo: unknown) => {
  if (isBird(foo)) {
    console.log(foo.length); // Error: Object is of type 'unknown'.
  }
};

example('bird')

ではどうすればよいのか?
ここで登場するのがisです。

const isBird = (animal: unknown): animal is string => {
  return animal === "bird";
};

const example = (foo: unknown) => {
  if (isBird(foo)) {
    console.log(foo.length); // 4
  }
};

example('bird')

以下の部分が変わったところですが、引数animalの型をisを使用して明示的に定義しています。
こうすることで、fooの型はstringとなり、ちゃんとコンパイルして4というlengthが取れていることがわかると思います。

const isBird = (animal: unknown): animal is string => {

ではもっと実践的な使い方を見てみましょう。
isはオブジェクトの型を絞る時にも使用できます。
以下例のようにTeacher型とStudent型の2種類型があり、
Teacher型の時はこういう処理Student型の時はこういう処理のように条件分岐をさせたいとします。

type Teacher = {
  name: string;
  age: number;
  role: string;
};

type Student = {
  name: string;
  age: number;
};

案①

typeofで判定する。
そもそもtypeofはプリミティブな型を判定するので、オブジェクトに対してtypeof使用するとobject型と認識されてしまいます

const getPersonRole = (person: Teacher | Student) => {
  if(typeof person === 'Teacher') { // Error: This condition will always return 'false' since the types '"string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"' and '"Teacher"' have no overlap.
      console.log(person.role)
  } 
}

案②

if文を使用してプロパティの有無を確かめる
これだとコンパイル時にStudent型にはroleがないのでエラーが出ます。

const getPersonRole = (person: Teacher | Student) => {
  if(!!person.role) { // Error: Property 'role' does not exist on type 'Student'.
     console.log(person.role)
  } 
}

案③

isを使用して型ガードする。
isTeacher()関数は引数personのroleがundefinedか判定してbooleanを返しています。
person is Teacherで明示的にpersonはTeacher型であることを示しています。
こうすることで無事personの型で条件分岐することに成功しています。

const isTeacher = (person: Teacher | Student): person is Teacher => {
  return (person as Teacher).role !== undefined
}

const getPersonRole = (person: Teacher | Student) => {
  if(isTeacher(person)) {
    console.log(person.role) // personがTeacer型として推論される
  } else {
    console.log(person.age) // personがStudent型として推論される
  }
}

おわりに

複数の型の可能性がある時に結構使えそうですね。