I have 2 types of messages and different context for each.
I've created a function which takes type
and context
and is able to tell the caller of the function which context
to use based on the type
const MessageOptions = {
textMessage: "textMessage",
interactiveFormMessage: "interactiveFormMessage"
} as const;
type MessageOptions = typeof MessageOptions[keyof typeof MessageOptions];
interface SimpleTextMessageContext { ... }
interface InteractiveFormMessageContext { ... }
interface MessageTypes {
textMessage: SimpleTextMessageContext;
interactiveFormMessage: InteractiveFormMessageContext;
};
function sendInteractiveMessage<T extends MessageOptions>(
type: T,
details: MessageTypes[T]
) { ... }
sendInteractiveMessage(MessageOptions.textMessage, /* expects text message context */{});
sendInteractiveMessage(MessageOptions.interactiveFormMessage, /* expects form context */{});
The problem I'm having is that inside the implementation of sendInteractiveMessage
I cannot refer to details
as the right context w/o details as SimpleTextMessageContext
. see example:
function sendInteractiveMessage<T extends MessageOptions>(
type: T,
details: MessageTypes[T]
) {
if (type === MessageOptions.textMessage) {
details // type here is MessageOptions[T] and NOT SimpleTextMessageContext
// ^?
}
}
Is there a way to effect details
's type in the implementation based on type
's type?
Thank you!
CodePudding user response:
You can achieve the same behavior as before while having the types being narrowed by creating a union of the possible arguments to your function:
type Args = {
[K in MessageOptions]: [type: K, details: MessageTypes[K]];
}[MessageOptions]
function sendInteractiveMessage(...[type, details]: Args) {
if (type === MessageOptions.textMessage) {
details
}
}
You'll notice that autocomplete now gives you fields of both types for the details
parameter, which is understandably annoying. You can opt-in to use an external signature (which is just your original functions') so userland is happy and you're happy with the function body as well:
type Args = {
[K in MessageOptions]: [type: K, details: MessageTypes[K]];
}[MessageOptions]
// extra signature so userland doesn't get excess invalid properties
function sendInteractiveMessage<K extends MessageOptions>(type: K, details: MessageTypes[K]): void;
function sendInteractiveMessage(...[type, details]: Args) {
if (type === MessageOptions.textMessage) {
details
}
}