Reputation: 12679
For the following code:
class Generic<T extends string> {
constructor (private input : T) {}
PRINT () {
console.log(this.input)
}
}
class Extended extends Generic {
ANOTHER_PRINT() {
}
}
Is there an easy way to pass the generic for an extended class? I can easily solve it with:
class Generic<T extends string> {
constructor (private input : T) {}
PRINT () {
console.log(this.input)
}
}
class Extended<T extends string> extends Generic<T> {
ANOTHER_PRINT() {
}
}
But then it feels like there's code duplication.
Error is:
Generic type 'Generic<T>' requires 1 type argument(s).
Upvotes: 2
Views: 2832
Reputation: 330161
Since TypeScript doesn't have higher kinded types of the sort requested in microsoft/TypeScript#1213, you can't talk about generic types in the abstract. If you have a generic type/class/interface like Foo<T>
, you can mention it like Foo<string>
or Foo<number>
or Foo<U>
for some other generic type parameter U
, but you cannot just say Foo
to mean the general type function. The easiest and most straightforward way to do what you want is therefore to be redundant and specify the type parameters in both cases:
class ExtendedOne<T extends string> extends Generic<T> { /*..impl..*/ }
The closest you can get to what you want to do here is to take the Generic
constructor value and pass it into a higher order function that extends constructors in its implementation. If you do it right, the compiler can infer a generic constructor in its output and save you from having to manually write out the type parameter. It could look like this:
const Extended = extend(Generic, {
ANOTHER_PRINT() {
console.log("ME ALSO SAY:");
this.PRINT()
}
})
/* const Extended: new <T extends string>(input: T) => Generic<T> & {
ANOTHER_PRINT(): void;
} */
const n = new Extended("hello");
/* const n: Generic<"hello"> & {
ANOTHER_PRINT(): void;
} */
n.PRINT(); // hello
n.ANOTHER_PRINT(); // ME ALSO SAY: hello
You can see that Extended
is "magically" a generic constructor with the same type parameter as Generic
. The implementation of extend
is something like
function extend<A extends any[], R extends object, T>(
parentCtor: new (...args: A) => R,
sub: T & ThisType<R & T>
):
new (...args: A) => R & T {
const c = class extends parentCtor { };
Object.assign(c.prototype, sub);
return c as any;
}
and having A
and R
be generic parameters for the arguments and return type of the constructor function parentCtor
, the compiler can perform its higher kinded type magic. The ThisType<T>
intrinsic utility type gives the compiler a hint that the this
inside of ANOTHER_PRINT
's implementation is the same as the extended class's instance type.
So, this works, but it's a lot of effort and weird-looking code just to avoid writing <T extends string>
and <T>
. There are reasons to want to implement generic constructor-producing functions, but I wouldn't say this is one of them.
And so I still recommend just writing out the type explicitly, despite being a little redundant.
Upvotes: 1