Reputation: 10380
I want to allow a string
or an array of string
s as a parameter. Depending on whether a string
or an array of string
s have been passed I want to return either a value or an array of values.
function(input: string | string[]): inputIsArray ? returnValue[] : returnValue {}
How can I write proper types for this?
Upvotes: 7
Views: 2206
Reputation: 1075755
I think I'd use overloads (but conditional types can be used, see below). In this example, I've used number
for returnType
but of course that's just for the example:
function example(input: string): number;
function example(input: string[]): number[];
function example(input: string | string[]): number | number[] {
if (Array.isArray(input)) {
return (<string[]>input).map(str => Number(str));
}
return Number(<string>input);
}
const result1 = example("42"); // `result1`'s type is `number`
const result2 = example(["42", "67"]); // `result2`'s type is `number[]`
Note that the third one of those is just the implementation signature. The only valid call signatures for example
there are the first two.
Re your comment asking if this is possible with conditional types, off-site I pinged Titian Cernicova-Dragomir, one of SO's experts on TypeScript. He said he'd seen the question and was about to post the overloads answer when he saw I had :-), and that he'd also use overloads for this — but it is possible with conditional types if you want to do it that way, and doing so opens up a third call signature. Here's what he came up with:
function example<T extends string | string[]>(input: T): T extends string ? number : number[];
function example(input: string | string[]): number | number[] {
if (Array.isArray(input)) {
return (<string[]>input).map(str => Number(str));
}
return Number(<string>input);
}
const result1 = example("42"); // `result1`'s type is `number`
const result2 = example(["42", "67"]); // `result2`'s type is `number[]`
Notice how introducing T
lets us use it both in the type of the parameter and the (conditional) type of the return value. (That was the key part I missed trying to apply conditional types to this.)
He pointed out that the conditional types version opens up a third way to call it: With an argument whose type is string | string[]
, not one or the other:
declare const value: string | string[];
let result3 = example(value) // `result3`'s type is `number | number[]`. Would not have worked with overloads.
So if you want that third call signature, you can use conditional types to get it. Otherwise, overloads provide just the first two signatures.
Upvotes: 6