You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中泛型与内联场景下`{}`交叉原始类型的行为差异及NonNullable<T>实现疑问

TypeScript中泛型与内联场景下{}交叉原始类型的行为差异及NonNullable实现疑问

嘿,这个问题真的戳中了TypeScript类型系统里一个容易让人挠头的细节!我当初第一次碰到的时候也盯着编辑器愣了半天,咱们来慢慢捋清楚到底是怎么回事。

首先咱们再明确下问题里的核心矛盾:

  • 当用泛型工具类型NonNullable<number>时,得到的SafeNumberV1还是原始的number类型,所以SafeNumberV1 extends object返回false,这完全符合预期;
  • 但直接把定义内联写成number & {}得到的SafeNumberV2,却让SafeNumberV2 extends object返回true,这就很反直觉了对吧?

这本质上是TypeScript对泛型参数的交叉类型和直接内联的原始类型交叉采用了两种不同的处理逻辑:

1. 泛型场景下的T & {}:保留原始类型,仅排除null/undefined

当你在泛型里写T & {}时,TypeScript知道你大概率是想通过交叉{}来排除null和undefined(因为这两种类型是唯一不能赋值给{}的)。所以当T是原始类型(比如number、string)时,TypeScript会直接把T & {}简化为T本身——毕竟原始类型本身就不是null或undefined,交叉{}之后完全不需要改变它的本质。这就是为什么NonNullable<number>最终还是number,自然不会被判定为object的子类型。

2. 直接内联的原始类型 & {}:被解读为包装对象类型

而当你直接写number & {}这种内联的交叉时,TypeScript会把它解读为原始值对应的包装对象类型。比如number & {}等价于大写的Number类型(也就是原始number值被装箱后得到的对象类型),而所有包装对象都是object的子类型,所以SafeNumberV2 extends object会返回true。

可能你会好奇为什么TypeScript要搞这种区别对待?其实都是为了让NonNullable<T>这种工具类型能正确工作——如果泛型里的T & {}都像内联那样变成包装对象,那原始类型经过NonNullable处理后就全变成对象类型了,这完全违背了我们使用这个工具类型的初衷:我们只是想去掉null和undefined,保留原始类型本身而已。

总结一下:

  • 泛型中的T & {}:针对原始类型,简化为T本身,仅完成排除null/undefined的任务;
  • 内联的原始类型 & {}:被视为对应的包装对象类型,属于object的子类型。

这样就能理解为什么两个断言结果会不一样啦!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:38:04