👀

型の絞りこみ~型ガード編①~【個人学習まとめ】

に公開

型ガード

前回の記事で型の絞り込みが 2 種類あることを学習しました。
その 2 種類のうち、型ガードについて今回の記事から学習しましょう。

等価性による絞り込み

型を絞り込むために、等価演算子(===!====!=)を利用することができます。
if 文や switch 文を使った利用方法を確認してみましょう。

let test1 = Math.random() > 0.5 ? 1 : "HELLO";

if (test1 === "HELLO") {
  console.log(test1.toLowerCase());
  → hello
}

test1.substring(3);
→ プロパティ 'toLowerCase' は型 'string | number' に存在しません。プロパティ 'toLowerCase' は型 'number' に存在しません。

上記の if 文では、変数test1の値がHELLO型である場合にtrueとなりconsoleが実行されます。

この if 文のスコープ内では、変数test1string型(HELLO型から型の拡大がされる)であると TypeScript が理解しているため、toLowerCaseメソッドを呼び出すことができます。
しかし、スコープの外にあるsubstringメソッドを呼び出そうとてしても、変数test1の型はstring | numberであるため TypeScript はエラーを表示します。

もう一つ別のパターンを見てみましょう。

function testFunction(strOrNum: string | number, strOrBool: string | boolean) {
  if (strOrNum === strOrBool) {
    //どちらもstring型(かつ引数の値が一致している)の場合に真となる
    console.log(strOrNum.toLowerCase());
    console.log(strOrBool.toLowerCase());
  } else {
    //どちらかがstring型以外の場合に偽となる
    console.log("どちらかがfalseの場合");
    console.log(strOrNum);
    console.log(strOrBool);
  }
}

if 文の条件式で型の等価性を評価した後は、引数strOrNumstrOrBoolはそれぞれstring型であることを TypeScript は理解するので、toLowerCaseメソッドを呼び出すことができます。

仮にtestFunction関数のトップレベルで、それぞれの引数に対してtoLowerCaseメソッドを呼び出しても TypeScript は引数がstring型なのか、number型なのか、boolean型なのか理解できていないためエラーになります。

function testFunction(strOrNum: string | number, strOrBool: string | boolean) {
  strOrNum.toLowerCase();
  → プロパティ 'toLowerCase' は型 'string | number' に存在しません。
  プロパティ 'toLowerCase' は型 'number' に存在しません。

  strOrBool.toLowerCase();
  → プロパティ 'toLowerCase' は型 'string | boolean' に存在しません。
  プロパティ 'toLowerCase' は型 'false' に存在しません。
  if (strOrNum === strOrBool) {
    ...

typeof による絞り込み

typeof演算子は型を返す式です。
typeof

typeof ○○○とすると指定した値の型を確認することができます。

function checkValueType(value: string | number) {
  if (typeof value === "string") {
    console.log("string型です。");
    console.log(value.toLowerCase());
  } else {
    console.log(value.toFixed());
  }
}

checkValueType("MOJIRETSUDAYO");
→ mojiretsudayo
checkValueType(3.141592653589);3

上記の例ではtypeof演算子を使って引数valuestring型であるかどうかをチェックします。
if 文が真となった場合は、valueの値がstring型であることが保証され、toLowerCaseメソッドを呼び出すことができます。
if 文が偽となった場合は、引数valueの値がnumber型である。と TypeScript が推論し、toFixedメソッドを呼び出すことができます。

in 演算子による絞り込み

in演算子は、指定したオブジェクトが特定のプロパティを保持しているかどうかチェックする演算子です。特定のプロパティを保持している場合はtrueを返します。
in 演算子

"プロパティ名" in オブジェクトとするとチェックできます。

interface Circle {
  radius: number;
}

interface Square {
  width: number;
  height: number;
}

const pi = 3.14;

function getArea(shape: Circle | Square) {
  if ("radius" in shape) {
    console.log("円の面積は・・・");
    console.log(shape.radius * shape.radius * pi);
  } else {
    console.log("四角形の面積は・・・");
    console.log(shape.width * shape.height);
  }
}

const circleObject: Circle = { radius: 2 };
const squareObject: Square = { width: 3, height: 5 };
getArea(circleObject);
getArea(squareObject);

上記のgetArea関数では、引数shapeCircle型かSquare型のユニオン型として定義されています。
if 文にて"radius" in shapeと条件を指定しています。ここでは、引数shapeオブジェクトにradiusというプロパティが含まれているかチェックしています。
ここで真となれば、引数shapeの型はCircle型に絞り込まれ、円の面積の計算が実行されます。
偽となれば引数shapeの型はSquareとなるので、四角形の面積の計算が実行されます。

instateof による絞り込み

instateof演算子は、対象のオブジェクトが特定のクラスのインスタンスであるかどうか判定します。

class Bird {
  fly() {
    console.log("飛ぶ");
  }
}

class Fish {
  swim() {
    console.log("泳ぐ");
  }
}

function move(animal: Bird | Fish) {
  if (animal instanceof Bird) {
    animal.fly();
  } else {
    animal.swim();
  }
}

const suzume = new Bird();
const ayu = new Fish();
move(suzume);
→ 飛ぶ
move(ayu);
→ 泳ぐ

上記のmove関数では、引数にBird型またはFish型のインスタンスが渡されることを想定しています。
if 文にてanimal instanceof Birdと条件を指定しています。ここでは引数animalBirdのインスタンスであるかどうかチェックしています。
真となればBirdクラスのコンストラクタflyメソッドを実行することができます。
偽であれば引数animalのインスタンスはFishに絞り込まれるためswimメソッドを実行することができます。


今回はここまで、もう少し型ガードにはもう少し種類があるので続いて学習していきましょう。

Discussion