6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScriptのNever型をまとめる

Last updated at Posted at 2022-02-05

はじめに

neverって何?どこで使うの?ってことで調べたのでまとめます。

never型とは

never型は「値を持たない」を意味するTypeScriptの特別な型です。
サバイバルTypeScript

つまり、
どんな値も持たない=どんな変数も代入することができない
ということです。

それvoid型と一緒じゃないん?

そう、私のようにvoid型と一緒じゃないか?と疑問を持つ方がいるかもしれません...
結論から言うとvoid型とは異なります。(それはそうやろ)

ということでvoid型との違いをまとめてみました。

void型

  • 処理が正常終了した結果、何も返さない(最後まで実行される)
  • undefinedを代入できる
void.ts
// OK
const sampleFunc1 = (): void => {
  return undefined;
}

never型

  • 処理が中断されるか、永遠に実行される(最後まで実行されない)
  • つまり、そもそも関数が正常終了しないので値が返ってこない
  • undefinedすら代入できない(何も代入できないので)
never.ts
// NG: never型にundefinedを代入できない
const sampleFunc2 = (): never => {
  return undefined;
}
// Type 'undefined' is not assignable to type 'never'.

// OK: エラーを投げるだけの関数(最後まで到達しない)
const sampleFunc3 = (): never => {
  throw new Error('Errorをthrowします')
  // ここに到達できない
}

// OK: 永遠に実行される関数
const sampleFunc4 = (): never => {
  while(true) {
    console.log('永遠にループするで')
  }
  // ここに到達できない
}

never型の例として、「エラーを投げるだけの関数」と「永遠に実行される関数」の2つがよく紹介されているけれど、こんなん使うのか???と思う。後者に至ってはただのバグじゃないかと。。。

ちなみに「分岐の結果、エラーを投げる関数」は、never型にできません。

// NG: 分岐の結果、エラーを投げる関数
const sampleFunc5 = (err: boolean): never => {
  if(err) {
    throw new Error('Errorをthrowします')
  }
}
// A function returning 'never' cannot have a reachable end point.
// 略: never型を返す関数は、最後まで到達できてはいけないよ〜

errfalseの場合は、エラーを投げずに最後まで到達してしまうので、never型にはできないよ」という意味ですね。

never型とvoid型は異なる

結局いつ使えるか?

上にあげた関数たちはとても実用性がなさそうです。

1つ言うなら、推論の結果、neverを返す関数があったら、最後まで到達できない関数(=つまりバグ?)を検知できそうってくらいかなぁと...(見たことないですが)

ただ、このneverの特性を活かし、唯一使えそうなのがswitch文における**「網羅性チェック」**です。

網羅性チェック

例えば、以下のようなユニオンリテラル型であるAnimalを受け取り、鳴き声を出力する関数(cryFunc)があるとします。

cryFunc内のcase句で、Animalに定義されている全てのリテラルを網羅したい所ですが、現状Birdのcase句が足りていません。
このことをTypeScriptはエラーで教えてくれません;;

type Animal = 'Dog' | 'Cat' | 'Bird';

const cryFunc = (animal: Animal) => {
  switch(animal) {
    case 'Dog':
      console.log('ワン!')
      break;
    case 'Cat':
      console.log('ニャー!')
      break;
    // 'Bird'のcase句が存在しないけど、エラーが発生しない
  }
}

これの何が困るかと言うと、cryFuncの存在を知らない開発者がAnimalに値を追加した時に、コンパイルエラーが出ないので、バグを生んでしまう可能性があることです。

ここでエラーを出すようにするために登場するのが、neverです!

type Animal = 'Dog' | 'Cat' | 'Bird';

const cry = (animal: Animal) => {
  switch(animal) {
    case 'Dog':
      console.log('ワン!')
      break;
    case 'Cat':
      console.log('ニャー!')
      break;
    default:
      const exhaustivenessCheck: never = animal;
      // NG: Type 'string' is not assignable to type 'never'.
      break;
  }
}

default句の中で、網羅性をチェックしたい値に対して、never型を定義してあげます。
こうすることで、TypeScriptが代入に関するエラーを出してくれるようになります。

※網羅性チェック用の例外クラスを作ると良いらしい。
参考:例外による網羅性チェック

never型は網羅性チェックとしてだけは使えそう

結論

never型とは?

  • 何も代入ができない
  • void型とは異なる
  • 網羅性チェックをしたいシーンでは活躍しそう(それ以外は...)

参考にさせて頂いた記事

サバイバルTypeScript
TypeScript Deep Dive 日本語版
TypeScriptのNever型とは?

6
3
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
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?