never型って?
typeScriptにおけるbottom型です。
bottom型って何よ
型理論、数理論理学において値を持たない型のことである
↑wikiからの抜粋です。
値を持たない型, これだけだと???となりそうですが、
戻り値の型がボトム型である関数は、いかなる値も返さない。
こちらの説明だと少しわかりやすいかと思います。
つまり、
const foo = () => {
while(true){}
}
上記のようにwhile(true)で戻り値がない場合(= returnが絶対に走らない場合)などは、関数fooの戻り値の型はneverと言えます。
特徴
neverは値のない型なので、値を入れるとエラーになります。
let foo:never
foo = 'test' // コンパイルerror
実際に戻り値がneverとなる例
上記でwhileの例を上げましたが、他の例とも合わせて再掲します。
// 無限ループする場合
const foo = () => {
while(true){}
}
// errorをthrowする場合
const bar = () => {
throw new Error("これはエラーです")
}
voidとneverの違いは?
これは実際にコードを見てもらった方がわかりやすいです。
const foo = () => {
}
const bar = () => {
return
}
const baz = () => {
while(true){}
}
let fooVar:never = foo() // error
let barVar:never = bar() // error
let bazVar:never = baz() //ok!
上記では bazのみ戻り値がnever型で、 fooとbarの戻り値はvoid型です。
JSに置いてreturn文を省略された場合はundefinedが返る仕様なので、
fooとbarは実質的に同じです。
voidはreturnでの戻り値なし!の型なのに対して
neverはそもそもreturnしない。
似ているようで全然違っています。
実際での使用例
値を入れることができない という部分が生きてきます。
下記のような型がそれぞれあったとします。
interface Neko {
type: "neko"
}
interface Inu {
type: "inu"
name: number
}
interface Other {
type: "other"
}
type Animal = Neko | Inu | Other
上記Animal型を使う上で、typeによってそれぞれ別の処理をさせたい場合、
下記のようなコードになるかと思います。
function intro(s: Animal) {
if (s.type === "neko") {
return `吾輩は猫である。名前はまだない`
}
else if (s.type === "inu") {
return `${name}は犬のお廻りです。`
}
}
上記ではNeko Inu の型しか想定されていません。
Other型, またそれ以外でも想定しない型が渡ってきた場合にerrorを返す仕組みがあれば便利です。
そこで、予期しない型が渡ってきた場合はneverに代入することでコンパイルエラーを出し、
網羅漏れに気づくことができます。
(全て網羅できていれば、 _exhaustiveCheckはnever型となります。)
function intro(s: Animal) {
if (s.type === "neko") {
return `吾輩は猫である。名前はまだない`
}
else if (s.type === "inu") {
return `${name}は犬のお廻りです。`
}
else {
// Otherが渡ってきた場合、neverの特徴によってerrorが出て気づくことができる。
const _exhaustiveCheck: never = s;
}
}
上記の手法はswitchのdefault文に置いても有効ですね。
参考文献
TypeScript Deep Dive 日本語版
Use the never type to avoid code with dead ends using TypeScript
TypeScriptの型入門