为何TypeScript将{...Omit<Type, Missing>, ...Pick<Type, Add>}推断为交集类型?
TypeScript泛型字段添加函数的类型正确性疑问
我正在实现一个泛型函数,目标是以类型安全的方式为未知类型的部分对象逐个添加字段,预期行为如下:
type Test = { a: string, b: number, c: boolean } // 待构建对象的类型 type Missing = 'a' | 'b' // 部分对象缺失的字段 type Add = 'b' // 要添加到部分对象的字段 const partial: Omit<Test, Missing> = { c: true } // 当前对象 const toAdd: Pick<Test, Add> = { b: 2 } // 扩展内容 const result: Omit<Test, Exclude<Missing, Add>> = { ...partial, ...toAdd } // 结果
上述示例无类型问题,对应的函数签名如下:
function add< Type extends object, Add extends Missing, Missing extends keyof Type = keyof Type> ( partial: Omit<Type, Missing>, field: Add, value: Type[Add] ): Omit<Type, Exclude<Missing, Add>> { // 实现代码 } const added: Pick<Test, 'b' | 'c'> = add(partial, 'b', 2)
这个签名本身没有类型问题,但尝试最直接的实现时:
return { ...partial, [field]: value }
会触发报错:
const toAdd: Pick<Type, Add> = { [field]: value } // 类型“{ [x: string]: Type[Add]; }”无法赋值给类型“Pick<Type, Add>”。(2322)
这是TypeScript已知的索引窄化问题,我尝试用类型断言处理:
const toAdd = { [field]: value } as Pick<Type, Add> const result: Omit<Type, Exclude<Missing, Add>> = { ...partial, ...toAdd } // 类型“Omit<Type, Missing> & Pick<Type, Add>”无法赋值给类型“Omit<Type, Exclude<Missing, Add>>”。(2322)
原因在于TypeScript错误地将右侧表达式的类型推断为Omit<Type, Missing> & Pick<Type, Add>,这个类型不够精确。比如{ ...{ a: string }, ...{ a: number } }实际会得到{ a: number },但TypeScript会推断为{ a: string & number }(即{ a: never })。
如果直接断言结果类型:
const result = { ...partial, ...toAdd } as Omit<Type, Exclude<Missing, Add>> // 将类型“Omit<Type, Missing> & Pick<Type, Add>”转换为类型“Omit<Type, Exclude<Missing, Add>>”可能有误,若需转换请先转为unknown。(2352)
现在我的核心疑问是:当Type被约束为object时,{ ...Omit<Type, Missing>, ...Pick<Type, Add> }的类型是否始终等于Omit<Type, Exclude<Missing, Add>>?
扩展场景说明
实际场景中函数逻辑更复杂:部分对象不仅可能缺失字段,还可能存在字段类型错误,示例如下:
type Test = { a: string, b: number, c: boolean } type Missing = 'a' | 'b' type Add = 'b' // 部分对象包含缺失字段的错误类型值 const partial: Omit<Test, Missing> & OptionalRecord<Missing, unknown> = { b: null, c: true } const toAdd: Pick<Test, Add> = { b: 2 } const result: Omit<Test, Exclude<Missing, Add>> & OptionalRecord<Exclude<Missing, Add>, unknown> = { ...partial, ...toAdd }
其中OptionalRecord定义为:
type OptionalRecord<K extends string | number | symbol, V> = { [key in K]+?: V } // Record<K, V>的变体,字段为可选
对应的扩展场景函数签名:
function add< Type extends object, Add extends Missing, Missing extends keyof Type = keyof Type>( partial: Omit<Type, Missing> & OptionalRecord<Missing, unknown>, field: Add, value: Type[Add] ): Omit<Type, Exclude<Missing, Add>> & OptionalRecord<Exclude<Missing, Add>, unknown> { // 实现代码 }
虽然遇到相同的类型推断问题,但以下实现未使用as unknown as 目标类型却能通过类型检查:
const toAdd = { [field]: value } as Pick<Type, Add> const result = { ...partial, ...toAdd } as { [key in keyof Type]: key extends Missing ? key extends Add ? Type[key] : never : Type[key] } as Omit<Type, Exclude<Missing, Add>> & OptionalRecord<Exclude<Missing, Add>, unknown>
内容的提问来源于stack exchange,提问作者kataik
相关产品推荐
相关产品推荐

