0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

型ガード

型ガードとはif(typeof value === 'string')のように型情報による条件分岐やin演算子などによってブロック内の型を絞り込む機能を指します。
この絞り込み部分をユーザー(実装者)が関数の形で作成して、型ガードとして利用するのがユーザー定義型ガード(型述語)です。

ユーザー定義型ガード(型述語)

ユーザー定義型ガードとは、条件を満たした時に関数の引数の型をユーザーが設定した型としてTypeScriptのコンパイラに扱わせる機能です。
asによる型アサーションやany型と同様にTypeScriptの型安全性を破壊しうる危険な機能ですが、確認すべき範囲がasanyに比べて明確であることから、これらの機能をやむを得ず使用する場合にはユーザー定義型ガードの使用が推奨されています。

引数 is 型によるユーザー定義型ガード

関数によって型の絞り込みを行いたい場合には、ユーザー定義型ガードが使用できます。
とくに、unknown型の値を扱うのに便利な機能となっています。

type Fish = {
  name: string
  age?: number
}

function isFish(fish: any) {
  if (fish === null) {
    return false
  }
  return (
    typeof fish.name === "string" &&
    (typeof fish.age === "number" || typeof fish.age === undefined)
  )
}

const tuna: unknown = {
  name: "tuna",
  age: 5,
}

if (isFish(tuna)) {
  console.log(tuna.age) //'tuna' is of type 'unknown'.ts(18046)
}

上のコード例のように関数による型の条件分岐を行なっても、TypeScriptのコンパイラは型の絞り込みを認識できず、コンパイルエラーとなってしまいます。

 'tuna' is of type 'unknown'.ts(18046)

そこで、関数の返り値の型にfish is Fishとつけることで、関数がtrueを返した場合にはコンパイラに引数として渡した変数の型はFish型であると認識させることができます。
ここの実装を間違えると誤った型をコンパイラが認識したままの状態になり、型安全性が損なわれるので注意が必要です。

type Fish = {
  name: string
  age?: number
}

function isFish(fish: any): fish is Fish {
  if (fish === null) {
    return false
  }
  return (
    typeof fish.name === "string" &&
    (typeof fish.age === "number" || typeof fish.age === undefined)
  )
}

const tuna: unknown = {
  name: "tuna",
  age: 5,
}

if (isFish(tuna)) {     // const tuna: unknown
  console.log(tuna.age) // const tuna: Fish
}

ユーザー定義型ガードを追加した後のプログラムでは型エラーが出なくなっており、if(isFish(tuna))に書かれている変数tunaunknownと認識されていますが、ユーザー定義型ガードを通った後のtuna変数が使用されている箇所console.log(tuna.age)では、Fish型として推論されるようになっています。

asserts 引数 is 型によるユーザー定義型ガード

関数が例外を投げて終了しない可能性がある場合には、asserts 引数 is 型の構文によるユーザー定義型ガードが使えます。
こちらは関数がtrueを返した場合に型を強制するのではなく、関数が最後の処理まで到達した場合に型を強制します。

function isNumber(value: unknown): asserts value is number {
  if (typeof value !== "number") {
    throw new Error()
  }
  return
}

const value: unknown = 314
try {
  // const value: unknown
  console.log(value) // 314

  // const value: unknown
  isNumber(value)

  //const value: number
  console.log(value.toString(16)) // "13a"
} catch (error) {
  console.log("error occurred")
}

tryブロック内ではisNumberより後ろの箇所でのvalueの型がnumber型として推論されています。

ユーザーが「isNumberが正常終了しているのならばvalueの型はnumber型である」と保証しているわけですが、ここの保証の部分、つまりユーザー定義型ガードの実装に誤りがあると型安全性は大きく損なわれてしまうため、慎重に使用する必要があります。

anyや型アサーションに比べると、ユーザーが責任を負うべき箇所が関数ないのみと明確なので、やむを得ない場合にはユーザー定義型ガードの使用を先に検討することが推奨されているわけですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?