[英]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.