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

为何TypeScript中看似相同的元组类型不相等?

TypeScript元组UnNullify类型与Equals测试失败的原因及解决方法

问题背景

在TypeScript中操作元组时,可选元素会被自动追加undefined类型(例如[a: 1, b?: 2]会被隐式转为[a: 1, b?: 2 | undefined]),可读性较差。为此编写了UnNullify类型,用于移除元组元素中冗余的null和undefined:

// 依赖Playground中的Cast类型,确保输入为ReadonlyArray
type UnNullify<Data extends ReadonlyArray<any>> = Cast<{
  [K in keyof Data]: Exclude<Data[K], null | undefined>
}, ReadonlyArray<any>>

该类型的计算结果在类型提示中显示为[a: 1, b?: 2],但与手写的同结构元组无法通过Equals类型相等性测试:

type T = UnNullify<[a: 1, b?: 2 | undefined]> // 类型提示显示为 [a: 1, b?: 2]
type Test1 = Equals<T, [a: 1, b?: 2]> // 返回false
type Test2 = Equals<[a: 1, b?: 2], [a: 1, b?: 2]> // 返回true(符合预期)

原因分析

这是TypeScript的已知问题(对应Bug报告#50753):当通过映射数组索引的方式处理元组时,TypeScript无法保留元组可选元素的原始语义标记。

  • 手写的[a: 1, b?: 2]中,b是显式标记的可选元素,TypeScript内部会将其记录为元组的可选位置。
  • 而UnNullify通过映射生成的元组,b元素是通过Exclude移除undefined后,由TypeScript自动推断为“可选”,但内部实际标记为「包含undefined的必填元素」,只是在类型提示中简化显示为可选形式。
    Equals类型会严格对比类型的内部标记,而非仅表面的结构等价,因此返回false。

解决方法

需要绕过映射数组索引的方式,直接针对元组的元素和可选修饰符进行构造,保留原始的可选元素标记。修改后的UnNullify类型可以利用元组的递归推断实现:

// 依赖Cast类型确保输入为元组
type UnNullify<T extends ReadonlyArray<any>> = Cast<
  T extends readonly [infer First, ...infer Rest]
    ? First extends undefined | null
      ? never // 处理全为null/undefined的元素(按需调整)
      : [Exclude<First, null | undefined>, ...UnNullify<Rest>]
    : T extends readonly [infer First?, ...infer Rest]
      ? [Exclude<First, null | undefined>?, ...UnNullify<Rest>]
      : [],
  ReadonlyArray<any>
>

测试验证:

type T = UnNullify<[a: 1, b?: 2 | undefined]> // [a: 1, b?: 2]
type Test1 = Equals<T, [a: 1, b?: 2]> // 返回true(符合预期)

这种方式通过递归拆解元组,分别处理必填和可选的元素位置,直接构造带有正确可选标记的元组,从而通过Equals的严格相等测试。

内容的提问来源于stack exchange,提问作者zedryas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 20:30:31