Javascript TypeScript类工厂需要一个交集
我有下面的类工厂Javascript TypeScript类工厂需要一个交集,javascript,typescript,Javascript,Typescript,我有下面的类工厂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] =
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;
A类{
keya=“a”作为常量;
}
B类{
keyb=“b”作为常量;
}
类型类映射={
a:a
b:b
}
常量pickSomething=(键:K):类映射[K]=>{
开关(钥匙){
案例“a”:
返回新的A();//错误:A不能分配给A&B
案例“b”:
return new B();//错误:B不能分配给A&B
}
抛出新错误();
}
//它的外观很好
常数a=pickSomething('a')。keya;
常数b=pickSomething('b').keyb;
它在外部工作正常(正如您从
const a=pickSomething('a').keya;
中看到的)。这意味着外部ClassMap[K]
正在映射到正确的实例(A
或B
,具体取决于传入的键
)。但是在内部,我在每个return语句上都会得到一个错误。TypeScript期望ClassMap[K]
表示A&B
。有没有更好的类型注释(不诉诸类型断言)来解决这个问题?我认为一般的问题是,TypeScript不会像通常对特定联合类型的值那样,通过控制流分析来缩小扩展联合的类型参数。请参阅以进行讨论。您已经检查了键
是“a”
还是“b”
,并且键
的类型是K
,但这对K
本身没有影响。由于编译器不知道K
比“a”|“b”
窄,所以它不知道ClassMap[K]
可以比a&b
宽。(,写入键并集上的查找属性需要属性的交集;请参见。)
从技术上讲,编译器拒绝进行这种缩小是正确的,因为没有任何东西可以阻止类型参数K
被指定为完全联合类型“a”|“b”
,即使您检查它:
pickSomething(Math.random() < 0.5 ? "a" : "b"); // K is "a" | "b"
但产生的JavaScript至少是惯用的
其他可能性:编译器允许您通过在类型为
T
的对象上实际查找键类型为K
的属性来返回查找属性类型T[K]
。您可以重构代码来实现这一点:
const pickSomething = <K extends keyof ClassMap>(key: K): ClassMap[K] => {
return {
a: new A(),
b: new B()
}[key];
}
它编译时不会出错,并且是类型安全的。但这是奇怪的代码,所以我不知道是否值得。目前,我认为类型断言是正确的方法。希望在某个时候,会有一个更好的解决方案,使您的原始代码可以在不需要断言的情况下工作
如果你一定要问,可能没有。但我们可能会在TypeScript的问题追踪器中找到一些reportson…这不是另一个问题吗?TypeScript不知道
K
没有实例化为'a'|'b'
。这种情况下K
可能比单个键更宽。理论上,您可以调用pickSomething('b')
,但返回类型无效。实际上,你不会这么做的。所以只要使用断言就行了。我想重载可能会有所帮助,但这不会使用ClassMap
编写as ClassMap[K]
比as A&B
更能证明未来
const pickSomething = <K extends keyof ClassMap>(key: K): ClassMap[K] => {
return {
a: new A(),
b: new B()
}[key];
}
const pickSomething = <K extends keyof ClassMap>(key: K): ClassMap[K] => {
return {
get a() { return new A() },
get b() { return new B() }
}[key];
}