简体   繁体   中英

TypeScript Conditional Function Argument name

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?

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

Playground link to code

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 
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM