The problem here is TypeScript's lack of direct support for correlated union types as described in microsoft/TypeScript#30581. Essentially the type checker cannot easily be made to understand that the check message.type === messageType
should narrow message
to the member of the Message
union whose payload
property can be handled by onMessage()
. The type of message
becomes correlated to the type of messageType
after that check without being known specifically. But there's no easy way to convey such information to the compiler.
Often you can work around this issue with generics and some refactoring of types as described in microsoft/TypeScript#47109, but I couldn't get that to work here (if someone does get this to work, let me know).
So instead, I think the best thing we can do is write a custom type guard function to say that message.type === messageType
will narrow message
appropriately. Here's one way to do it:
First, I'm going to copy your conditional type for onMessage()
's parameter into a utility type we can reuse:
type CorrespondingPayload<K extends Message['type']> =
Extract<Message, { type: K }> extends {
payload: infer TPayload;
} ? TPayload : undefined
And now the custom type guard function looks like this:
function sameType<K extends Message['type']>(
message: Message, messageType: K
): message is Message & { type: K, payload: CorrespondingPayload<K> } {
return message.type === messageType;
}
If sameType(message, messageType)
returns true
, then message
will be narrowed to a type where payload
is known to be of type CorrespondingPayload<K>
. Let's use it:
const someFn = <K extends Message['type']>(
messageType: K,
onMessage: (payload?: CorrespondingPayload<K>) => void
) => {
const handleMessage = (message: Message) => {
if (sameType(message, messageType)) {
onMessage(message.payload); // okay
}
}
};
Looks good!
Playground link to code
` for two independent generic types `K` and `P`. [This](https://tsplay.dev/w6vYvw) is the closest I can get, and it's really the same as my earlier solution except that it tries to use ms/TS#47109. So, shall I write up an answer showing how to write a type guard function to deal with this? Or do you think I'm still missing something about the question? (Pls mention @jcalz to notify me, thanks)