A have an external array-like interface I want to implement
interface ListOf<T> {
readonly length: number;
readonly [index: number]: T;
item(index: number): T | null;
// note the parent parameter and its type
forEach(callback: (value: T, key: number, parent: ListOf<T>) => void, thisArg?: any): void;
}
The interface looks very similar to normal array, so for implementation I choose to extend the Array
class:
export class SimpleList<T> extends Array<T> implements ListOf<T> {
constructor(arrayLength: number) {
super(arrayLength);
}
item(index: number): T | null {
return this[index] || null;
}
}
But it fails
error TS2420: Class 'SimpleList' incorrectly implements interface 'ListOf'.
Types of property 'forEach' are incompatible.
...
Property 'item' is missing in type 'T[]' but required in type 'ListOf'
The error can easily be fixed by adding forEach
with compatible signature:
// class SimpleList<T>
forEach(callback: (value: T, key: number, parent: SimpleList<T>) => void, thisArg?: any): void {
super.forEach(callback, thisArg);
}
But for me it looks really awkward - I just call super
here, nothing changed at all, basically original method can do the same thing.
How can I tell typescript that original method with its signature actually matches the new one from the interface? I don't want an extra method that just re-declares one of superclass.
Beside the question I have a feeling that original Array
should have declared its forEach
as
forEach(callbackfn: (value: T, index: number, array: /*originally T[]*/ this) => void, thisArg?: any): void;
Not sure if its helpful but might lead to some solution.
It's technically unsafe to do what you're doing, at least according to Array
's declaration. TypeScript thinks that the callback you give to forEach()
might be called with a third argument of an arbitrary Array<T>
instead of this
. I don't know if TypeScript will ever change the Array
method declarations to use this
instead; there was a pull request at microsoft/TypeScript#46781 which has not been merged.
Your workaround of
forEach(callback: (value: T, key: number, parent: SimpleList<T>) => void, thisArg?: any): void {
super.forEach(callback, thisArg); // error!
// ~~~~~~~~
}
also has an error in it, for the same reason. And, as you said, all it does is forward the call to super
, which means it might as well not be overridden.
The approach I'd take here instead is to explicitly narrow the type of SimpleList
's forEach
without touching the emitted JavaScript, by using a declare
modifier. You can't use declare
on methods (see microsoft/TypeScript#38008 and microsoft/TypeScript#48290 for relevant feature requests) so you can rewrite it as a function-valued property instead:
class SimpleList<T> extends Array<T> implements ListOf<T> {
constructor(arrayLength: number) {
super(arrayLength);
}
item(index: number): T | null {
return this[index] || null;
}
declare forEach:
(callback: (value: T, key: number, parent: SimpleList<T>) => void, thisArg?: any) => void;
}