我希望在基类上定义的静态方法(充当构造器)能够自动推断派生类和包装类型。我能推断出其中一种,但不能两者兼而有之。一个简化的示例和一个需要处理的游乐场:
export class Collection<T> {
constructor(private _xs: T[]) {}
static fromArray1<C extends typeof Collection, T>(this: C, xs: T[]): InstanceType<C> {
return new this(xs) as InstanceType<C>;
}
static fromArray2<C extends Collection<T>, T>(this: new (xs: T[]) => C, xs: T[]): C {
return new this(xs);
}
}
export class ExtendedCollection<T> extends Collection<T> {}
// Goal: automatically infer as ExtendedCollection<number>
const ec1 = ExtendedCollection.fromArray1([1, 2, 3]);
const ec2 = ExtendedCollection.fromArray2([1, 2, 3]);发布于 2022-10-31 02:53:08
实现目标的主要障碍是在TypeScript中缺乏对更高类型类型的直接支持,这是微软/打字稿#1213中所要求的类型。现在您可以构建像type Gen<T extends Foo> = ...这样的泛型类型,其中类型参数T表示某些特定类型,但是您不能对此进行抽象,以生成一个更高的泛型类型,比如type Higher<F<~> extends Gen<~>> = ...,其中类型参数F表示某些泛型类型,它本身带有一个类型参数,比如Gen。
如果你可以的话,也许你就能写
// NOT VALID TS, DON'T TRY THIS:
static fromArray<C<~> extends Collection<~>, T>(
this: new (...args: any) => C<any>,
xs: T[]
): C<T>;并将泛型类型参数C“应用”到类型T以生成C<T>。但这不是直接支持的,所以我们必须绕开它。
这个问题,microsoft/TypeScript#1213,提到了几种解决方法。它们目前都需要一些需要维护的样板代码。例如,如果您愿意为每个定义的子类将属性合并到接口中,您可以定义一个“应用”合并类型函数:
export class Collection<T> {
constructor(private _xs: T[]) { }
static fromArray<C extends typeof Collection, T>(this: C, xs: T[]): Apply<C, T> {
return new this(xs) as any;
}
x = 0
}
interface HKT<T> { Collection: Collection<T> }
type Apply<C extends new (...args: any) => any, T> =
{ [K in keyof HKT<any>]:
HKT<any>[K] extends InstanceType<C> ?
InstanceType<C> extends HKT<any>[K] ?
HKT<T>[K] : never : never }[keyof HKT<any>]从上面可以看出,Apply<typeof Collection, number>将生成Collection<number>。然后,对于子类,您可以在另一个属性中合并:
export class ExtendedCollection<T> extends Collection<T> {
y = 1
}
// manually merge in this
interface HKT<T> { ExtendedCollection: ExtendedCollection<T> }现在,Apply<typeof ExtendedCollection, string>将生产ExtendedCollection<number>。因此,您的示例将按需要工作:
const ec1 = ExtendedCollection.fromArray([1, 2, 3]);
// const ec1: ExtendedCollection<number>
ec1.y在不尝试模拟更高类型的情况下,我唯一能想到的其他解决方法就是放弃抽象"apply C to T“,而只是手动缩小每个子类,从而使其方法对自己有硬编码的引用:
export class Collection<T> {
constructor(private _xs: T[]) { }
static fromArray<T>(xs: T[]): Collection<T> {
return new this(xs) as any;
}
x = 0
}这就是基类,然后子类只会适当地标记它们的方法类型,而不实际修改它们:
export class ExtendedCollection<T> extends Collection<T> {
static fromArray: <T>(xs: T[]) => ExtendedCollection<T>;
y = 1;
}同样,该示例的工作原理是:
const ec1 = ExtendedCollection.fromArray([1, 2, 3]);
// const ec1: ExtendedCollection<number>
ec1.y显然,这两种方法都不是完美的。也许还有其他更适合你需求的解决办法,但是除非微软/类型编号1213被实现,否则我认为没有任何完全没有痛苦的方法可以做到。
https://stackoverflow.com/questions/74253853
复制相似问题