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的使用习惯。
实现类似“封闭类型”的方案
如果你希望限制对象只能包含指定属性,避免额外属性干扰类型窄化,可以通过以下方式实现:
- 添加
never类型的索引签名
这种方式会明确禁止对象存在任何未定义的属性:
type Bar = { bar: 'bar' // 禁止任何额外的字符串/数字/Symbol属性 [key: string]: never [key: number]: never [key: symbol]: never }
此时fooledYou因为包含额外的foo属性,会直接无法赋值给FooOrBar,从源头避免错误。
- 使用工具类型模拟精确类型
可以自定义工具类型,确保对象没有超出定义的属性:
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
相关产品推荐
相关产品推荐

