Apologies in advance if this is a duplicate, but my search has not turned up anything that quite fits the issue I'm having.
Firstly, the desired behavior is to have a class method with two params, the second optional, where the second param's type is dependent on the first param's type. If the first param is type A, the second param should always be required and should be of type X, if the first param is type B, the second param should be omitted.
I've achieved something quite like this with function overloading:
// types
enum MessageType { FOO, BAR, BAZ }
type MessagePayload<T extends MessageType> = T extends MessageType.FOO
? string
: T extends MessageType.BAR
? number
: never;
// overloads
function sendMessage<T extends MessageType.BAZ>(action: T): void
function sendMessage<T extends MessageType>(action: T, payload: MessagePayload<T>): void
// implementation
function sendMessage<T extends MessageType>(action: T, payload?: MessagePayload<T>) {
// do something
}
// tests
sendMessage(MessageType.FOO, "10") // no error - as expected
sendMessage(MessageType.FOO, 10) // error - as expected, payload is not string
sendMessage(MessageType.FOO) // error - as expected, payload must be string
sendMessage(MessageType.BAZ); // no error - as expected - since MessageType is BAZ
However, the same exact constructs, when applied to a class method, do not produce the same results. This snippet is a continuation of the first and uses the same types:
// interface
interface ISomeClient {
sendMessage<T extends MessageType.BAZ>(action: T): void
sendMessage<T extends MessageType>(action: T, payload: MessagePayload<T>): void
}
// implementation
class SomeClient implements ISomeClient {
sendMessage<T extends MessageType>(action: T, payload?: MessagePayload<T>) {
// do something
}
}
// tests
const client = new SomeClient();
client.sendMessage(MessageType.FOO, "10"); // no error - as expected
client.sendMessage(MessageType.FOO, 10); // error, payload is not string
client.sendMessage(MessageType.FOO) // no error??? different behavior than function example
client.sendMessage(MessageType.BAZ); // this part works fine
Here is a more complete example on the TS Playgound.
So, I guess this is a two-parter:
- why is this not working for the class example?
- is there some better way to achieve this that will work for both classes and functions and that doesn't require maintaining an overload to capture the types that don't require a payload? I've used an enum and a conditional type here to constrain the second param to match what's expected given the first param. I've played around with another way involving an key to type map but it seems hacky, still requires overloads, and suffers from this same issue for classes and functions.
Thanks.