Reputation: 713
function foo(x: string[] | number[]) {
// HOW TO DETERMINE WHETHER x IS OF TYPE string[] OR number[]?
}
I have a class X
which can be instantiated with either no args or an array of string or an array of number:
class X {
constructor(...args?: string[] | number[]) {
// ensure there are args and array isn't empty
if (!args || args.length === 0) {
return;
}
// check for number[]
if (typeof args[0] === 'number') { // <<<< THIS IS WHERE I'M LOOKING FOR A DIFFERENT SOLUTION
this.setNumbers(...args); // <<<< THE ERROR
}
// it's a string[]
else {
this.setStrings(...args);
}
}
function setNumbers(...args: number[]): X {
// TODO: validate and set numbers
return this;
}
function setStrings(...args: string[]): X {
// TODO: validate and set strings
return this;
}
}
I couldn't find a solution on how to determine the type of the args
array, other than checking the type of the actual content.
And logically speaking this would be a valid solution: The args
array can only ever be either of type string[]
or number[]
. It can never be a mix of both types (like a (string | number)[]
would allow it to be).
So if it's non-empty and has an element of type number
, then it has to be of type number[]
, but TypeScript doesn't agree with me on that:
TS2345: Argument of type 'string | number' is not assignable to parameter of type 'number'.
It would be nice if we could check if (args instanceof number[])
, but that won't work on primitive data types, because they can't be instantiated and what gets instantiated here is an array of a certain type (not the type itself) and TypeScript kinda agrees: TS2693: 'number' only refers to a type, but is being used as a value here.
So how about if (args instanceof Array<number>)
? Would be nice as well, but TS2359: The right-hand side of an 'instanceof' expression must be of type 'any' or of a type assignable to the 'Function' interface type.
.
What would be the correct way to check the type of the args
array?
Upvotes: 1
Views: 232
Reputation: 25966
There is no runtime information on the Array itself, so checking the content is a good approach. You need to use a user-defined type guard to convince the compiler that your check narrows the type for the entire array:
class X {
constructor(...args: string[] | number[]) {
// ensure there are arguments and array isn't empty
if (!args || args.length === 0) {
return;
}
// check for number[]
if (this.isNumberArray(arguments[0])) {
this.setNumbers(...arguments);
}
// it's a string[]
else {
this.setStrings(...arguments);
}
}
isNumberArray(args: string[] | number[]): args is number[] {
return typeof arguments[0] === 'number';
}
setNumbers(...args: number[]): X {
// TODO: validate and set numbers
return this;
}
setStrings(...args: string[]): X {
// TODO: validate and set strings
return this;
}
}
On top of that:
The fact that arguments object is used for functions with variable number of parameters in ES3 & ES5 should not make you use it in typescript.
For functions with variable number of parameters in typescript, you should use rest parameters. Typescript compiler will compile it to:
Upvotes: 1