[英]React/TypeScript Dynamic Switch Statement using Dependency Injection
我目前正在使用 typescript 在 React 上开发 Websocket 通信总线。 目前,尝试重构此代码:
public onClientMessage(msg: IMessage) {
switch (msg.type) {
case "init": {
this.handleInit(msg);
break;
}
case "authorize": {
this.handleAuthorize(msg);
break;
}
case "render": {
this.handleRender(msg as IRenderMessage);
break;
}
default: {
console.warn("Unsupported type. Type: ", msg.type);
break;
}
}
}
以便将其抽象出来,其他类可以添加新的消息类型和 function 调用此方法。 我正在研究依赖注入(或多或少http://inversify.io/ )。 但是,我觉得对于一个简单的任务来说这可能是一种矫枉过正。
大家还有什么可以推荐的吗? 我也想过这样的事情:
private map = new Map<Message, (msg) => void>([
[Message.Init, this.handleInit(msg)],
[Message.Authorize, this. handleAuthorize(msg)],
[Message.Render, this. handleRender(msg)] ...
]);
onClientMessage(msgType: Message, msg: IMessage) {
if (this.map.has(msgType)) {
this.map.get(msgType)();
}
}
基本上 append 到 map。
有更好的解决方案吗?
您有一个接收消息的套接字。 您希望动态添加根据消息类型执行的处理程序,但您事先不知道存在哪些处理程序,但希望动态添加它们。 实际上,他们应该添加自己。
所以基本上,您需要一些管理器来公开 API 以注册或删除处理程序,并在消息上触发正确的处理程序。
关于依赖注入的一句话
依赖注入解决了一个不同的问题。 使用控制反转解决了不知道特定服务或 API 的实现或实例的问题,同时预先知道需要什么 API - 所以这取决于它。 一般来说,让上下文决定特定实现或实例以提供依赖关系有助于隔离、网格化,尤其是对单元进行推理(例如,用于单元测试)。 关于依赖注入还有更多要说的,但应该已经很清楚了,它不适合解决问题的定义(但它会有所帮助,如后所述)。
构建管理器很容易,您的 Map 是正确的方法。 基本上,您需要某种映射来知道为传入消息调用哪个处理程序。 您现在决定是否允许每个类型使用单个处理程序或多个. 您目前在 Map 中允许每种类型使用一个处理程序。 如果要支持多个,则保留键(消息类型),map 的值将成为处理程序列表。
管理器 API为了让模块自己注册和删除,我建议应用观察者(或侦听器)模式。 在这里,您还可以决定您的经理是否真正控制了呼叫谁,或者是否在每条消息上都调用了侦听器/观察者,并且他们自己决定是否要对消息做出反应。 后者在 redux 中很常见,但对于您的用例,控制管理器似乎是一个不错的选择。 您还希望将onClientMessage
嵌入到管理器中(根据向侦听器发出的事件,这通常称为发出)。
从您当前的 Map 方法中,您只需要将 map 封装在提供注册和删除侦听器的 class 中:
const createMessageHandlerManager = () => {
// message-type => listener
const listeners = {};
return {
addListener: (key, listener) => {
if (listeners[key]) {
throw new Error(`Listener already present for key '${key}'`);
}
listeners[key] = listener;
},
removeListener: (key) => {
if (!key || !listeners[key])) {
return;
}
delete listeners[key];
},
onClientMessage: (msgType: Message, msg: IMessage) => {
const handler = listeners[msgType];
if (handler) {
handler(msg);
}
},
};
};
提供经理是下一个任务。 使用const manager = createMessageHandlerManager()
创建它,但谁应该创建它? 您的模块如何认识经理? 要么你有某种全局 state 你的所有模块都可以引用,所以它们可以将自己(或它们的事件处理程序)注册到管理器。 或者在构建过程中,他们需要对管理器的引用(使其成为依赖注入)。 决定权在你,取决于你想应用这种模式的上下文。 例如,如果您想正确测试您的模块,我建议依赖注入是一个好主意,因为它很容易模拟管理器并隔离被测单元。
一个模块(例如一个组件)基本上可以在安装时向管理器注册并在卸载时取消注册:
const ModuleA = ({ messageHandlerManager }) => {
const handleInit = useCallback(msg => console.log(msg), []);
const handleAuthorize = useCallback(msg => console.log(msg), []);
useEffect(() => {
messageHandlerManager.addListener(Message.Init, handleInit);
messageHandlerManager.addListener(Message.Authorize, handleAuthorize);
return () => {
messageHandlerManager.removeListener(Message.Init, handleInit);
messageHandlerManager.removeListener(Message.Authorize, handleAuthorize);
};
}, []);
// ... render ...
};
要摆脱switch
以使其具有动态性,您已经有了一个很好的方法。 只需将 map 和onClientMessage
与注册/删除API 一起封装在manager中。 接下来,决定谁负责创建和提供模块的管理器,你就可以开始了!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.