[英]TypeScript Template Literals break generic constraints
我正在尝试编写一个支持以下语法表示的事件和子事件的 Sub-Pub 类:
publisher.on("message:general", ... ) // subscribe to all messages
publisher.on("message", ... ) // subscribe to messages in general
为此,我使用了 TypeScript 模板文字。
问题是,虽然它广泛有效,但它似乎打破了通用约束。 难道我做错了什么?
这是到目前为止的样子:
interface ChatEvents {
connect: {
user: string;
};
disconnect: {
user: string;
reason: "banned" | "timeout" | "leave";
};
}
declare type ChatEvent = keyof ChatEvents;
interface SubEvents extends Record<ChatEvent, string> {
connect: "general" | "watercooler" | "lobby";
disconnect: "voice" | "text";
}
declare type EventWithSubEvent<T extends ChatEvent> = `${T}${
| ""
| `:${SubEvents[T]}`}`;
// "connect" | "connect:general" | "connect:watercooler" | "connect:lobby"
declare type ChatConnectEvent = EventWithSubEvent<"connect">;
// "disconnect" | "disconnect:voice" | "disconnect:text"
declare type ChatDisconnectEvent = EventWithSubEvent<"disconnect">;
// So far so good!
// Let's write some generics:
declare function subscribeToEvent<E extends ChatEvent>(
event: E,
callback: (payload: ChatEvents[E]) => void
): void;
subscribeToEvent("connect", (payload) => {
// Correctly extracts matching type
type TPayload = typeof payload; // { user: string; }
});
declare function subscribeToSubEvent<E extends ChatEvent>(
event: EventWithSubEvent<E>,
callback: (payload: ChatEvents[E]) => void
): void;
subscribeToSubEvent("connect:general", (payload) => {
// Extracts all possible payload types instead of only the `connect` one
/*
{
user: string;
} | {
user: string;
reason: "banned" | "timeout" | "leave";
}
*/
type TPayload = typeof payload;
});
subscribeToSubEvent("connect:voice", () => {}) // Should fail but doesn't. `:voice` is a subevent of `disconnect`, not `connect`.
一般来说,我发现打字稿从参数中推断出泛型类型的工作越少,泛型越有效(尤其是提供体面的智能感知帮助)。 在这种情况下,如果您将泛型设置为作为第一个参数传递的确切文本(限制为所有有效选项),则使用辅助类型仅提取'connect' | 'disconnect'
'connect' | 'disconnect'
部分在回调中使用它会工作得更顺畅。 ( 操场)
interface ChatEvents {
connect: {
user: string;
};
disconnect: {
user: string;
reason: "banned" | "timeout" | "leave";
};
}
interface SubEvents extends Record<keyof ChatEvents, string> {
connect: "general" | "watercooler" | "lobby";
disconnect: "voice" | "text";
}
type AllChatEvents = {[K in keyof ChatEvents]: K | `${K}:${SubEvents[K]}`}[keyof ChatEvents]
// helper to extract the 'connect'|'disconnect' from an event
type ExtractEvt<T extends AllChatEvents> = T extends keyof ChatEvents ? T : T extends `${infer A}:${string}` ? A : never
declare function subscribeToSubEvent<E extends AllChatEvents>(
event: E,
callback: (payload: ChatEvents[ExtractEvt<E>]) => void
): void;
subscribeToSubEvent("disconnect:voice", (payload) => {
console.log(payload.reason) // detects that 'disconnect' is the payload type
});
subscribeToSubEvent("connect:voice", () => {}) // fails properly
作为奖励,因为泛型约束是要传递的所有有效字符串,与其他泛型设置相比,intellisense 将给出非常有用的结果,在其他泛型设置中,在您输入之前它无法确定正确的行为是什么:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.