【问题标题】:React/TypeScript Dynamic Switch Statement using Dependency Injection使用依赖注入的 React/TypeScript 动态切换语句
【发布时间】:2020-08-04 22:36:14
【问题描述】:

我目前正在使用 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;
    }
  }
}

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

并且基本上附加到地图上。

有没有更好的解决方案?

【问题讨论】:

  • 我一般不太熟悉 websockets,所以也许其他人可以提供更好的答案,但我认为你想多了。当简单的函数可以使用时,我并没有真正看到在此处使用依赖注入或类的意义。没有更多代码或澄清,我看不出这两个代码示例有什么问题。无论哪种方式,您都必须拥有某种类型的条件映射或键/值映射。
  • 我同意@MartinDawson。所描述的用例让我想起了 Redux Thunk。您可能会从那里获得灵感。
  • 是的,我就是这么想的。我可能想太多了。谢谢大家
  • 我真的很喜欢 Map 方法。我做过类似的事情,效果非常好。
  • 我会保留 switch 语句,但使用 string 和 typeof 作为 type 属性来键入您的消息,以便打字稿可以推断出 switch 语句中的消息类型(因此不再进行诸如“msg as IRenderMessage”)。您可以查看 redux 文档以获取完整示例:redux.js.org/recipes/…

标签: javascript reactjs typescript dependency-injection websocket


【解决方案1】:

问题定义

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

所以基本上,您需要一些 Manager 来公开 API 以 register 或 remove handlers 和消息触发正确的处理程序。

关于依赖注入的一句话

依赖注入解决了一个不同的问题。使用控制反转解决了不知道特定服务或 API 的实现或实例的问题,同时预先知道什么 API 需要 -所以它取决于。通常,让上下文决定特定实现或实例以提供依赖关系有助于隔离、网格化,尤其是对单元进行推理(例如,用于单元测试)。关于依赖注入还有很多要说的,但应该已经很清楚了,它不适合解决问题的定义(但后面会有所帮助)。

解决方法

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

Manager 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 在ma​​nager 中。接下来,确定谁负责创建模块并将其提供给模块,然后您就可以开始了!

【讨论】:

  • 哦,当我第一次阅读你的问题时我有另一个想法,我忘了回答:依赖注入作为一个概念很容易,你已经在到处使用它了。有时需要一个框架,因为创建依赖关系图可能会变得非常困难(即正确创建和设置所有依赖项 - 您可能已经经历过为子组件提供道具的经历,以至于它变得很痛苦,您宁愿尝试以不同的方式提供信息( Redux,Context Api 等)。但在你的情况下,它是直截了当的。
猜你喜欢
  • 2021-01-27
  • 1970-01-01
  • 2016-01-20
  • 2013-04-10
  • 2015-11-05
  • 1970-01-01
  • 2020-02-05
  • 2018-09-25
  • 1970-01-01
相关资源
最近更新 更多