【问题标题】:Workaround for accessing class type arguments in static method in Typescript在 Typescript 的静态方法中访问类类型参数的解决方法
【发布时间】:2018-09-26 12:47:31
【问题描述】:

以下错误

静态成员不能引用类类型参数。

以下代码的结果

abstract class Resource<T> {
    /* static methods */
    public static list: T[] = [];
    public async static fetch(): Promise<T[]> {
        this.list = await service.get();
        return this.list;
    }
    /*  instance methods */ 
    public save(): Promise<T> {
        return service.post(this);
    }
}

class Model extends Resource<Model> {
}

/* this is what I would like, but the because is not allowed because :
"Static members cannot reference class type parameters."
*/

const modelList = await Model.fetch() // inferred type would be Model[]
const availableInstances = Model.list // inferred type would be Model[]
const savedInstance = modelInstance.save() // inferred type would be Model

我认为从这个例子中可以清楚地看到我想要实现的目标。我希望能够在我的继承类上调用实例和静态方法,并将继承类本身作为推断类型。我找到了以下解决方法来获得我想要的:

interface Instantiable<T> {
    new (...args: any[]): T;
}
interface ResourceType<T> extends Instantiable<T> {
    list<U extends Resource>(this: ResourceType<U>): U[];
    fetch<U extends Resource>(this: ResourceType<U>): Promise<U[]>;
}

const instanceLists: any = {} // some object that stores list with constructor.name as key

abstract class Resource {
    /* static methods */
    public static list<T extends Resource>(this: ResourceType<T>): T[] {
        const constructorName = this.name;
        return instanceLists[constructorName] // abusing any here, but it works :(
    }
    public async static fetch<T extends Resource>(this: ResourceType<T>): Promise<T[]> {
        const result = await service.get()
        store(result, instanceLists) // some fn that puts it in instanceLists
        return result;
    }
    /*  instance methods */ 
    public save(): Promise<this> {
        return service.post(this);
    }
}
class Model extends Resource {
}
/* now inferred types are correct */
const modelList = await Model.fetch() 
const availableInstances = Model.list 
const savedInstance = modelInstance.save()

我遇到的问题是覆盖静态方法变得非常乏味。执行以下操作:

class Model extends Resource {

    public async static fetch(): Promise<Model[]> {
        return super.fetch();
    } 
}

将导致错误,因为Model 不再正确扩展Resource,因为签名不同。我想不出一种方法来声明一个 fetch 方法而不给我错误,更不用说有一种直观简单的重载方法了。

我可以开始工作的唯一解决方法是:

class Model extends Resource {
    public async static get(): Promise<Model[]> {
        return super.fetch({ url: 'custom-url?query=params' }) as Promise<Model[]>;
    }
}

在我看来,这不是很好。

有没有一种方法可以覆盖 fetch 方法,而不必手动转换为 Model 并使用泛型进行技巧?

【问题讨论】:

    标签: javascript typescript generics inheritance


    【解决方案1】:

    你可以这样做:

    function Resource<T>() {
      abstract class Resource {
        /* static methods */
        public static list: T[] = [];
        public static async fetch(): Promise<T[]> {
          return null!;
        }
        /*  instance methods */
        public save(): Promise<T> {
          return null!
        }
      }
      return Resource;
    }
    

    在上面的Resource 是一个泛型函数,它返回一个locally declared class。返回的类不是泛型的,因此它的静态属性和方法具有T 的具体类型。你可以像这样扩展它:

    class Model extends Resource<Model>() {
      // overloading should also work
      public static async fetch(): Promise<Model[]> {
        return super.fetch();
      }
    }
    

    一切都有你期望的类型:

     Model.list; // Model[]
     Model.fetch(); // Promise<Model[]>
     new Model().save(); // Promise<Model>
    

    所以这可能对你有用。

    我现在能看到的唯一警告:

    • class X extends Resource&lt;X&gt;() 中存在一些重复,这并不完美,但我认为您无法通过上下文输入来推断第二个 X

    • 本地声明的类型往往不可导出或用作声明,因此您可能需要小心或想出解决方法(例如,导出一些结构相同或结构足够接近的类型并声明Resource 是那种类型?)。

    无论如何希望这会有所帮助。祝你好运!

    【讨论】:

    • 这确实很有趣!但是,是的,我觉得我可能会遇到本地声明的类型导出问题。在回到这里/接受您的答案之前,我需要进一步调查该部分 :-) 在旁注中,对我来说,打字稿人应该只允许在静态方法中访问类型参数。
    • 不幸的是,让静态端从实例端访问类型参数是没有意义的。在class A&lt;T&gt; 中,构造函数A 需要能够为任何T 创建A&lt;T&gt;。这意味着从静态方面的角度来看,T 不是具体类型。您可以将A 的静态方面想成interface AStatic {new&lt;T&gt;(): AInstance&lt;T&gt;}。如果您将其他方法添加到AStatic,那么它们从new 访问T 根本没有意义。 T 的范围不存在。
    • "在class A&lt;T&gt; 中,构造函数A 需要能够为任何T 创建一个A&lt;T&gt;" => 如果我们有abstract class A&lt;T&gt;,就不行,对吧? Also, this these kind of patterns are perfectly normal in languages like C#(我知道,由于原型继承,它与 javascript 不同)。 F-bound 多态性....听起来很有趣,我知道
    • 我认为为了让它工作,以便静态和实例端可以访问相同的类型参数,需要有一个静态端为每个实例端。在 C++(也许是 C#?)中,这是可行的,因为模板元编程实际上为类型参数的每个实例化生成新代码。在 Java 和 TypeScript 中,您不能这样做,因为每个类的类型系统中只有一个实际的静态端。我可以想象改变它,但它不知道这样的建议是否会受到欢迎。
    • @jcalz 我得到一个error TS4060: Return type of exported function has or is using private name 'Paths'. 我想这是因为,如果你没有明确说明函数的返回类型,它会从它返回的内容中推断出来。此函数返回在函数范围内定义的类,因此会出现错误。有什么建议吗?谢谢!
    猜你喜欢
    • 2020-11-29
    • 2020-05-07
    • 2023-03-07
    • 1970-01-01
    • 2011-08-19
    • 1970-01-01
    • 2010-11-24
    • 1970-01-01
    • 2013-09-04
    相关资源
    最近更新 更多