I have a notification system in my app, which I want to make type safe. So I defined a type holding the possible notification IDs:
export interface IRequisitionCallBackMethods {
"open": (requestId: string, values: { type: string; name: string }) => Promise<void>;
"close": (requestId: string, values: { name: string }) => Promise<void>;
"delete": (requestId: string, values: { id: number }) => Promise<void>;
}
In my class I can now use a type mapping to define a registration function for callbacks, triggered when one of the events are sent:
export class RequisitionHub {
public register = <K extends keyof IRequisitionCallBackMethods>(event: K,
callback: IRequisitionCallBackMethods[K]): void => {
// todo
};
}
That works so far, but only for a single event callback combination. In my old implementation I can register the same callback for many events. That's why I need a way to build a type union out of the possible callback types. Is that possible and how?
I tried:
export class RequisitionHub {
public register2 = <K extends (keyof IRequisitionCallBackMethods | Array<keyof IRequisitionCallBackMethods>)>
(event: K,
callback: <union of IRequisitionCallBackMethods valid for K>): void => {
// todo
};
}
but didn't find anything that would work for the callback union type.
For comparison, that's what I currently use:
export type RequisitionCallback = (requestId: string, args?: unknown[]) => Promise<void>;
CodePudding user response:
You have a couple of options here. If we start with your IRequisitionCallBackMethods
type, we can get the types of the individual functions and — more usefully — their values
types:
export interface IRequisitionCallBackMethods {
"open": (requestId: string, values: { type: string; name: string }) => Promise<void>;
"close": (requestId: string, values: { name: string }) => Promise<void>;
"delete": (requestId: string, values: { id: number }) => Promise<void>;
}
export type IRequisitionOpenCallback = IRequisitionCallBackMethods["open"];
export type IRequisitionOpenValues = Parameters<IRequisitionOpenCallback>[1];
export type IRequisitionCloseCallback = IRequisitionCallBackMethods["close"];
export type IRequisitionCloseValues = Parameters<IRequisitionCloseCallback>[1];
export type IRequisitionDeleteCallback = IRequisitionCallBackMethods["delete"];
export type IRequisitionDeleteValues = Parameters<IRequisitionDeleteCallback>[1];
Or alternatively, you could start with those and then define IRequisitionCallBackMethods
using those types.
export type IRequisitionOpenValues = { type: string; name: string };
export type IRequisitionOpenCallback = (requestId: string, values: IRequisitionOpenValues) => Promise<void>;
export type IRequisitionCloseValues = { name: string };
export type IRequisitionCloseCallback = (requestId: string, values: IRequisitionCloseValues) => Promise<void>;
export type IRequisitionDeleteValues = { id: number };
export type IRequisitionDeleteCallback = (requestId: string, values: IRequisitionDeleteValues) => Promise<void>;
export interface IRequisitionCallBackMethods {
"open": IRequisitionOpenCallback;
"close": IRequisitionCloseCallback;
"delete": IRequisitionDeleteCallback;
}
Either way, you now have types you can readily use to declare a handler for multiple events:
const handler = async (requestId: string, values: IRequisitionOpenValues | IRequisitionCloseValues) => {
console.log(requestId);
if ("type" in values) {
console.log(values.type, values.name);
} else {
console.log(values.name);
}
};
const r = new RequisitionHub();
r.register("open", handler);
r.register("close", handler);
Playground for second approach
Or if you prefer, type the function rather than the values
, either works:
const handler: IRequisitionOpenCallback & IRequisitionCloseCallback = async (requestId, values) => {
console.log(requestId);
if ("type" in values) {
console.log(values.type, values.name);
} else {
console.log(values.name);
}
};