はじめに
neverって何?どこで使うの?ってことで調べたのでまとめます。
never型とは
never型は「値を持たない」を意味するTypeScriptの特別な型です。
サバイバルTypeScript
つまり、
どんな値も持たない=どんな変数も代入することができない
ということです。
それvoid型と一緒じゃないん?
そう、私のようにvoid型と一緒じゃないか?と疑問を持つ方がいるかもしれません...
結論から言うとvoid型とは異なります。(それはそうやろ)
ということでvoid型との違いをまとめてみました。
void型
- 処理が正常終了した結果、何も返さない(最後まで実行される)
- undefinedを代入できる
// OK
const sampleFunc1 = (): void => {
return undefined;
}
never型
- 処理が中断されるか、永遠に実行される(最後まで実行されない)
- つまり、そもそも関数が正常終了しないので値が返ってこない
- undefinedすら代入できない(何も代入できないので)
// 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型を返す関数は、最後まで到達できてはいけないよ〜
「err
がfalse
の場合は、エラーを投げずに最後まで到達してしまうので、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型とは?