我想知道是否有一种方法可以在以下场景中删除一些冗余/添加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
参数,并且仍然可以通过它获得能够索引处理程序的值?
上面的代码使自动补全能够计算出PingMessage
的name
必须是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
的成员完全相同。
一般来说,最好将泛型参数设置为最简单的类型,以便可以用来派生更复杂的类型。在本例中,这意味着只有名称,然后使用该名称为回调找到正确的接口。
工作示例