从Typescript中的type推断文字值



我想知道是否有一种方法可以在以下场景中删除一些冗余/添加dry。

假设我正在创建一个超级简单的消息集线器——接收者可以注册某种类型的消息,然后,每当这些消息到达集线器时,都会收到一份副本。

一个简单的实现可能像这样:

interface Message {
name: "ping" | "pong";
}
interface PingMessage extends Message {
name: "ping";
value: string;
}
interface PongMessage extends Message {
name: "pong";
value: string;
}
type HubMessage = PingMessage | PongMessage;
type MessageHandler<T extends HubMessage> = (message: T) => any | Promise<any>;
class MessageHub {
private recipients: Record<string, MessageHandler<any>[]> = {};
public receive<T extends HubMessage>(
name: T["name"],
handler: MessageHandler<T>
) {
this.recipients[name] = [...(this.recipients[name] ?? []), handler];
}
public send(message: HubMessage) {
(this.recipients[message.name] ?? []).forEach((handler) =>
handler(message)
);
}
}
const hub = new MessageHub();
// register a few handler
hub.receive<PingMessage>("ping", (m) => console.log(`pinging ${m.value}`));
hub.receive<PingMessage>("ping", (m) => console.log(`pinging too ${m.value}`));
hub.receive<PongMessage>("pong", (m) => console.log(`ponging ${m.value}`));
// start sending
hub.send({ name: "ping", value: "fly there" });
hub.send({ name: "pong", value: "and back" });

receive方法上,是否有一种方法可以删除name参数,并且仍然可以通过它获得能够索引处理程序的值?

上面的代码使自动补全能够计算出PingMessagename必须是ping,因此必须指定通用参数感觉有点傻。name;然而,我怀疑,这些信息在编译时是完全无法获取的。

沙盒在这里玩,任何见解非常感谢!

在接收方法上,是否有一种方法可以删除名称参数,并且仍然可以获得能够通过它索引处理程序的值?

不。

类型信息在运行时完全丢失。因此,你必须在运行时依赖于一个值来确定对象的类型,以便你可以安全地访问它。


你能做的是走另一条路。您可以从名称"ping"更好地推断类型。因此,您可以通过去掉显式类型参数来删除重复。

要做到这一点,我们需要改变泛型形参的工作方式。

public receive<T extends HubMessage['name']>(
name: T,
handler: MessageHandler<HubMessage & { name: T }>
)

现在不是整个HubMessage作为泛型参数,它只是HubMessage的唯一名称。由此,您可以导出您想要的具有交集的确切HubMessage的类型:

HubMessage & { name: T }

将联合过滤到匹配{ name: T }的。

现在你可以这样做:

hub.receive("ping", (m) => console.log(`${m.name}ing ${m.value}`)); // m.name is "ping"
hub.receive("ping", (m) => console.log(`${m.name}ing too ${m.value}`)); // m.name is "ping"
hub.receive("pong", (m) => console.log(`${m.name}ing ${m.value}`)); // m.name is "pong"

在每种情况下,m都是强类型的,与HubMessage的成员完全相同。

一般来说,最好将泛型参数设置为最简单的类型,以便可以用来派生更复杂的类型。在本例中,这意味着只有名称,然后使用该名称为回调找到正确的接口。

工作示例

最新更新