在Typescript中的静态方法中访问类类型参数的解决方法

Mau*_*eys 2 javascript generics inheritance typescript

以下错误

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

以下代码的结果

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
Run Code Online (Sandbox Code Playgroud)

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

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()
Run Code Online (Sandbox Code Playgroud)

我的问题是,重写静态方法确实很繁琐。执行以下操作:

class Model extends Resource {

    public async static fetch(): Promise<Model[]> {
        return super.fetch();
    } 
}
Run Code Online (Sandbox Code Playgroud)

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

我能上班的唯一解决方法如下:

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

我认为这不是很好。

有没有一种方法可以覆盖fetch方法,而不必手动将其强制转换为Model并使用泛型做一些技巧?

jca*_*alz 8

您可以执行以下操作:

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;
}
Run Code Online (Sandbox Code Playgroud)

上面Resource是一个泛型函数,它返回一个本地声明的类。返回的类不是通用的,因此其静态属性和方法具有的具体类型T。您可以像这样扩展它:

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

一切都有您期望的类型:

 Model.list; // Model[]
 Model.fetch(); // Promise<Model[]>
 new Model().save(); // Promise<Model>
Run Code Online (Sandbox Code Playgroud)

这样可能对您有用。

我现在可以看到的唯一警告:

  • 有一点重复class X extends Resource<X>()并不完美,但是我认为您不能通过上下文类型来X推断第二个。

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

无论如何希望能有所帮助。祝好运!

  • @jcalz 我得到一个“错误 TS4060:导出函数的返回类型具有或正在使用私有名称“路径”。”我认为这是因为,如果您没有显式声明函数的返回类型,它将从返回的内容中推断出它。该函数返回一个在函数作用域内定义的类,因此会出现错误。有什么建议吗?谢谢! (4认同)