- 此问题在此处已有答案**:
Pick<Foo, Bar> & Omit<Foo, Bar> !== Foo in Typescript?(1个答案)
12小时前关门了。
我发现了一个令人费解的错误,假设我想有一个相当无用的函数,它合并了一个对象的两个互补部分:
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'.
}
我得到了上面的错误,而下面的代码显然工作:
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 }
这个错误试图告诉我,我可以得到一个T
和K
,其中Pick<T, K> & Omit<T, K>
不是T
。有人能给我一个这样的例子吗(或者对上面的错误的任何其他解释)?
编辑1:
更奇怪的是,TS本身认为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'.
}
编辑二:
显然,Pick<T, K> & Omit<T, K>
是T
的子集,因此一定存在T
中存在但Pick<T, K> & Omit<T, K>
中不存在的情况:
function foo<T, K extends keyof T>(x: Pick<T, K> & Omit<T, K>): T {
return x // TS2322: Type 'Pick<T, K> & Omit<T, K>' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'Pick<T, K> & Omit<T, K>'.
}
function bar<T, K extends keyof T>(x: T): Pick<T, K> & Omit<T, K> {
return x // no issue
}
1条答案
按热度按时间tv6aics11#
这里确实存在不合理的可能性,
Pick
和Omit
都实现为mapped types,其结果可能不包含源类型的所有特征。想想这个:
我已经给
Test
一个调用签名,Pick<Test, 'a'>
和Omit<Test, 'a'>
都不会产生带有调用签名的类型,从而导致Pick<Test, 'a'> & Omit<Test, 'a'>
无法赋值给Test
。还有一个问题:当
T
和K
是泛型类型时,编译器无法正确分析Pick<T, K>
等操作的结果。类型T
和K
的作用更像占位符。在调用函数之前,无法知道它们的具体类型。因此编译器对Pick<T, K>
与Omit<T, K>
相交的含义只有有限的理解。您可以在#28884中看到关于类似问题的讨论。Playground