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

为何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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 18:55:17