如何获取泛型构造函数的参数



我有一个类Collection,它接受另一个类作为参数personClass。 我想方法add应该接受包含在类User构造函数中的参数

class Person {
constructor(public data: object) {
}
}
type GetConstructorParams<T> = T extends {
new (...params: infer Args): any
} ? Args : never
type GetConstructor<Instance> = new (...params: GetConstructorParams<Instance>) => Instance
class Collection<
Instance extends Person
> {
personClass: GetConstructor<Instance>
items: Instance[] = []
constructor(personClass: GetConstructor<Instance>) {
this.personClass = personClass
}
add(...params: GetConstructorParams<Instance>) {
const instance = new this.personClass(...params)
this.items.push(
instance
)
}
}
class User extends Person {
data: {
name: string
}
constructor(name: string) {
super({name})
}
get name() {
return this.data.name
}
}
const collection = new Collection(User)
collection.add('123')
^^^ TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.

然后我将尝试将 personClass 作为可选参数。默认情况下,personClass 应等于Person

如何避免此方法中的错误collection.add('123')

TypeScript 中的类实例类型不保留对其构造函数类型的引用。 请考虑以下类:

class Foo {
a: number;
constructor(a: number) {
this.a = a;
}
}
let f: Foo = new Foo(123);

f被批注为类型Foo。 但是,您应该将类型Foo视为描述类实例形状的interface,而不是表示"由Foo构造函数构造"。 实际上,您可以将相同形状的值分配给f,而无需由 al 的Foo构造:

f = { a: 456 }; // <-- also acceptable

如果您查看 microsoft/TypeScript#3841,您会发现即使是类实例的constructor属性也不够强类型,无法确定有关实际构造函数的任何内容。

因此,无法查看类型Foo并确定名为Foo的构造函数是否具有类型number的构造函数参数。 从实例类型转到构造函数类型而不丢失信息是不可能的。GetConstructor<I>永远不会按照你想要的方式工作。 无法从名为User的类型转到名为typeof User的类型。


另一方面,构造函数类型肯定知道它们构造的实例。 因此,给定类型typeof UserUser构造函数,您可以获取其实例类型。 甚至还为您提供了InstanceType<T>实用程序类型。

此外,由于您关心的只是实例类型和构造函数参数列表,因此您可以直接在这些类型中使Collection泛型,而不必关心构造函数类型本身。 假设构造函数参数类型是类似数组的类型A,实例类型是类似Person的类型T。 然后是Collection的样子:

class Collection<A extends any[], T extends Person>{
personClass: new (...args: A) => T;
items: T[] = []
constructor(personClass: new (...args: A) => T) {
this.personClass = personClass
}
add(...params: A) {
const instance = new this.personClass(...params)
this.items.push(
instance
)
}
}

这一切都编译没有问题。 然后,您可以传入User并查看所有内容都按预期工作:

class User extends Person {
data!: {
name: string
}
constructor(name: string) {
super({ name })
}
get name() {
return this.data.name
}
}
const collection = new Collection(User)
// const collection: Collection<[name: string], User>
collection.add('123'); // okay

collection的类型是Collection<[name: string], User>,对应于User构造函数采用单个string参数并生成类型User的实例。

操场链接到代码

最新更新