尾端类型

在类型系统中,尾端类型(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 类型。若参数 messagestring 类型,则执行该分支内的代码。因此,第 3 行中参数 message 的类型为 string 类型。

第 5 行,在 else 分支中参数 message 的类型应该是非 string 类型。由于函数声明中定义了参数 message 的类型是 string 类型,因此 else 分支中已经不存在其他可选类型。在这种情况下,TypeScript 编译器会将参数 message 的类型推断为 never 类型,表示不存在这样的值。