英文:
Is it possible to reference an implementing type in an abstract class method type in TypeScript?
问题
在这个示例中,我想让testWithTypeParam
返回一个类型为{beans: {someParam: string}}
的对象,以与Thingymabob
上的feedMeBeans
方法兼容。然而,似乎完全不可能实现这一点:我无法找到一种方法来做到这一点。我尝试过使用多态的this
类型,如示例代码中所示,但在这方面没有取得任何进展:似乎完全没有任何区别。
interface MyStuff {
[name: string]: {someParam: string}
};
type Something<T> = {
thing: T;
}
abstract class Thingydoo {
// 在这里 - 在这一行上 - TypeScript 使用 "this" 意味着 Thingydoo,而不是从它派生的 Thingymabob
static testWithTypeParam(thing: ReturnType<typeof this.differentFunc>): Something<ReturnType<typeof this.differentFunc>> {
// 在这里,TypeScript _知道_ this.name 是 Thingymabob,而不是 Thingydoo。
console.log("hi! this is a " + this.name);
return {thing: thing};
}
static differentFunc(): MyStuff {
return {};
}
}
class Thingymabob extends Thingydoo {
static differentFunc(): {beans: {someParam: string}} {
return {beans: {someParam: "test"}};
}
static feedMeBeans(beansContainer: {beans: {someParam: string}}) {
console.log(beansContainer.beans.someParam);
}
}
let something = Thingymabob.testWithTypeParam({beans: {someParam: "test3"}});
// 所以,尽管在JS中有意义,编译到JS也没有问题,而且在JS中运行也没有问题,但在tsc中会出现编译器错误,因为它没有意识到beans的类型与MyStuff兼容。
Thingymabob.feedMeBeans(something.thing);
我是否漏掉了一些明显的东西?
注意: 以上只是代码的一部分,其中包括了您希望翻译的内容。如果您有任何其他问题或需要更多帮助,请随时提出。
英文:
I have an abstract class Thingydoo
, and a class Thingymabob
that extends it. I have some static methods on Thingydoo
that I want to create something of a type that exists in Thingymabob
. The underlying reason I'm trying to do this is in order to give an API for different types of Thingydoo
to be created with different options for each of them programmatically.
interface MyStuff {
[name: string]: {someParam: string}
};
type Something<T> = {
thing: T;
}
abstract class Thingydoo {
// here - on this line - TypeScript uses "this" to mean Thingydoo, not the Thingymabob that comes from it
static testWithTypeParam(thing: ReturnType<typeof this.differentFunc>): Something<ReturnType<typeof this.differentFunc>> {
// here, in this line, TypeScript _knows_ that this.name is Thingymabob, not Thingydoo.
console.log("hi! this is a " + this.name);
return {thing: thing};
}
static differentFunc() : MyStuff {
return {};
}
}
class Thingymabob extends Thingydoo {
static differentFunc(): {beans: {someParam: string}} {
return {beans: {someParam: "test"}};
}
static feedMeBeans(beansContainer: {beans: {someParam: string}}) {
console.log(beansContainer.beans.someParam);
}
}
let something = Thingymabob.testWithTypeParam({beans: {someParam: "test3"}});
// so this, despite making sense and compiling to JS fine + running fine in JS, issues a compiler error in tsc, because it doesn't realise the beans type is MyStuff compatible.
Thingymabob.feedMeBeans(something.thing);
In this example, I want testWithTypeParam
to return an object of type {beans: {someParam: string}}
, so as to be compatible with the feedMeBeans
method on Thingymabob
. This doesn't seem to want to be possible at all, though: I can't find a way of doing it. I've tried using the polymorphic this
types, as shown in the example code, but have made no headway at all with that route: it seems to make absolutely no difference (playground).
Am I missing something obvious?
答案1
得分: 1
TypeScript不直接支持多态的this
类型用于static
成员。对于这个问题已经有一个长期存在的开放功能请求,可以在microsoft/TypeScript#5863找到,但它还没有被实现(尚未?)。在那之前,你需要解决这个问题。
对于静态方法,一个常见的解决方法是使方法成为泛型,其类型为T
,并且受限于你希望this
成为的某个超类型(如果你的类名是Foo
,可能是typeof Foo
,但不一定是),然后给方法一个this
参数,类型为T
。然后(这可能是你忽略的部分),你在类型this
的位置使用类型T
。所以,不是:
class Foo {
⋯
static method(arg: A<this>): R<this> {⋯}
⋯
}
而是:
class Foo {
⋯
static method<T extends typeof Foo>(this: T, arg: F<T>): R<T> {⋯}
⋯
}
对于你的代码,看起来是这样的:
abstract class Thingydoo {
static testWithTypeParam<T extends typeof Thingydoo>(
this: T, thing: ReturnType<T["differentFunc"]>
): Something<ReturnType<T["differentFunc"]>> {
console.log("hi! this is a " + this.name);
return { thing: thing };
}
static differentFunc(): MyStuff {
return {};
}
}
注意,T
是一个类型,不是一个值,所以你不能写成typeof T.differentFunc
。相反,你想要的是“T
的属性,其键为"differentFunc"
的类型”,你可以通过索引访问类型(indexed access type)T["differentFunc"]
来获得。
现在它可以按预期工作了:
let something = Thingymabob.testWithTypeParam({ beans: { someParam: "test3" } });
// let something: Something<{ beans: { someParam: string; }; }>
Thingymabob.feedMeBeans(something.thing); // okay
英文:
TypeScript doesn't directly support the polymorphic this
type for static
members. There's a longstanding open feature request for it at microsoft/TypeScript#5863, but it hasn't been implemented (yet?). Until and unless that happens you'll have to work around it.
For static methods a common workaround is to make the method generic in a type T
constrained to some supertype of what you want this
to be (it might be typeof Foo
if your class name is Foo
, but it doesn't have to be), and then give the method a this
parameter of type T
. And then (in this might be the part you were missing) you use the type T
in place of the type this
. So instead of
class Foo {
⋯
static method(arg: A<this>): R<this> {⋯}
⋯
}
you'd have
class Foo {
⋯
static method<T extends typeof Foo>(this: T, arg: F<T>): R<T> {⋯}
⋯
}
For your code that looks like
abstract class Thingydoo {
static testWithTypeParam<T extends typeof Thingydoo>(
this: T, thing: ReturnType<T["differentFunc"]>
): Something<ReturnType<T["differentFunc"]>> {
console.log("hi! this is a " + this.name);
return { thing: thing };
}
static differentFunc(): MyStuff {
return {};
}
}
Note that T
is a type, not a value, so you can't write typeof T.differentFunc
. Instead you want "the type of the property of T
whose key is "differentFunc"
", which you get via the indexed access type T["differentFunc"]
.
And now it works as desired:
let something = Thingymabob.testWithTypeParam({ beans: { someParam: "test3" } });
// let something: Something<{ beans: { someParam: string; }; }>
Thingymabob.feedMeBeans(something.thing); // okay
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论