Yuzhe's Blog

yuzhes

IsNever

IsNever

题目链接

题目

实现类型 IsNever,判断传入的类型 T 是否为 never。 如果是 never,返回 true;否则返回 false

type A = IsNever<never>     // 期望为 true
type B = IsNever<undefined> // 期望为 false
type C = IsNever<null>      // 期望为 false
type D = IsNever<[]>        // 期望为 false
type E = IsNever<number>    // 期望为 false

解法

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

一行代码。看起来简单,玄机全在那对方括号里。

深入理解

为什么不能直接写 T extends never

// ❌ 错误写法
type IsNeverWrong<T> = T extends never ? true : false

type Test = IsNeverWrong<never> // never,不是 true!

结果是 never 而不是 true。发生了什么?

又是分布式条件类型的锅

当你写 T extends Constraint ? A : B,并且 T 是一个裸类型参数时,TypeScript 会对联合类型进行特殊处理——它会把条件分布到联合的每个成员上:

type Distributed = (string | number) extends string ? 'yes' : 'no'
// 展开为:
// (string extends string ? 'yes' : 'no') | (number extends string ? 'yes' : 'no')
// = 'yes' | 'no'

问题来了:never空联合——一个没有任何成员的联合。当你对空集分布时,什么都得不到:

// T = never(空联合)
// 分布:... 遍历零个成员 ...
// 结果:never(零个结果的联合)

条件根本没被求值。TypeScript 直接短路返回 never

元组包裹技巧

用元组 [T]T 包起来可以禁用分布

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

现在 [T] 不再是裸类型参数——它是一个包含 T 的元组类型。TypeScript 不会对元组类型分布,所以条件只会被求值一次:

为什么 [never] 能用但 never 不行?

表达式行为
never extends never对空集分布 → never
[never] extends [never]单次比较 → true

元组充当容器,把类型”固化”住,阻止了空联合的特殊行为。

使用这个技巧的常见模式

// 检测 never
type IsNever<T> = [T] extends [never] ? true : false

// 阻止任何联合的分布
type NoDistribute<T> = [T] extends [T] ? T : never

// 无分布的安全比较
type Equals<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false

相关陷阱:any

注意 [any] extends [never] 的结果是 boolean,不是 truefalse

type Test = [any] extends [never] ? true : false // boolean

因为 any 同时可赋值和不可赋值给任何类型——TypeScript 两边都不敢押,把两个分支都返回为联合。如果你需要一个同时拒绝 anyIsNever,需要额外检测:

type IsStrictNever<T> = [T] extends [never]
  ? (0 extends (1 & T) ? false : true)  // 排除 any
  : false

但大多数场景下,简单的 [T] extends [never] 就够用了。

核心要点