I have a function that is meant to be called as either
myFunc("")
myFunc({}, {}) // two different objects
it can also be called like
myFunc("", {})
in which case the second argument is simply ignored.
I want to prevent it from being called without a second argument if the first argument is an obj
instead of a string
myFunc({}) // should type error
I implement this like this
type SomeType = { name: string }
type SomeOtherType = { config: number }
function myFunc(firstArg: string | SomeType, secondArg?: SomeOtherType): number {
if (typeof firstArg === "string") {
return doStringStuff(firstArg)
}
if (secondArg === undefined) {
throw new Error('secondArg must be defined when firstArg is SomeType')
}
return doObjStuff(firstArg, secondArg)
}
(where doStringStuff()
and doObjStuff()
both return numbers) but is there a better way to do this? At the type level?
CodePudding user response:
Seems that function overloading works here, surprisingly even for variable argument counts (Sandbox):
function myFunc(p1: string): void;
function myFunc(p1: object, p2: object): void;
function myFunc(p1: string | object, p2: object | null = null): void { }
myFunc(''); // OK
myFunc({}, {}); // OK
myFunc({}); // Error: Argument of type '{}' is not assignable to parameter of type 'string'.
myFunc('', {}); // Error: Argument of type 'string' is not assignable to parameter of type 'object'.
myFunc({}, ''); // Error: Argument of type 'string' is not assignable to parameter of type 'object'.
myFunc('', ''); // Error: Argument of type 'string' is not assignable to parameter of type 'object'.
CodePudding user response:
You can use overloads to ensure that in TypeScript you can't call with a string
and SomeTye
:
function myFunc(firstArg: string): void
function myFunc(firstArg: SomeType, secondArg: SomeOtherType): void
function myFunc(firstArg: string | SomeType, secondArg?: SomeOtherType) {
if (typeof firstArg === "string") {
return doStringStuff(firstArg)
}
if (secondArg === undefined) {
throw new Error('secondArg must be defined when firstArg is SomeType')
}
return doTypeStuff(firstArg, secondArg)
}
myFunc("", { name : ""}) // error
myFunc("") // ok
You could also use a union of tuples in rest parameters to help TypeScript understand the relationship between parameters:
function myFunc(...{ 0:firstArg, 1:secondArg, length }:
| [firstArg: string]
| [firstArg: SomeType, secondArg: SomeOtherType]) {
if (length === 1) {
return doStringStuff(firstArg)
}
return doTypeStuff(firstArg, secondArg)
}
myFunc("", { name : ""}) // error
myFunc("") // ok
The union of tuples will act like overloads at call site so your callers aren't expose to the tuples