使用依赖注入的React/TypeScript动态切换语句



我目前正在React上使用typescript开发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;
}
}
}

这样它就可以被抽象出来,其他类可以向这个方法添加新的消息类型和函数调用。我在研究依赖注入(或多或少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)();
}
}

并基本上附加到地图上。

有更好的解决方案吗?

问题定义

您有一个接收消息的套接字。您希望动态添加根据消息类型执行的处理程序,但您不知道存在哪些处理程序,而是希望动态添加它们。事实上,他们应该加上自己。

因此,基本上,您需要一些管理器,它向注册移除处理程序公开API,并在消息上触发正确的处理程序。

依赖注入

依赖注入解决了另一个问题。使用控制反转,它解决了不知道特定服务或API的实现或实例的问题,而提前知道需要什么样的API-因此它依赖于它。通常,让上下文决定特定实现或实例以提供依赖性有助于隔离,网格,尤其是关于单元的推理(例如用于单元测试(。关于依赖注入还有更多的话要说,但应该已经很清楚了,它不适合在其打开时解决问题定义(但如稍后所述,它会很有帮助(。

解决方案方法

建立一个经理很容易,您的地图是正确的方法。基本上,您需要某种映射来知道为传入消息调用哪个处理程序。您现在可以决定是允许每个类型使用单个处理程序,还是允许多个。您当前允许Map中的每个类型有一个处理程序。如果你想支持多个,你可以保留键(消息类型(,映射的值将变成一个处理程序列表。

管理器API若要让模块注册移除本身,我建议应用观察者(或侦听者(模式。在这里,你还可以决定你的经理是否真的控制了给谁打电话,或者是否在每条消息上都会呼叫听众/观察员,他们自己决定是否要对消息做出反应。后者在redux中很常见,但对于您的用例来说,管理者控制似乎是一个不错的选择。您还希望在管理器中嵌入onClientMessage(就向侦听器发出的事件而言,这通常被称为emit(。

根据当前的Map方法,您只需要将该映射封装在一个提供注册和删除侦听器的类中:

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()创建它,但谁应该创建它?您的模块是如何了解经理的?要么您有某种全局状态,所有模块都可以引用,这样它们就可以向管理器注册自己(或其事件处理程序(。或者,在构建过程中,他们需要对管理器的引用(使其成为依赖注入(。决定权在你,取决于你想应用这种模式的环境。例如,如果您想要正确地测试您的模块,我建议依赖注入是一个好主意,因为模拟管理器和隔离测试中的单元非常容易。

模块(例如组件(基本上可以在装载时向管理器注册,在卸载时注销:

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动态化,您已经有了一个很好的方法。只需管理器中封装映射和onClientMessage以及注册/移除API。接下来,决定谁负责创建并为模块提供管理器,你就可以开始了!

最新更新