尾端类型
在类型系统中,尾端类型(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
类型,表示不存在这样的值。