type-challenges:IsNever

44 阅读6分钟

IsNever

问题描述

实现一个类型 IsNever,它接受输入类型 T。如果类型解析为 never,则返回 true,否则返回 false

举例:

type A = IsNever<never> // expected to be true
type B = IsNever<undefined> // expected to be false
type C = IsNever<null> // expected to be false
type D = IsNever<[]> // expected to be false
type E = IsNever<number> // expected to be false
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'type cases = [
  Expect<Equal<IsNever<never>, true>>,
  Expect<Equal<IsNever<never | string>, false>>,
  Expect<Equal<IsNever<''>, false>>,
  Expect<Equal<IsNever<undefined>, false>>,
  Expect<Equal<IsNever<null>, false>>,
  Expect<Equal<IsNever<[]>, false>>,
  Expect<Equal<IsNever<{}>, false>>
]
​
// ============= Your Code Here =============
// 正确答案1
type IsNever<T> = [T] extends [never] ? true : false
// 正确答案2
type IsNever<T> = (() => T) extends () => never ? true : false
type aa = IsNever<never> // true
type bb = never extends never ? true : false // true
// 错误答案
type IsNever<T> = T extends never ? true : false
type cc = IsNever<never> // never

并不理解为什么错误答案得到的是 never ,而正确答案需要包一层数组。

解释在 这里 | 这里 | 这里 | 这里

chatgpt

问题一

ts 中定义类型type IsNever<T> = = T extends never ? true : false
为什么 type aa = IsNever<never>  aa 的类型为never 而不是 true

回答

这是因为 TypeScript 的类型系统在处理 never 类型时有一些特别的规则。

首先,让我们看一下这个类型定义:

type IsNever<T> = T extends never ? true : false;

在这段代码中,IsNever 类型通过条件类型来检查传入的类型 T 是否是 never。如果 Tnever,则返回 true,否则返回 false

现在来看这行代码:

type aa = IsNever<never>;

根据 IsNever 的定义,如果 Tnever,那么 aa 应该是 true。但是,实际上在 TypeScript 中,aa 的类型会被推断为 never,这是因为 never 类型在 TypeScript 中的特殊处理方式。

具体来说,当你在条件类型中使用 never 时,TypeScript 会直接将整个条件类型的结果视为 never。这就是为什么 aa 的类型是 never 而不是 true

这是因为 never 类型表示的是一种不可能的类型(即它没有任何值)。当你对 never 类型应用条件类型时,TypeScript 会认为这个条件永远不会成立,因此直接将结果类型视为 never

为了验证这一点,我们可以通过一个稍微不同的方式来实现相同的逻辑:

type IsNever<T> = [T] extends [never] ? true : false;

在这种情况下,TypeScript 不会直接将条件类型的结果视为 never,因为我们通过包裹 T 在元组中来避免这种情况。现在如果你定义 aa

type aa = IsNever<never>;

aa 的类型将会是 true,如预期的那样。

总结一下,type IsNever<T> = T extends never ? true : false; 这种写法在处理 never 类型时,TypeScript 会直接将结果视为 never。而通过使用 [T] extends [never] ? true : false 的写法,可以避免这种情况,并获得预期的结果。

问题二

为什么TypeScript 会直接将结果视为 never

回答

这是因为 TypeScript 的条件类型在处理 never 时有特殊的行为。具体来说,当 never 类型作为条件类型中的检查类型时,TypeScript 会优化处理,将条件类型的结果直接视为 never。这是因为 never 表示的是一种不可能的类型(即,它没有任何值),因此条件类型中涉及 never 的部分也被认为是无效的,从而优化成 never

为了更好地理解这个行为,可以深入分析 TypeScript 条件类型的工作机制:

条件类型的工作原理

条件类型的基本形式是:

T extends U ? X : Y

意思是,如果类型 T 可以赋值给类型 U,则结果类型为 X,否则为 Y。但是,当 Tnever 时,情况有所不同。

处理 never 类型的特殊规则

Tnever 时,T extends U 表达式将始终为 false,因为 never 类型没有任何值。因此,在 T extends U ? X : Y 中,Tnever 的情况下,结果将直接被优化为 never,因为整个条件表达式被视为无效。

例如:

type Test<T> = T extends never ? true : false;

对于 Test<never>Tnever,条件类型的结果直接被视为 never,因为 TypeScript 优化了这种情况,认为条件表达式无意义。

避免这种情况的方法

为了避免这种优化,可以通过将 T 包装在元组中,使其不再直接被 never 优化:

type IsNever<T> = [T] extends [never] ? true : false;

在这种情况下,[T] 是一个包含 T 的元组,即使 Tnever,元组 [never] 也不会被直接优化为 never。因此,条件表达式 [T] extends [never] 将正确地评估为 true

示例验证

使用修改后的条件类型:

type IsNever<T> = [T] extends [never] ? true : false;
type aa = IsNever<never>;  // aa 的类型将是 true

这样,aa 的类型将是 true,如预期的那样。

总结

TypeScript 优化条件类型中的 never 是为了提高编译器效率,因为 never 类型表示不可能存在的类型。当条件类型涉及 never 时,TypeScript 会直接将结果视为 never。通过将类型包装在元组中,可以避免这种优化,从而获得预期的结果。

问题三

那为什么这样的话,结果就是true了呢?
type bb = never extends never ? true : false // true

回答

这是一个很好的问题,涉及到条件类型在 TypeScript 中的工作机制。我们需要仔细区分两种情况:

  1. 直接比较 never extends never 的情况
  2. 在泛型条件类型中比较 T extends never 的情况
直接比较 never extends never

当你直接比较 never extends never 时:

type bb = never extends never ? true : false; // true

在这种情况下,TypeScript 直接进行条件判断。由于 never 类型可以赋值给 never 类型,因此这个条件为 true。所以 bb 的类型是 true

在泛型条件类型中比较 T extends never

当你在泛型条件类型中使用 T extends never 时:

type IsNever<T> = T extends never ? true : false;

这里的 T 是一个泛型参数,当你传递 never 作为 T 时,TypeScript 会将 T 推断为 never,但它并不会直接进行条件判断,而是会优化条件类型为 never

例子解释
  1. 直接比较

    type bb = never extends never ? true : false; // true
    
    • 这是一个简单的条件类型比较,never 可以赋值给 never,所以结果是 true
  2. 泛型条件类型

    type IsNever<T> = T extends never ? true : false;
    type aa = IsNever<never>; // never
    
    • 在这个例子中,T 被替换为 never,然后 TypeScript 进行条件类型优化,直接将结果视为 never,而不是进行条件判断。
更详细的解释

为了更深入理解,我们可以考虑以下类型转换的工作方式:

  • never 类型表示没有值,因此当 never 被用作泛型参数时,条件类型 T extends never 被视为一个不存在的路径,TypeScript 优化处理这种情况,直接将结果设为 never
  • 但在直接比较的情况下(如 never extends never),没有这种优化,因为这是一个简单的布尔条件判断。
示例对比
type IsNever<T> = T extends never ? true : false;
​
// 直接比较
type bb = never extends never ? true : false; // true// 使用泛型条件类型
type aa = IsNever<never>; // never

为了避免这种优化,可以使用元组包装来确保条件类型得到正确处理:

type IsNever<T> = [T] extends [never] ? true : false;
type aa = IsNever<never>; // true

在这种情况下,aa 的类型将是 true,因为 [T] extends [never] 会正确评估条件。

总结
  • 直接比较 never extends never 是一个简单的条件判断,结果为 true
  • 泛型条件类型 T extends never 中,传递 never 作为 T 时,TypeScript 优化处理这种情况,结果为 never
  • 使用元组包装 [T] extends [never] 可以避免这种优化,确保条件类型评估正确。