React / TypeScript Dynami c Оператор переключения с использованием внедрения зависимостей - PullRequest
4 голосов
/ 22 апреля 2020

В настоящее время я работаю над коммуникационной шиной Websocket в React, используя машинопись. В настоящее время пытаются реорганизовать этот код:

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)();
  }
}

И в основном добавляю к карте.

Есть ли лучшее решение?

1 Ответ

1 голос
/ 29 апреля 2020

Определение проблемы

У вас есть сокет получения сообщений. Вы хотите динамически добавлять обработчики, которые выполняются в зависимости от типа сообщения, но вы не знаете заранее, какие существуют обработчики, но хотите добавить их динамически. На самом деле, они должны добавить себя.

Таким образом, вам нужен Manager , который предоставляет API для обработчиков register или remove и в сообщениях запускает правые обработчики.

Слово об инъекции зависимостей

Инъекции зависимостей решает другую проблему. Использование инверсии управления решает проблему незнания реализации или экземпляра определенного c сервиса или API, в то время как знание заранее , какой API необходим - так что зависит от этого. В общем, разрешение контекста определять конкретную реализацию или экземпляр c для предоставления зависимости помогает изолировать, связывать и особенно рассуждать о модуле (например, для модульного тестирования). Можно еще сказать о внедрении зависимостей, но уже должно быть ясно, что он не подходит для решения проблемы при ее включении (но будет полезно, как будет указано ниже).

Решение Подход

Построение менеджера легко, и ваша карта - правильный подход. По сути, вам нужно какое-то отображение , чтобы узнать, какой обработчик вызывать для входящего сообщения. Теперь вы решаете, разрешить ли вам один обработчик для типа или несколько . В настоящее время вы разрешаете один обработчик для каждого типа на карте. Если вы хотите поддерживать множественное, вы сохраняете ключ ( тип сообщения ) и значение карты становится списком обработчиков.

API менеджера Чтобы разрешить модули регистрируются и удаляют сами, я бы предложил применить шаблон наблюдателя (или слушателя). Здесь вы также можете решить, действительно ли ваш менеджер контролирует, кому звонить или слушатели / наблюдатели вызывают каждое сообщение, и они сами решают, хотят ли они реагировать на сообщение. Последний типичен для redux, но для вашего сценария управления менеджер кажется хорошим выбором. Вы также хотите встроить onClientMessage в менеджер (это часто называется emit в терминах событий, которые отправляются слушателям).

Из вашего текущего подхода к карте вам просто нужно чтобы инкапсулировать эту карту в класс, который обеспечивает регистрацию и удаление прослушивателей:

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, чтобы сделать его динамическим c вы уже есть хороший подход. Просто инкапсулируйте карту и onClientMessage вместе с зарегистрируйте / удалите API в диспетчере . Затем решите, кто отвечает за создание и предоставление менеджера для модулей, и вы хороши для go!

...