Let's say I have a function with multiple optional parameters.
Why is there no type guard based on the function arity through the arguments
keyword and how to solve this without changing the implementation and without ugly casts?
function test(arg0?: string, arg1?: string, arg2?: string) {
if (arguments.length === 1) {
// I expect arg0 to be of type string instead of string | undefined
}
if (arguments.length === 2) {
// I expect arg0 and arg1 to be of type string instead of string | undefined
}
if (arguments.length === 3) {
// I expect arg0, arg1 and arg2 to be of type string instead of string | undefined
}
}
CodePudding user response:
Please see arguments
type:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
interface IArguments {
/** Iterator */
[Symbol.iterator](): IterableIterator<any>;
}
As you might have noticed, keyword arguments
knows nothing about arity of arguments type. From typescript perspective it is not even bound to function context. It just an object with a bunch of unrelated to function arguments properties.
Moreover, length
property does not act like typeguard. You are unable to infer for example [string, string]
type just by checking if length === 2
.
Please see this answer. You will find there appropriate links to github issues and this hasLengthAtLeast
helper.
CodePudding user response:
The currently accepted answer is quite good, making use of a type predicate to narrow the type.
I would suggest an alternate way, making use of rest parameters, like so:
function test(...args: string[]) {
if (args.length === 1) {
console.log('arg0:', typeof args[0]);
}
if (args.length ==- 2) {
console.log('arg0:', typeof args[0], 'arg1:', typeof args[1]);
}
if (args.length === 3) {
console.log('arg0:', typeof args[0], 'arg1:', typeof args[1], 'arg2:', typeof args[2]);
}
}
When used in with various arguments, we get the following output:
Input | Compiles? | Output |
---|---|---|
test('one'); |
yes | arg0: string |
test('one', 'two', 'three'); |
yes | arg0: string arg1: string arg2: string |
test(1); |
no | error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. |
test(undefined); |
no | error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'string'. |
One (potentially major) downside of this approach is that the length of the args
array is not limited. Depending on your use case it could be sufficient to ignore excess arguments, or you could perform a runtime check like so:
if (args.length > 3) {
throw new Error(`Expected 3 arguments, but got ${args.length}`);
}
Food for thought.