never型とは『けっして起こりえないこと』を表現する型です。
けっして発生しない事象に対してnever型が付与されるので、never型にはどんな値もセットできません。
今回はnever型の活用事例について紹介します。
目次
Switch文やif文における網羅性のチェック
never型は『あり得ない』を表現するため、どんな値もnever型へセットできません。
この特性を応用することで網羅性をチェックできます。
利用するサンプルコード
正方形、長方形、三角形の面積を求めるarea
メソッドを作る場合を考えます。
area
の実行結果は以下のようなイメージです。
type Square = {
kind: "square";
size: number;
}
type Rectangle = {
kind: "rectangle";
width: number;
height: number;
}
type Triangle = {
kind: "triangle";
width: number;
height: number;
}
type Shape = Square | Rectangle | Triangle;
const square: Square = { kind: "square", size: 5 }
const rectangle: Rectangle = { kind: "rectangle", width: 5, height: 4 }
const triangle: Triangle = { kind: "triangle", width: 5, height: 4 }
console.log(area(square)); // 25
console.log(area(rectangle)); // 20
console.log(area(triangle)); // 10
考慮漏れが実行時にならないと気がつかない例
以下の実装はTriangle型の考慮が漏れている実装です。
const area =(shape: Shape) => {
if (shape.kind === "square") {
return shape.size * shape.size;
} else if (shape.kind === "rectangle") {
return shape.width * shape.height;
} else {
throw new Error(`Not Shape.`);
}
}
上記の場合、考慮漏れに気がつくのはTriangle型の変数をメソッドに渡した実行時です。
コード自体は間違っていないため、コンパイルエラーで事前検知できません。
// 実行時になって初めて例外が発生するのに気がつく
console.log(area(triangle)); // Error: Not Shape.
neverを利用することで考慮漏れがあった場合はコンパイル時に気がつける
以下の実装も先ほどと同様、Triangle型の考慮が漏れている実装です。違いはelse
節でnever型に値をセットするコードを追加している点です。
never型にはどんな値もセットできないため、以下のコードはコンパイルエラーになります。
const area = (shape: Shape) => {
if (shape.kind === "square") {
return shape.size * shape.size;
} else if (shape.kind === "rectangle") {
return shape.width * shape.height;
} else {
// Triangle型の場合、shapeがnever型にセットされるのでコンパイルエラーになる
const _exhaustiveCheck: never = shape; // Type 'Triangle' is not assignable to type 'never'.ts(2322)
throw new Error(`Not Shape.`);
}
}
このように、never型を利用することで考慮漏れによるエラーを事前検知できます。
Union型(Union Types)をフィルタリングするConditional Typesの作成
Conditional Typesとnever型を組み合わせることで、条件を満たした型のみUnion型に含めるというフィルタリング機能を実装できます。
Conditional Typesについて
Conditional Typesとは条件分岐の概念がある型のことをいいます。
Conditional TypesはT extends U ? A : B
のように記述し、『型Tが型Uを満たす場合は型A、満たさない場合は型B』となります。
例えば『型Tがstring型を満たす場合はtrue型、満たさない場合はfalse型』をConditional Typesで表現するとT extends string ? true : false
となります。
具体的な利用方法は以下の通りです。
// IsString<T>型は『型Tがstring型を満たす場合はtrue型、満たさない場合はfalse型』というConditional Types
type IsString<T> = T extends string ? true : false
// 'a'はstring型を満たすのでtrue型
type A = IsString<'a'>
// type A = true
// 1はstring型を満たさないのでfalse型
type B = IsString<1>
// type B = false
// booleanはstring型を満たさないのでfalse型
type C = IsString<boolean>
// type C = false
Union型におけるnever型の特性について
never型にはどんな値もセットできないため、T | never
というUnion型はT
になります。
具体的には以下の通りです。
type Foo = string | never
// type Foo = string と型推論される
Conditional Typesとnever型を組み合わせた、Union型のフィルタリングの具体例
Union型のフィルタリングには、never型を利用したT extends U ? T : never
というConditional Typesを利用します。
T extends U ? T : never
は『型Tが型Uを満たす場合は型T、満たさない場合はnever型』を表現します。つまり、『型Uを満たさなければ型Tをnever型として扱う』と言い換えられます。
Union型において、型Uを満たさない型Tは除外されることになります。
具体的なコードは以下の通りです。
type Square = {
kind: "square";
size: number;
}
type Rectangle = {
kind: "rectangle";
width: number;
height: number;
}
type Triangle = {
kind: "triangle";
width: number;
height: number;
}
type FourSidedShape = Square | Rectangle;
// FourSidedShapeを満たす型のみをフィリタリングする Conditional Types
type FourSidedShapeFilter<T> = T extends FourSidedShape ? T : never
// Triangleは FourSidedShape を満たさないので FourSidedShapeTypeから除外されている
type FourSidedShapeType = FourSidedShapeFilter<Square | Rectangle | Triangle>
// type FourSidedShapeType = Square | Rectangle
type FourSidedShapeType = FourSidedShapeFilter<Square | Rectangle | Triangle>
がtype FourSidedShapeType = Square | Rectangle
になる理由について説明します。
FourSidedShapeFilter<Square>
は Square
、FourSidedShapeFilter<Rectangle>
はRectangle
、FourSidedShapeFilter<Triangle>
はnever
になります。
ですので、FourSidedShapeFilter<Square | Rectangle | Triangle>
はSquare | Rectangle | never
というUnion型になります。
Union型においてnever型は無視されるので最終的にはSquare | Rectangle
というUnion型になります。
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!