为啥 TypeScript 中引入了 `never` 类型?

Posted

技术标签:

【中文标题】为啥 TypeScript 中引入了 `never` 类型?【英文标题】:Why was the `never` type introduced in TypeScript?为什么 TypeScript 中引入了 `never` 类型? 【发布时间】:2018-08-19 13:06:56 【问题描述】:

我现在已经在互联网上搜索了为什么要引入 never 类型的解释。 TypeScript 手册和 TypeScript Deep Dive 都解释了它是什么以及其他一些文章。但是,他们都没有解释为什么要使用 never 类型以及它解决了什么问题。 大多数示例显示代码,编译器已经推断出never,那么为什么我必须分配类型?它解决了什么问题。是否“只是”向智能感知发出信号之后我可以做什么(即什么都没有——这可能已经足够有用了)。编译器将这些信息用于什么目的? 与往常一样,解释“做什么”和“如何”可能会帮助人们做事,但只有“为什么”才能让他们理解。

【问题讨论】:

【参考方案1】:

never 类型是 TypeScript 对来自type theory 的bottom type 的表示。因此,如果您正在寻找 TypeScript 中never 的动机和用途,您可能需要研究计算机科学中的底层类型。


“为什么将never 引入TypeScript”的规范答案可能最好通过分析the pull request that introduced it 的评论及其解决的两个问题来找到:a request for a bottom type 和request to better handle unreachable function returns。

开发人员的主要用途是为根本不返回任何值的函数(或函数的部分)创建一个类型,甚至 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 standard library 以找到never 出现的地方很有启发性。


最后,当你问的时候

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

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

希望有所帮助;祝你好运!


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

如果您正在为 javascript 库编写类型,其中函数抛出异常而不是返回,那么如果没有 never,您将无法真正做到这一点。

当编译器无法推断泛型类型参数时,它们默认为。它有点“打开失败”,因为 与(几乎)所有值兼容。在某些情况下,您希望它“失败关闭”并使其不兼容任何值。您可以使用never 作为默认参数。

Tnever 的任何类型的并集就是TTnever 的交集是never。这些规则(以及其他规则)让开发人员可以构建相当复杂的类型函数,如果没有never,这些函数将变得更加困难或不可能。例如这里是Diff<T,U>

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

它是一个类型函数,它接受字符串文字 T 的并集和字符串文字 U 的并集,并返回 T 中不存在于 U 中的所有值的并集。

除其他外,您可以使用它从类型中删除属性:

type Foo =  a: string, b: number, c: boolean ;
type FooWithoutB = Pick<Foo, Diff<keyof Foo, 'b'>>;
// equivalent to  a: string, c: boolean 

这有点像数学中的数字零。你不需要它;许多文化在没有零概念的情况下相处得很好。但它非常有用,可以让您轻松表达原本会很麻烦或不可能的想法。

希望这更引人注目?无论如何,再次祝你好运。

【讨论】:

感谢您的详细解释。所以它确实说它更像是编译器的需要而不是编程的需要,因为再一次,没有真正的理由说明为什么你会使用 never 语句作为程序员,对吧? 程序员使用never的原因有很多。我已经用其中一些编辑了我的答案。 再次感谢 jcalz。因此,还需要对此类函数的类型进行完整的描述,因为从不返回的函数仍然可以是 void 类型并且可以正常工作,除非它表明该函数实际上将返回 void,因为它没有返回不返回?并帮助编译器:typescriptlang.org:“第二种方法使用编译器用来检查详尽性的 never 类型:”所以我仍然看不到任何“实际”原因 - 解决任何实际问题,对吧?啊 - 除了它会阻止 af 函数返回不应该的函数吗?

以上是关于为啥 TypeScript 中引入了 `never` 类型?的主要内容,如果未能解决你的问题,请参考以下文章

[TypeScript] Use TypeScript’s never Type for Exhaustiveness Checking

与 Typescript 反应:“never[]”类型的参数不可分配给“StateProperties |”类型的参数(() => 状态属性)'

[TypeScript] Use the never type to avoid code with dead ends using TypeScript

为什么状态在React TypeScript中首先返回空并且类型'never'不存在属性?

为啥联合类型中的“从不”类型毫无意义?

在TypeScript中如何防止属性被添加到一个空对象中?