繁体   English   中英

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

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM