Reputation: 2476
Is it possible to make a function have either mandatory or optional parameters based on conditional types in TypeScript?
This is what I've got so far:
const foo = <T extends string | number>(
first: T,
second: T extends string ? boolean : undefined
) => undefined;
foo('foo', true); // ok, as intended
foo(2, true); // not ok, as intended
foo(2, undefined); // ok, as intended
foo(2); // compiler error! I want this to be ok
Upvotes: 53
Views: 29944
Reputation: 3519
Previous answers either did not really answer the question or were very messy, I decided to simplify things.
You need to create a conditional type for the function args like so:
type FooArgs<T extends string | number> = T extends string ?
[first: T, second: boolean] :
[first: T, second?: undefined];
const foo = <T extends string | number>(...args: FooArgs<T>) => {
const [first, second] = args;
// do something
};
foo('foo', true); // ok, as intended
foo(2, true); // not ok, as intended
foo(2, undefined); // ok, as intended
foo(2); // ok, as intended
foo('foo'); // not ok, as intended
Upvotes: 0
Reputation: 18281
Building on the answer of @robstarbuck, to avoid having to consume the arguments as spread arguments, you can use overloading or simply type overriding. As long as the implementation matches the overloaded types, TS seems to use the strictest types it can match.
const bar: (<T extends string | number>(
...args: (T extends string
? // two arguments
[T, boolean]
: // one argument or undefined second argument
[T] | [T, undefined])
) => string) =
// implementation without spread args
<T,>(a: T, b?: boolean) => `${a} ${b}`;
bar('bar', true); // ok
bar(2, true); // not ok
bar(2); // ok
bar(2, undefined); // ok
Upvotes: 0
Reputation: 8091
To ensure a second
argument is never provided (even when undefined), you could group both parameters into the the rest statement.
const bar = <T extends string | number>(
...args: (T extends string ? [T, boolean] : [T])
) => undefined;
// Usage
bar('bar', true); // ok, as intended
bar(2, true); // not ok, as intended
bar(2); // ok, as intended
bar(2, undefined); // not ok
This is a small adjunct to @titian-cernicova-dragomir's answer.
Demo.
Upvotes: 4
Reputation: 249586
You can do this in 3.1 using Tuples in rest parameters and spread expressions
const foo = <T extends string | number>(
first: T,
...a: (T extends string ? [boolean] : [undefined?])
) => undefined;
foo('foo', true); // ok, as intended
foo(2, true); // not ok, as intended
foo(2, undefined); // ok, as intended
foo(2); // ok
But the better way is to use overloads.
function foo2(first: string, second: boolean) : undefined
function foo2(first: number, second?: undefined): undefined
function foo2<T>(first: T, second?: boolean): undefined{
return undefined
}
foo2('foo', true); // ok, as intended
foo2(2, true); // not ok, as intended
foo2(2, undefined); // ok, as intended
foo2(2); // ok
Upvotes: 94