为何无意义的Omit会彻底改变泛型函数参数中Partial的行为?
核心场景回顾
先明确你的类型定义和函数逻辑:
type Shape = {kind: "circle", radius: number} | {kind: "square", size: number}; // 初始泛型更新函数 function update<T extends Shape>(obj: T, values: Partial<T>) {} // 加了Omit修饰的版本 function update<T extends Shape>(obj: T, values: Omit<Partial<T>, "dont_care">) {}
1. 为什么直接给Shape赋值size报错,但原update函数允许传{size:3}?
当调用update(aShape, {size:3})时,TypeScript会对泛型T做分配性推断:它会把联合类型Shape拆成Circle和Square两个分支,分别检查是否存在合法的T使得调用成立。
Partial<T>对联合类型是分配性的,Partial<Shape>等价于Partial<Circle> | Partial<Square>,也就是{kind?: "circle", radius?: number} | {kind?: "square", size?: number}。{size:3}完全匹配Partial<Square>这个分支,所以TypeScript认为存在合法的T(即Square),因此允许这个操作。
而直接给aShape.size = 4报错,是因为TypeScript对联合类型的直接属性访问采用严格模式:只有所有联合成员都存在的属性才能直接赋值/访问,size只属于Square,所以不允许操作。
2. 为什么{unrelated:3}不能通过类型检查?
这是**多余属性检查(Excess Property Checks)**的作用:当你传递字面量对象作为参数时,TypeScript会额外校验对象中是否包含目标类型未定义的属性。Partial<Shape>的两个分支都没有unrelated属性,所以字面量{unrelated:3}会触发错误。
但如果先把对象赋值给变量再传递,多余属性检查就不会生效:
const extra = {unrelated:3}; update(aShape, extra); // 不会报错
3. 为什么加了Omit<Partial<T>, "dont_care">后,{size:3}会报错?
关键在于Omit破坏了泛型的分配性处理逻辑。
先看Omit的底层定义:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
当T是联合类型时,Exclude<keyof T, K>会把联合类型的所有key合并成一个集合(比如keyof Shape是"kind" | "radius" | "size"),再通过Pick生成一个包含所有可选属性的单一对象类型:{kind?: "circle" | "square", radius?: number, size?: number}。
此时TypeScript不再对T做分配性推断,而是把T当成整个Shape联合类型处理。{size:3}只满足Square分支的要求,但无法满足Circle分支(Circle没有size属性),所以会触发和直接赋值一样的严格检查错误。
简单总结:
- 原函数的
Partial<T>对联合类型是分配性的,只要匹配一个分支就通过; - 加了
Omit后,类型变成了包含所有联合成员属性的单一对象,要求参数对所有分支都有效,因此报错。
内容的提问来源于stack exchange,提问作者rodix

