I'm using TypeScript and I want to extend Array
to enforce a specific argument. This way, I can make sure that whenever I create a FruitArray, the first element is a fruit.
So I tried extending Array
in a class:
class FruitArray extends Array {
constructor(fruit: FruitsEnum, ...args: string[]) {
if (!enumContains(FruitsEnum, fruit)) {
throw new Error('Must initialize with a fruit!');
}
super(fruit, ...args);
}
}
However, the linter complains:
A spread argument must either have a tuple type or be passed to a rest parameter.
I think I am handling super()
wrong, but I don't understand how to get it to work.
First I'll address the problem you asked about, but then point out a couple of things that may bite you with the approach of extending Array
.
Re the specific error, it looks like the problem is that you've extended Array
without specifying its element type. With the error (playground):
class FruitArray extends Array {
// ^−−−−−− no type parameter here
// ...
}
Specifying an element type makes the error go away, for example here I'm giving FruitsEnum | string
(playground):
class FruitArray extends Array<FruitsEnum | string> {
// ...
}
Separately, though, a couple of issues with extending Array
like this:
Your code isn't handling all of the construction signatures it has to handle to properly subclass Array
. It needs to support at least these:
(I don't think it needs to support zero arguments. In the JavaScript specification, when creating arrays it does so via ArrayCreate
and ArraySpeciesCreate
, both of which expect a length
argument. But double-check me before relying on that.)
TypeScript doesn't complain about not handling that first argument being a number (because overlaying types on JavaScript's Array
after the fact is hard), but your fruit
parameter's value may well be a number. Example on the playground:
const f = new FruitArray(FruitsEnum.APPLE, "x");
const f2 = f.slice(1); // <== Calls your constructor with 1 as the only argument
While you can ensure that FruitArray
starts out with a FruitsEnum
value as its first element, that can soon become untrue (playground):
const f = new FruitArray(FruitsEnum.APPLE, "x");
f[0] = "x";
console.log(JSON.stringify(f));
// ^? const f: FruitArray
There, f
is still a FruitArray
, but the first element is now a string, not a FruitsEnum
.
You may be better off defining a tuple type or your own class that doesn't directly extend Array
(it could use an array in its implementation), but it depends on what you're going to do with it.