class Registry<Inst, Ctor extends new (...args: unknown[]) => Inst, T extends Readonly<Record<string, Ctor>>> {
  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()
  }
}

class Foo { foo: "foo" }
class Bar { bar: "bar" }

const registry = new Registry({
  foo: Foo,
  bar: Bar
})

const ctor = registry.getCtor('foo') // ctor type is "typeof Foo"
const inst = registry.getInst('foo') // inst type is "unknown", why not "Foo"?

正如最后两行代码中的注释所说,为什么构造函数类型被保留,而实例类型没有被保留?


编辑1:简化代码

推荐答案

第一个问题是您的generic个类型参数太多了. constraint T extends Readonly<Record<string, Ctor>>不充当Ctor的推理点,类似地,约束Ctor extends new (...args: unknown[]) => Inst不充当Inst的推理点.参见microsoft/TypeScript#7234,最终被拒绝. 对CtorInst的推理将失败并退回到约束中.因此,您的代码相当于

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]>,但InstanceTypeconditional 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 typeT来表达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

Typescript相关问答推荐

交叉点对数字和布尔的处理方式不同

如何根据参数的值缩小函数内的签名范围?

有没有办法适当缩小类型?

为什么从数组中删除一个值会删除打字错误?

如何使用泛型自动推断TS中的类型

Next.js错误:不变:页面未生成

如何判断对象是否有重叠的叶子并产生有用的错误消息

表单嵌套太深

如何使用Geojson模块和@Types/Geojson类型解析TypeScrip中的GeoJSON?

有什么方法可以类型安全地包装import()函数吗?

为什么我在这个重载函数中需要`as any`?

判断映射类型内的键值的条件类型

为什么在泛型方法中解析此promise 时会出现类型错误?

如何键入对象转换器

基于泛型的类型脚本修改类型

我可以将一个类型数组映射到TypeScrip中的另一个类型数组吗?

JSX.Element';不可分配给类型';ReactNode';React功能HOC

将带有额外id属性的Rust struct 展平回TypeScript单个对象,以返回wasm-bindgen函数

在类型{}上找不到带有string类型参数的索引签名 - TypeScript

可选通用映射器函数出错