Is it possible to create a function with arguments being optional depending on the value of first argument?
post(item:"book"|"page", (if (item == "book") bookName: string), (if (item == "page") bookPage: number), ): Observable<any> {
return
}
Is this or similar behaviour achievable with classes?
CodePudding user response:
Traditionally you would use function overloads to get this behavior, with multiple call signatures corresponding to each way you want people to call it:
// call signatures
function post(item: "book", bookName: string): void;
function post(item: "page", bookPage: number): void;
and then a single implementation which has to accept all the call signatures (but might be less strict than these signatures):
// implementation
function post(item: "book" | "page", nameOrPage: string | number) {
}
This will behave as you want from the caller's side
post("book", "The Great Gatsby"); // okay
post("page", 123); // okay
post("book", 456); // error
post("page", "War and Peace"); // error
Although the drawback is that inside the implementation the compiler wouldn't realize that the item
parameter can be used to narrow the nameOrPage
parameter. That is, you can't treat item
like the discriminant of a discriminated union:
function post(item: "book" | "page", nameOrPage: string | number) {
if (item === "book") {
nameOrPage.toUpperCase(); // error! 'toUpperCase' does not exist on 'string | number'.
} else {
nameOrPage.toFixed(); // error! 'toFixed' does not exist on 'string | number'
}
}
So you'd be forced to use type assertions or something like them:
// implementation
function post(item: "book" | "page", nameOrPage: string | number) {
if (item === "book") {
(nameOrPage as string).toUpperCase();
} else {
(nameOrPage as number).toFixed();
}
}
If the return type doesn't depend on the inputs, then nowadays you can get better behavior by using a single call signature rest argument list is of a union of tuple types, along with destructuring assignment to assign the rest arguments to named parameters, and as of TypeScript 4.6 the compiler will treat these parameters as discriminated unions:
function post(...[item, nameOrPage]:
[item: "book", bookName: string] |
[item: "page", bookPage: number]
): void {
if (item === "book") {
nameOrPage.toUpperCase(); // okay
} else {
nameOrPage.toFixed(); // okay
}
}
And from the caller's side it still looks like overloads:
post("book", "The Great Gatsby"); // okay
post("page", 123); // okay
post("book", 456); // error
post("page", "War and Peace"); // error
CodePudding user response:
I don't think there's something exactly like what you want.
I would just use better typing.
interface Book {
bookName: string
}
interface Page {
bookPage: number
}
post(item:Book|Page): Observable<any> {
return
}