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

TypeScript是否存在允许未定义字段且类型为unknown的模式?

TypeScript 联合类型窄化的边界问题

问题重现

下面这段代码存在逻辑错误,但却能通过TypeScript的类型检查:

type Foo = {
  foo: 'foo'
}

type Bar = {
  bar: 'bar'
}

type FooOrBar = Foo | Bar

function isFoo(o: FooOrBar) {
  if ('foo' in o) {
    return o
  } else {
    throw new Error('Only "foo"s please!')
  }
}

const fooledYou = {
  bar: 'bar',
  foo: 'fooled you!'
} as const

const test: FooOrBar = fooledYou

const huh: Foo = isFoo(test)

console.log(huh.foo) // 实际输出 'fooled you!',但类型检查认为是 'foo'

问题原因

TypeScript中,像type Bar = { bar: 'bar' }这样的对象类型默认是开放类型——它只要求对象包含bar属性,并不禁止存在额外属性。而类型窄化时,'foo' in o的检查只会判断对象是否有foo属性,不会验证该属性的类型是否符合Foo的定义,因此会错误地把同时包含bar和foo的对象窄化为Foo类型,导致后续的类型断言出错。

核心问题解答

TypeScript 默认不会将普通对象类型视为带索引签名的封闭类型

你提到的将type Bar = { bar: 'bar' }自动等同于带[key: keyof any]: unknown索引签名的类型,并不是TypeScript的默认行为。因为这种设计会大幅限制JavaScript对象的灵活性——JS中对象动态添加属性是很常见的操作,默认开放类型更符合原生JS的使用习惯。

实现类似“封闭类型”的方案

如果你希望限制对象只能包含指定属性,避免额外属性干扰类型窄化,可以通过以下方式实现:

  1. 添加never类型的索引签名
    这种方式会明确禁止对象存在任何未定义的属性:
type Bar = {
  bar: 'bar'
  // 禁止任何额外的字符串/数字/Symbol属性
  [key: string]: never
  [key: number]: never
  [key: symbol]: never
}

此时fooledYou因为包含额外的foo属性,会直接无法赋值给FooOrBar,从源头避免错误。

  1. 使用工具类型模拟精确类型
    可以自定义工具类型,确保对象没有超出定义的属性:
type Exact<T, U> = T & Record<Exclude<keyof U, keyof T>, never>

// 使用方式:
type Bar = Exact<{ bar: 'bar' }, typeof Bar>

这种方式同样能限制额外属性的存在,不过需要手动应用工具类型。

总结

TypeScript默认的开放类型设计是为了兼容JavaScript的动态特性,但如果你需要严格的类型约束,可以通过手动添加never索引签名或自定义工具类型来实现类似“封闭类型”的效果,避免额外属性导致的类型窄化错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 11:37:05