为什么在TypeScript中引入了“never”类型?

28

我现在在网上搜索关于为什么要引入 never 类型的解释。TypeScript手册和TypeScript深入讲解都解释了它是什么以及其他一些文章。但是,它们都没有解释为什么要使用 never 类型以及它解决了什么问题。 大多数例子展示的是代码,编译器已经推断出 never 类型,那么为什么还需要分配类型呢?它解决了什么问题?它只是为了告诉IntelliSense之后可以做什么(即什么都不做-这可能足够有用吗)。编译器会如何使用这些信息? 与往常一样,关于如何做事情的解释可能有所帮助,但只有为什么才能使人理解。

1个回答

51
never 类型是 TypeScript 对 底部类型 的表示,来自于类型理论。因此,如果您正在寻找 TypeScript 中 never 的动机和用途,您可能需要研究计算机科学中的底部类型。
"为什么 TypeScript 没有引入 'never' 类型" 的标准回答可能最好通过分析 引入该类型的 pull request 和它解决的两个问题来找到:请求底部类型请求更好地处理无法访问的函数返回。开发人员的主要用途是为不返回任何值的函数(或函数部分)提供类型,甚至不包括 undefined。比较这些函数:"
function u(): undefined {
    return;
}
const uRet = u();
console.log(typeof uRet); // undefined

function v(): void { }
const vRet = v();
console.log(typeof vRet); // undefined

这些函数返回undefined值:

function throws(): never {
    throw new Error();
}
const tRet = throws();
console.log(typeof tRet); // does not run

function loops(): never {
    while (true) { }
}
const lRet = loops();
console.log(typeof lRet); // does not run

由于异常或无限循环而根本不返回值的函数。 never类型允许开发人员和编译器推断出永远不会运行的代码部分。


除了代码示例,还有其他用途可以使用never,但我不打算尝试列举它们,因为您已经知道。检查TypeScript标准库以查找never出现的位置是很有启发性的。


最后,当您提出问题时

大多数示例都显示代码,其中编译器已经推断了never,那么为什么我还要分配类型?

请注意,即使在编译器为您推断never的情况下,它在语言中存在也是有用的,就像编译器推断string时一样有用。这似乎是关于何时需要显式注释类型而不是让编译器推断它们以及反之的不同问题。但是本文已经很长了,这个问题可能已经在其他地方得到了回答。

希望这能帮助您,祝好运!


编辑说明:有很多程序员会使用never,尽管许多程序员可能永远不需要使用它。让我列举一些我能想到的原因:

  • If you are writing typings for a JavaScript library where a function throws an exception instead of returning, you can't really do that without never.

  • Generic type parameters default to {} when they can't be inferred by the compiler. It sort of "fails open", since {} is compatible with (almost) all values. There are cases when you'd prefer it to "fail closed" and have it be compatible with no values. You can use never as the default parameter.

  • The union of any type T with never is just T, and the intersection of T with never is never. These rules (among others) let a developer build fairly sophisticated type functions that would be harder or impossible without never. For example, here's Diff<T,U>:

    type Diff<T extends string, U extends string> =
      ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
    

    It is a type function which takes a union of string literals T and a union of string literals U, and returns a union of all the values in T which are not present in U.

    You can use it to, among other things, remove a property from a type:

    type Foo = { a: string, b: number, c: boolean };
    type FooWithoutB = Pick<Foo, Diff<keyof Foo, 'b'>>;
    // equivalent to { a: string, c: boolean }
    
这有点像数学中的零。你不{{需要}}它;很多文化没有零的概念也能过得去。但是它非常有用,可以让你轻松表达那些否则会很麻烦甚至不可能实现的想法。希望这样更具吸引力?无论如何,再次祝好运。

感谢您的详细解释。所以它确实表明这更多是编译器的需求,而不是程序员的需求,因为再次强调,作为程序员,没有真正的理由使用never语句,对吧? - Morten H Pedersen
有很多原因程序员会使用 never。我已经编辑了我的答案并列举了其中一些。 - jcalz
再次感谢jcalz。因此,仍需更完整地描述此类函数的类型,因为永远不返回的函数仍可能是void类型并正常工作,只是它会指示该函数实际上将返回void,但它没有返回,对吗?并且为了帮助编译器:typescriptlang.org:“第二种方法使用编译器用于检查穷尽性的never类型:”所以我仍然没有看到任何“实际”的原因-解决任何实际问题,对吗?啊-除非它将防止一个不应该返回的函数返回? - Morten H Pedersen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接