在TypeScript中,是否可以在抽象类方法类型中引用一个实现类型?

huangapple go评论109阅读模式
英文:

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&lt;T&gt; = {
    thing: T;
}

abstract class Thingydoo {
    //                  here - on this line - TypeScript uses &quot;this&quot; to mean Thingydoo, not the Thingymabob that comes from it
    static testWithTypeParam(thing: ReturnType&lt;typeof this.differentFunc&gt;): Something&lt;ReturnType&lt;typeof this.differentFunc&gt;&gt; {
        // here, in this line, TypeScript _knows_ that this.name is Thingymabob, not Thingydoo.
        console.log(&quot;hi! this is a &quot; + this.name);
        return {thing: thing};
    }

    static differentFunc() : MyStuff {
        return {};
    }
}

class Thingymabob extends Thingydoo {
    static differentFunc(): {beans: {someParam: string}} {
        return {beans: {someParam: &quot;test&quot;}};
    }

    static feedMeBeans(beansContainer: {beans: {someParam: string}}) {
        console.log(beansContainer.beans.someParam);
    }
}

let something = Thingymabob.testWithTypeParam({beans: {someParam: &quot;test3&quot;}});
// so this, despite making sense and compiling to JS fine + running fine in JS, issues a compiler error in tsc, because it doesn&#39;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 typeT["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&lt;this&gt;): R&lt;this&gt; {⋯} 
  ⋯
}

you'd have

class Foo { 
  ⋯
  static method&lt;T extends typeof Foo&gt;(this: T, arg: F&lt;T&gt;): R&lt;T&gt; {⋯} 
  ⋯
}

For your code that looks like

abstract class Thingydoo {
    
    static testWithTypeParam&lt;T extends typeof Thingydoo&gt;(
        this: T, thing: ReturnType&lt;T[&quot;differentFunc&quot;]&gt;
    ): Something&lt;ReturnType&lt;T[&quot;differentFunc&quot;]&gt;&gt; {
        console.log(&quot;hi! this is a &quot; + 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 &quot;differentFunc&quot;", which you get via the indexed access type T[&quot;differentFunc&quot;].

And now it works as desired:

let something = Thingymabob.testWithTypeParam({ beans: { someParam: &quot;test3&quot; } });
// let something: Something&lt;{ beans: { someParam: string; }; }&gt;
Thingymabob.feedMeBeans(something.thing); // okay

Playground link to code

huangapple
  • 本文由 发表于 2023年6月5日 23:41:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76408033.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定