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。如果 T 是 never,则返回 true,否则返回 false。
现在来看这行代码:
type aa = IsNever<never>;
根据 IsNever 的定义,如果 T 是 never,那么 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。但是,当 T 为 never 时,情况有所不同。
处理 never 类型的特殊规则
当 T 是 never 时,T extends U 表达式将始终为 false,因为 never 类型没有任何值。因此,在 T extends U ? X : Y 中,T 为 never 的情况下,结果将直接被优化为 never,因为整个条件表达式被视为无效。
例如:
type Test<T> = T extends never ? true : false;
对于 Test<never>,T 是 never,条件类型的结果直接被视为 never,因为 TypeScript 优化了这种情况,认为条件表达式无意义。
避免这种情况的方法
为了避免这种优化,可以通过将 T 包装在元组中,使其不再直接被 never 优化:
type IsNever<T> = [T] extends [never] ? true : false;
在这种情况下,[T] 是一个包含 T 的元组,即使 T 是 never,元组 [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 中的工作机制。我们需要仔细区分两种情况:
- 直接比较
never extends never的情况: - 在泛型条件类型中比较
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。
例子解释
-
直接比较:
type bb = never extends never ? true : false; // true- 这是一个简单的条件类型比较,
never可以赋值给never,所以结果是true。
- 这是一个简单的条件类型比较,
-
泛型条件类型:
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]可以避免这种优化,确保条件类型评估正确。