TypeScriptのnever
型についてまとめます。
今まではいまいち使い所が分かっていないな〜という感じでしたが、今回理解できた気がします。
動作確認環境
- Mac
- TypeScript Playground v5.1.3
never型とは
TypeScriptの型の一つです。
「値を持たない」ことを表します。
never型の特徴
関数の戻り値型で考えてみます。何も値を返さない関数の場合、戻り値型がnever
となります。
例えば終了しない関数や、必ず例外が発生する関数は戻り値の型がnever
となります。
終了しない関数
const fn = (): never => {
while (true) {}; // 無限ループ
}
必ず例外が発生する関数
const fn = (): never => {
throw new Error();
}
never
型の変数にはnever
型の値のみ代入できます。その他の値は代入できません。
const foo: never = 1; // Error : Type 'number' is not assignable to type 'never'.
const fn = (): never => {
throw new Error();
}
const bar: never = fn();
never
型はどんな型にも代入できます。
const fn = (): never => {
throw new Error();
}
const num: number = fn();
const str: string = fn();
const flag: boolean = fn();
また、「作り得ない型」もnever
型となります。
// numberとstringを同時に満たす値は作り得ない。NumberStringはneverとなる
type NumberString = number & string;
never と void の違い
void
は値を返さない関数の戻り値として使われます。
※正確には、関数が何も返さないことを強制するものではなはなく、他の値を返すことができますが無視されます。詳しくはこちらを参照してください。
言い換えると、値を何も返さない関数はvoid
を返します。returnを返すことがない(または常にスローする)関数はnever
を返します
次のような場合はvoid
を返すのが正しいので、TypeScriptはコンパイルエラーを出します。
// Error : A function returning 'never' cannot have a reachable end point.
const fn = (value: string): never => {
console.log(value)
}
never型の使い所 網羅チェック
never
型を使うことで、網羅チェックを行い、網羅できていない場合に(実行時エラーではなく)コンパイルエラーを出すことができます。
以下のコードを例にします。
type FavoriteFruit = "Apple" | "Lemon";
const getMainColor = (fruit: FavoriteFruit) => {
switch (fruit) {
case "Apple":
return "red";
case "Lemon":
return "yellow";
default:
throw new Error(`${fruit} is not in FavoriteFruit.`);
}
}
console.log(getMainColor("Apple")); // "red"
FavoriteFruit
に Grape
「葡萄」を追加することにしました。
type FavoriteFruit = "Apple" | "Lemon" | "Grape";
関数getMainColor
もGrape
に対応する値を返すように修正する必要があります。
が、getMainColor
への修正を忘れてしまったとします。
getMainColor
への修正をしない状態でgetMainColor("Grape")
というコードを書いてもコンパイルエラーにはなりません。
実行することによって Grape is not in FavoriteFruit.
のようにエラーが発生します。
type FavoriteFruit = "Apple" | "Lemon" | "Grape";
const getMainColor = (fruit: FavoriteFruit) => {
switch (fruit) {
case "Apple":
return "red";
case "Lemon":
return "yellow";
default:
throw new Error(`${fruit} is not in FavoriteFruit.`);
}
}
console.log(getMainColor("Grape")); // Error : Grape is not in FavoriteFruit.
getMainColor
でnever
型を使う実装を行うことによって、これをコンパイルエラーとして検知することができるようになります。
const getMainColor = (fruit: FavoriteFruit) => {
switch (fruit) {
case "Apple":
return "red";
case "Lemon":
return "yellow";
default:
const _check: never = fruit; // Type 'string' is not assignable to type 'never'.
return _check
}
}
never
型の変数にはnever
型以外の値を代入することができません。
fruit
変数の値がGrape
だった場合、default
分岐に入ってくるため、それを推測したTypeScriptがコンパイルエラーを出しています。コンパイルエラーによって、実行前にコードを修正することができます。
以下が修正したコードです。
type FavoriteFruit = "Apple" | "Lemon" | "Grape";
const getMainColor = (fruit: FavoriteFruit) => {
switch (fruit) {
case "Apple":
return "red";
case "Lemon":
return "yellow";
case "Grape":
return "violet";
default:
const _check: never = fruit;
return _check
}
}
console.log(getMainColor("Grape")); // "violet"