TypeScriptの型ガードを理解する

型ガードというワードは知っていても、どんな時に使うのか?など理解が曖昧だったので調べ直しました。

:::note
TypeScriptを理解するための助けになれば幸いです。
:::

## 型ガードとは？
ある値に対して特定の型であるかチェックし、コードの特定の部分で変数が特定の型を持っていると保証することです。

## 型を判別する方法
型を判別する方法はいくつかあります。

- typeof演算子
- instanceof演算子
- in演算子

これらを利用してユーザー定義型の型ガードを定義します！

## 型ガードの方法
本題です。
特定の型であることを保証する方法はいろいろあります。

### `typeof`演算子
`typeof`はプリミティブ型と呼ばれる基本的な型を判定する際に利用します。

```typescript
function hoge(value: string | number) {
  if (typeof value === 'string') {
    console.log('string')
  } else {
    console.log('number')
  }
}
```

:::note warn
`null`の場合`object`を返すのでその点は注意が必要です…
:::

### `instanceof`演算子

値がクラスオブジェクトのインスタンスであるかどうかを判別して、真偽値を返します。

```typescript:TypeScript Deep Diveより
class Foo {
  foo = 123;
  common = '123';
}

class Bar {
  bar = 123;
  common = '123';
}

function doStuff(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.foo); // OK
    console.log(arg.bar); // Error!
  }
  if (arg instanceof Bar) {
    console.log(arg.foo); // Error!
    console.log(arg.bar); // OK
  }

  console.log(arg.common); // OK
  console.log(arg.foo); // Error!
  console.log(arg.bar); // Error!
}

doStuff(new Foo());
doStuff(new Bar());
```

## `in`演算子
オブジェクトが特定のプロパティを持つかを判定し、真偽値を返します。

```typescript:サバイバルTypeScriptより
interface Wizard {
  castMagic(): void;
}
interface SwordMan {
  slashSword(): void;
}

function attack(player: Wizard | SwordMan) {
  if ("castMagic" in player) {
    player.castMagic();
  } else {
    player.slashSword();
  }
}
```

:::note
少し話がずれますが、strictNullChecks[^1]を有効にしていると`== null` or `!= null`チェックをすることで`null`と`undefined`の両方を排除できます！
:::

## ユーザー定義の型ガード関数
ユーザー定義の型ガード関数を作るためには型述語（Type Predicate）を利用します。
Type predicateの宣言は戻り値がboolean型の関数に対して適用できます。

```typescript
function isDuck(animal: Animal): animal is Duck {
  return animal instanceof Duck;
}
```

`is`演算子を使用して、返り値の型を`引数名 is 型`とします。そして、関数の返り値がtrueの場合、引数名に与えられた値が型に絞り込みます。

:::note
関数の返り値の型を動的に制御し、より柔軟な型チェックができます。
:::

### 型述語の利点
 - 型ガードと組み合わせてより複雑な条件を満たすかどうかをチェックできる
 - 関数の返り値の型を制御し、型エラーを早期発見できコードの安全性を高められる

ユースケースとして、配列からnullやundefinedの型情報を削除する時にユーザー定義型ガードを利用できるとのこと。

```typescript
const array = [
  "Apple",
  null,
  "Orange",
  undefined,
  "Banana"
]

const filtedArray = array.filter((val) => val != null);
// 型：const filtedArray: (string | null | undefined)[]
```

上記を下記のように変えると型から`null`と`undefined`を排除できます！

```typescript
const filtedArray = array.filter((val): val is string => val != null);
// 型：const filtedArray: string[]
```

:::note
ただ、TypeScript5.5からは`Type Predicate`を利用しなくても型を`string[]`だと推論してくれます。詳しくは[公式のアップデート情報](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/)を確認ください！
また、TypeScript5.5のアップデート内容をざっくり把握したい場合は別記事にまとめたのでよろしければご一読ください！
:::

https://qiita.com/tanimoto-hikari/items/a70cb3b45bfa8486c503#%E5%9E%8B%E8%A1%93%E8%AA%9E%E3%82%92%E6%8E%A8%E8%AB%96%E3%81%99%E3%82%8B

## NonNullable<T>
ユーティリティ型の`NonNullable<T>`を利用することでnullとundefinedを排除できます。

```typescript:https://www.typescriptlang.org/より
type T0 = NonNullable<string | number | undefined>;
// type T0 = string | number
```

https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype

## 型ガードの結果を変数に代入できる

TypeScript4.4以降では以下のように型ガードの結果を変数に代入して再利用可能になりました。

```diff_typescript
function foo(arg: unknown) {
+ const argIsString = typeof arg === "string";
-  if (typeof arg === "string") {
+  if (argIsString) {
    // We know 'arg' is a string now.
    console.log(arg.toUpperCase());
  }
}
```

https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/#cfa-aliased-conditions

## まとめ
型安全な状態を保つ方法は色々あるのだと改めて勉強になりました。

:::note
現時点で全てを使いこなせる自信はありませんが、こんな方法があるのだと知っているだけでも違うと思うので調べ直して良かったと思います！
:::

## 参考記事

https://typescript-jp.gitbook.io/deep-dive/type-system/typeguard

https://typescriptbook.jp/reference/functions/type-guard-functions

https://typescriptbook.jp/reference/statements/control-flow-analysis-and-type-guard

https://zenn.dev/oreo2990/articles/5f75eaa285f2f9

[^1]:strictNullChecksはnullやundefinedのチェックを厳しくするコンパイラオプションです。