尾端类型
在类型系统中,尾端类型(Bottom Type)是所有其他类型的子类型。由于一个值不可能同时属于所有类型,例如一个值不可能同时为数字类型和字符串类型,因此尾端类型中不包含任何值。尾端类型也称作 0 类型或者空类型。
TypeScript 中只存在一种尾端类型,即 never 类型。
never
TypeScript 2.0 版本引入了仅有的尾端类型— never 类型。never 类型使用 never 关键字来标识,不包含任何可能值。示例如下:
function f(): never {
throw new Error();
}
根据尾端类型的定义,never 类型是所有其他类型的子类型。所以,never 类型允许赋值给任何类型,尽管并不存在 never 类型的值。示例如下:
let x: never;
let a: boolean = x;
let b: string = x;
let c: number = x;
let d: bigint = x;
let e: symbol = x;
let f: void = x;
let g: undefined = x;
let h: null = x;
正如尾端类型其名,它在类型系统中位于类型结构的最底层,没有类型是 never 类型的子类型。因此,除 never 类型自身外,所有其他类型都不能够赋值给 never 类型。示例如下:
let x: never;
let y: never;
// 正确
x = y;
// 错误
x = true;
x = 'hi';
x = 3.14;
x = 99999n;
x = Symbol();
x = undefined;
x = null;
x = {};
x = [];
x = function () {};
需要注意的是,就算是类型约束最宽松的 any 类型也不能够赋值给 never 类型。示例如下:
let x: any;
let y: never = x;
// ~
// 编译错误:类型'any'不能赋值给类型'never'
应用场景
never 类型主要有以下几种典型的应用场景。
场景一 never 类型可以作为函数的返回值类型,它表示该函数无法返回一个值。我们知道,如果函数体中没有使用 return 语句,那么在正常执行完函数代码后会返回一个 undefined 值。在这种情况下,函数的返回值类型是 void 类型而不是 never 类型。只有在函数根本无法返回一个值的时候,函数的返回值类型才是 never 类型。
一种情况就是函数中抛出了异常,这会导致函数终止执行,从而不会返回任何值。在这种情况下,函数的返回值类型为 never 类型。示例如下:
function throwError(): never {
throw new Error();
// <- 该函数永远无法执行到末尾,返回值类型为'never'
}
此例中,throwError 函数的功能是直接抛出一个异常,它永远也不会返回一个值,因此该函数的返回值类型为 never 类型。
若函数中的代码不是直接抛出异常而是间接地抛出异常,那么函数的返回值类型也是 never 类型。示例如下:
function throwError(): never {
throw new Error();
}
function fail(): never {
return throwError();
}
此例中,fail 函数包含了一条 return 语句,return 语句中表达式的类型为 never 类型,因此 fail 函数的返回值类型也为 never 类型。
除了抛出异常之外,还有一种情况函数也无法正常返回一个值,即如果函数体中存在无限循环从而导致函数的执行永远也不会结束,那么在这种情况下函数的返回值类型也为 never 类型。示例如下:
function infiniteLoop(): never {
while (true) {
console.log('endless...');
}
}
此例中,infiniteLoop 函数的执行永远也不会结束,这意味着它无法正常返回一个值。因此,该函数的返回值类型为 never 类型。
场景二 在 “条件类型” 中常使用 never 类型来帮助完成一些类型运算。例如,Exclude<T, U> 类型是 TypeScript 内置的工具类型之一,它借助于 never 类型实现了从类型 T 中过滤掉类型 U 的功能。示例如下:
type Exclude<T, U> = T extends U ? never : T;
下例中,我们使用 Exclude<T, U> 工具类型从联合类型 boolean | string 中剔除了 string 类型,最终得到的结果类型为 boolean 类型。示例如下:
type T = Exclude<boolean | string, string>; // boolean
关于条件类型的详细介绍请参考 6.7 节。
最后一个要介绍的 never 类型的应用场景与类型推断功能相关。在 TypeScript 编译器执行类型推断操作时,如果发现已经没有可用的类型,那么推断结果为 never 类型。示例如下:
function getLength(message: string) {
if (typeof message === 'string') { // 2
message; // string
} else {
message; // never // 5
}
}
此例中,getLength 函数声明定义了参数 message 的类型为 string。
第 2 行,在 if 语句中使用 typeof 运算符来判断 message 是否为 string 类型。若参数 message 为 string 类型,则执行该分支内的代码。因此,第 3 行中参数 message 的类型为 string 类型。
第 5 行,在 else 分支中参数 message 的类型应该是非 string 类型。由于函数声明中定义了参数 message 的类型是 string 类型,因此 else 分支中已经不存在其他可选类型。在这种情况下,TypeScript 编译器会将参数 message 的类型推断为 never 类型,表示不存在这样的值。