As you have encountered, the static side of a class
has no access to any of the instance side's generic type parameters. In some sense it is not generally meaningful to allow such access, because there is a single constructor and multiple instances... a class constructor like class Foo<T> {}
has a type like new() => Foo<T>
; a single constructor needs to be able to create a Foo<T>
for any possible T
. So the constructor itself has no specific T
that it can access.
There is a feature request at microsoft/TypeScript#34665 to allow such access inside the type signature for abstract static
methods, should TypeScript ever get them. Right now, neither abstract static
methods nor static access to instance type parameters are permitted. So you can't do this directly.
The obvious solution here is to make testSettings()
an instance method, but you can't do that for whatever reason.
The next possible way forward is to make a generic factory function which spits out non-generic classes. This is the solution presented in this SO answer. In your case it looks like this:
function BaseProvider<T extends ProviderType>(type: T) {
abstract class BaseProvider {
constructor(protected settings: SettingsMap[T]) {
}
static testSettings?(opts: TestSettingsOptions<T>) {
throw new Error('Method not implemented');
}
}
return BaseProvider;
}
The type parameter T
is in scope everywhere inside the implementation of the BaseProvider
function, including the static side of the locally declared class that gets returned. Note that the type
parameter passed in is used only to help the compiler infer T
; I'm not using the value anywhere. But you could, if you wanted to.
And now your subclasses won't extend BaseProvider
, but the output of BaseProvider
called on whatever enum type you want:
class ProviderA extends BaseProvider(ProviderType.TypeA) {
constructor(protected settings: SettingsTypeA) {
super(settings);
}
static testSettings(opts: TestSettingsOptions<ProviderType.TypeA>) {
// do some testing
}
}
class ProviderB extends BaseProvider(ProviderType.TypeB) {
constructor(protected settings: SettingsTypeB) {
super(settings);
}
static testSettings(opts: TestSettingsOptions<ProviderType.TypeB>) {
// do some testing
}
}
All of that now compiles. Of course there are caveats. The ones I can think of:
something like instanceof BaseProvider
will no longer work; not even instanceof BaseProvider(ProviderType.TypeA)
will work, because there is no unique class constructor anymore.
if you need to export BaseProvider
declarations in a .d.ts file, you'll need to do extra work to annotate types; function-local classes tend not to be exportable cleanly.
Playground link to code
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…