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

为何严格模式下TypeScript有时允许联合类型混合字段?

TypeScript联合类型的“超集匹配”陷阱:实例不匹配任意成员却通过类型检查的场景

TypeScript采用结构子类型规则进行类型校验,这会导致一种容易踩坑的场景:一个对象实例既不完全符合联合类型中的任何单个成员,却依然能通过联合类型的类型检查,进而引发运行时错误。

示例类型定义

type Bar = {
  bar: true
  bar2: number
}
type Baz = {
  baz: number
}
type Foo = Bar | Baz

两种赋值场景的差异

  • 当直接赋值给Baz类型时,TypeScript会严格校验属性,额外的bar属性会触发错误:
// 类型错误:对象文字可以只指定已知属性,“bar”不在类型“Baz”中
const bazArg: Baz = {baz: 5, bar: true}
  • 但赋值给联合类型Foo时,不会触发任何类型错误:
// 无类型错误,但该对象既不符合Bar(缺少必填属性bar2),也不符合Baz(多了bar属性)
const bazArg: Foo = {baz: 5, bar: true}

由此引发的运行时bug

这种“不完整匹配”的对象会破坏基于属性检查的类型窄化逻辑:

const fn = (foo: Foo): number => 'bar' in foo ? foo.bar2 : foo.baz
const bazArg: Foo = {baz: 5, bar: true}
console.log(fn(bazArg)) // 输出undefined

这里的问题是:代码通过'bar' in foo判断对象为Bar类型,进而访问bar2属性,但实际对象并没有bar2,最终返回undefined,违反了函数声明的number返回类型约定。

问题根源

TypeScript对联合类型的校验是**“最小满足”**而非“严格匹配”:只要对象满足联合类型中某一个成员的所有必填属性,就会被判定为合法,哪怕它额外拥有其他成员的属性,或者不满足其他成员的要求。在上述例子中,{baz:5, bar:true}满足Baz的必填属性baz,因此被视为符合Foo类型,但它同时携带的bar属性干扰了类型窄化逻辑。

解决方案

  1. 添加唯一类型标签(推荐)
    给联合类型的每个成员添加专属的区分属性,让TypeScript能准确识别并窄化类型:
type Bar = {
  type: 'bar' // 唯一标签
  bar: true
  bar2: number
}
type Baz = {
  type: 'baz' // 唯一标签
  baz: number
}
type Foo = Bar | Baz

const fn = (foo: Foo): number => foo.type === 'bar' ? foo.bar2 : foo.baz
const validBaz: Foo = {type: 'baz', baz: 5, bar: true} // 无错误,fn会正确返回baz的值
const invalidArg: Foo = {baz:5, bar:true} // 现在会报错:缺少type属性
  1. 自定义严格类型守卫
    通过自定义类型守卫函数,确保对象完全匹配联合类型中的某一个成员:
function isBar(foo: any): foo is Bar {
  return foo.bar === true && typeof foo.bar2 === 'number'
}
function isBaz(foo: any): foo is Baz {
  return typeof foo.baz === 'number' && !(foo.bar)
}

const fn = (foo: Foo): number => {
  if (isBar(foo)) return foo.bar2
  if (isBaz(foo)) return foo.baz
  throw new Error('传入的Foo类型不合法')
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 10:05:32