第一个问题是您的generic个类型参数太多了. constraint T extends Readonly<Record<string, Ctor>>
不充当Ctor
的推理点,类似地,约束Ctor extends new (...args: unknown[]) => Inst
不充当Inst
的推理点.参见microsoft/TypeScript#7234,最终被拒绝. 对Ctor
和Inst
的推理将失败并退回到约束中.因此,您的代码相当于
class Registry<T extends Readonly<Record<string, new (...args: unknown[]) => unknown>>> {}
因此,无论如何,您的输出中都没有地方出现Inst
.
我可能会把上面的内容改为
class Registry<T extends { [K in keyof T]: new () => object }> { }
(Readonly
或允许任意构造函数参数,甚至Record<string,
似乎没有太大意义,最终会禁止接口),但基本上是一样的.
问题是,当您拥有generic中的ctor
类型T[K]
时,编译器不明白如何generically解释new ctor()
.您希望它会以某种方式得到InstanceType<T[K]>
,但InstanceType
是conditional type,并且编译器无法"明白"它的意思.相反,new ctor
会导致编译器将ctor
扩展到其约束new () => object
,而您只得到object
(或unknown
或其他).
最简单的修复方法是仅将assert个返回类型与您希望的相同:
class Registry<T extends { [K in keyof T]: new () => object }> {
constructor(public records: T) { }
getCtor<K extends keyof T>(key: K) {
return this.records[key]
}
getInst<K extends keyof T>(key: K) {
const ctor = this.records[key]
return new ctor() as InstanceType<T[K]> // assert
}
}
这是可行的,但需要您承担类型判断的责任,而不是编译器.
一个更好的方法是使类不是在records
类型中通用,而是在实例类型的相应对象中通用.所以{foo: Foo, bar: Bar}
而不是{foo: typeof Foo, bar: typeof Bar}
. 那么我们可以用mapped type用T
来表达records
:
class Registry<T extends { [K in keyof T]: object }> {
constructor(public records: { [K in keyof T]: new () => T[K] }) { }
getCtor<K extends keyof T>(key: K) {
return this.records[key]
}
getInst<K extends keyof T>(key: K) {
const ctor = this.records[key]
return new ctor();
}
}
一旦您这样做,那么getCtor()
返回new () => T[K]
,而getInst()
返回T[K]
(因为new ctor()
将new
应用于类型new () => T[K]
,而编译器does"获取"了该类型. 这会给你想要的行为:
class Foo { foo = "foo" }
class Bar { bar = "bar" }
const registry = new Registry({
foo: Foo,
bar: Bar
})
const ctor = registry.getCtor('foo') // new () => Foo
const inst = registry.getInst('foo') // Foo
Playground link to code