为何TypeScript中省略联合交叉类型属性会引发类型错误?
问题分析与解决
问题重现
你定义了如下TypeScript类型:
export interface BaseTravelpayoutsPricesV3Options { currency ?: string; departure_at?: string; return_at ?: string; direct ?: boolean; limit ?: number; page ?: number; sorting ?: 'price' | 'route'; unique ?: boolean; one_way ?: boolean; } export interface TravelpayoutsPricesV3OptionsWithOrigin { origin: string; } export interface TravelpayoutsPricesV3OptionsWithDestination { destination: string; } export interface TravelpayoutsPricesV3OptionsWithOriginAndDestination { origin : string; destination: string; } export type TravelpayoutsPricesV3Options = BaseTravelpayoutsPricesV3Options & ( TravelpayoutsPricesV3OptionsWithOrigin | TravelpayoutsPricesV3OptionsWithDestination | TravelpayoutsPricesV3OptionsWithOriginAndDestination );
当尝试用Omit移除limit属性作为函数参数类型时,传入origin属性却提示未知属性:
function test(options: Omit<TravelpayoutsPricesV3Options, 'limit'>) { return 'foo'; } test({ origin: 'test', // 报错:Object literal may only specify known properties, and 'origin' does not exist in type 'Omit<TravelpayoutsPricesV3Options, "limit">' })
原因
1. Omit对联合类型的分布式处理
TypeScript中,Omit是分布式条件类型,当它作用于联合类型时,会对联合中的每个成员单独执行Omit操作。你的TravelpayoutsPricesV3Options本质是三个交叉类型的联合:
(Base & WithOrigin) | (Base & WithDestination) | (Base & WithOriginAndDestination)
执行Omit<..., 'limit'>后,得到的是三个独立Omit后的类型联合:
(Omit<Base, 'limit'> & WithOrigin) | (Omit<Base, 'limit'> & WithDestination) | (Omit<Base, 'limit'> & WithOriginAndDestination)
2. 新鲜对象字面量的严格多余属性检查
当直接传入对象字面量{origin: 'test'}时,TypeScript会触发严格多余属性检查:它要求字面量的所有属性必须存在于联合类型的每一个成员中。但上面的联合类型里,Omit<Base, 'limit'> & WithDestination这个成员没有origin属性,所以TypeScript判定origin是多余属性,抛出错误。
解决方法
方法一:提前对基础类型做Omit再交叉联合
直接修改类型定义,先移除limit再和各个分支交叉,避免分布式处理带来的问题:
type OmittedBase = Omit<BaseTravelpayoutsPricesV3Options, 'limit'>; type OmittedTravelpayoutsPricesV3Options = OmittedBase & TravelpayoutsPricesV3OptionsWithOrigin | OmittedBase & TravelpayoutsPricesV3OptionsWithDestination | OmittedBase & TravelpayoutsPricesV3OptionsWithOriginAndDestination; function test(options: OmittedTravelpayoutsPricesV3Options) { return 'foo'; } test({ origin: 'test' }); // 正常通过
方法二:绕过新鲜字面量检查
把对象字面量先赋值给变量,再传入函数——变量不会触发严格多余属性检查:
const opts = { origin: 'test' }; test(opts); // 正常通过
方法三:使用类型断言(不推荐,仅当确定类型安全时使用)
如果确定传入的对象符合类型,可以用断言跳过检查:
test({ origin: 'test' } as Omit<TravelpayoutsPricesV3Options, 'limit'>);
内容的提问来源于stack exchange,提问作者JamesJGoodwin
相关产品推荐
相关产品推荐

