为何类型'Pick<T, K> & Omit<T, K>'无法赋值给类型'T'?
TypeScript泛型合并对象的类型错误解析
我编写了一个用于合并对象两个互补部分的泛型函数,却遇到了TypeScript类型错误,代码如下:
function foo<T extends object, K extends keyof T>(a: Pick<T, K>, b: Omit<T, K>): T { return { ...a, ...b } // TS2322: Type 'Pick<T, K> & Omit<T, K>' is not assignable to type 'T'. 'Pick<T, K> & Omit<T, K>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'object'. }
错误提示指出,存在某些T和K的实例,使得Pick<T, K> & Omit<T, K>不等于T。但下面的具体实例却能正常编译:
interface Test { a: string, b: string } const a: Pick<Test, 'a'> = { a: 'a' } const b: Omit<Test, 'a'> = { b: 'b' } const t: Test = { ...a, ...b }
反例演示:Pick<T, K> & Omit<T, K>≠T的场景
场景1:带有只读属性的类型
当T包含只读属性时,合并后的对象会丢失只读约束:
interface ReadonlyTest { readonly a: string; b: string; } const partialA: Pick<ReadonlyTest, 'a'> = { a: 'a' }; const partialB: Omit<ReadonlyTest, 'a'> = { b: 'b' }; // 此处会报错:合并后的对象中a属性是可写的,无法赋值给ReadonlyTest const result: ReadonlyTest = { ...partialA, ...partialB };
此时Pick<ReadonlyTest, 'a'> & Omit<ReadonlyTest, 'a'>的实际类型是{ a: string; b: string },和ReadonlyTest(带有readonly约束)并不等价。
场景2:带有额外约束的子类型
如果T是一个带有额外属性约束的子类型,合并后的对象无法满足该约束:
// 要求对象不能有c属性(c的类型为never) type ExactTest = { a: string; b: string } & { readonly c?: never }; const partialA: Pick<ExactTest, 'a'> = { a: 'a' }; const partialB: Omit<ExactTest, 'a'> = { b: 'b' }; // 合并后的对象没有c属性相关约束,无法赋值给ExactTest const result: ExactTest = { ...partialA, ...partialB };
编辑1:条件返回类型仍报错的原因
即使TypeScript认可T extends Pick<T, K> & Omit<T, K>的关系,将返回类型改为条件类型后依然报错:
function foo<T extends object, K extends keyof T>(a: Pick<T, K>, b: Omit<T, K>): T extends Pick<T, K> & Omit<T, K> ? T : any { const res: Pick<T, K> & Omit<T, K> = { ...a, ...b } return res // TS2322: Type 'Pick<T, K> & Omit<T, K>' is not assignable to type 'T extends Pick<T, K> & Omit<T, K> ? T : any'. }
这是因为泛型上下文里的条件类型是延迟解析的,TypeScript无法在编译期确认Pick<T, K> & Omit<T, K>能直接匹配条件类型的结果,即使逻辑上两者存在包含关系。
编辑2:类型关系的本质
从以下两个函数可以明确T和Pick<T, K> & Omit<T, K>的关系:
function foo<T, K extends keyof T>(x: Pick<T, K> & Omit<T, K>): T { return x // 报错:Pick<T, K> & Omit<T, K>无法赋值给T } function bar<T, K extends keyof T>(x: T): Pick<T, K> & Omit<T, K> { return x // 无报错:T可以赋值给Pick<T, K> & Omit<T, K> }
这说明Pick<T, K> & Omit<T, K>是T的超类型:所有T的实例都属于Pick<T, K> & Omit<T, K>,但存在Pick<T, K> & Omit<T, K>的实例不属于T(比如前面的只读属性例子)。
内容的提问来源于stack exchange,提问作者kataik
相关产品推荐
相关产品推荐

