我有以下类工厂pickSomething
根据从 ClassMap
传入的键创建类型:
class A {
keya = "a" as const;
}
class B {
keyb = "b" as const;
}
type ClassMap = {
a: A
b: B
}
const pickSomething = <K extends keyof ClassMap>(key: K): ClassMap[K] => {
switch (key) {
case 'a':
return new A(); // Error: A is not assignable to A & B
case 'b':
return new B(); // Error: B is not assignable to A & B
}
throw new Error();
}
// It works fine externally
const a = pickSomething('a').keya;
const b = pickSomething('b').keyb;
它在外部工作得很好(正如您从 const a = pickSomething('a').keya;
中看到的那样)。这意味着外部 ClassMap[K]
正在映射到正确的实例( A
或 B
取决于传入的 key
)。但是在内部,我在每个 return 语句上都收到一个错误。 TypeScript 需要 ClassMap[K]
表示A & B
.有没有办法用更好的类型注释来解决它(不诉诸类型断言)?
最佳答案
我认为普遍的问题是 TypeScript 不会像通常对特定联合类型的值那样通过控制流分析来缩小扩展联合的类型参数。见 microsoft/TypeScript#24085供讨论。您已经检查了 key
是 "a"
或 "b"
, 和 key
是 K
类型, 但这对 K
没有影响本身。而且由于编译器不知道 K
是比 "a" | "b"
更窄的任何东西, 它不知道 ClassMap[K]
可以是比 A & B
更宽的任何值. ( Since TypeScript 3.5 ,写入键联合上的查找属性需要属性的交集;参见 microsoft/TypeScript#30769 。)
从技术上讲,编译器拒绝做这种缩小是正确的,因为没有什么能阻止类型参数 K
从被指定为完整联合类型 "a" | "b"
,即使您检查它:
pickSomething(Math.random() < 0.5 ? "a" : "b"); // K is "a" | "b"
目前没有办法告诉编译器你不是真的意思K extends "a" | "b"
,而是类似于 K extends "a"
或 K extends "b"
;也就是说,不是对联合的约束,而是约束的联合。如果你能表达出来,也许可以检查 key
缩小 K
本身,然后理解,例如,ClassMap[K]
只是 A
当key
是 "a"
.见 microsoft/TypeScript#27808和 microsoft/TypeScript#33014对于那里的相关功能请求。由于这些都没有实现,让你的代码以最少的更改编译的最简单方法是使用类型断言。当然,它不是完全类型安全的:
const pickSomething = <K extends keyof ClassMap>(key: K): ClassMap[K] => {
switch (key) {
case 'a':
return new A() as A & B
case 'b':
return new B() as A & B
}
throw new Error();
}
但至少生成的 JavaScript 是惯用的。其他可能性:编译器确实允许您返回查找属性类型
T[K]
通过实际查找键类型 K
的属性在 T
类型的对象上.你可以重构你的代码来做到这一点:const pickSomething = <K extends keyof ClassMap>(key: K): ClassMap[K] => {
return {
a: new A(),
b: new B()
}[key];
}
如果您不想实际创建 new A()
和 new B()
每次调用pickSomething
, 你可以使用 getters相反,这样实际上只遵循所需的代码路径:const pickSomething = <K extends keyof ClassMap>(key: K): ClassMap[K] => {
return {
get a() { return new A() },
get b() { return new B() }
}[key];
}
这编译没有错误并且是类型安全的。但这是奇怪的代码,所以我不知道它是否值得。我认为类型断言目前是正确的方法。希望在某个时候,microsoft/TypeScript#24085 会有更好的解决方案。这使您的原始代码无需断言即可工作。Playground link to code
关于javascript - TypeScript 类工厂需要一个交集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65947409/