Интеграция с WebSocket соединениями

Stimulus — легковесный JavaScript-фреймворк, ориентированный на поведение компонентов в HTML. Одним из его ключевых преимуществ является простая интеграция с динамическими источниками данных, включая WebSocket, позволяющая создавать интерактивные интерфейсы с обновлением в реальном времени.

Подключение WebSocket в Stimulus контроллере

Для работы с WebSocket создаётся отдельный Stimulus контроллер. Структура контроллера обычно включает методы для инициализации соединения, обработки сообщений и закрытия соединения.

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static values = {
    url: String
  }

  connect() {
    this.socket = new WebSocket(this.urlValue);
    this.socket.addEventListener("open", this.onOpen.bind(this));
    this.socket.addEventListener("message", this.onMessage.bind(this));
    this.socket.addEventListener("close", this.onClose.bind(this));
    this.socket.addEventListener("error", this.onError.bind(this));
  }

  disconnect() {
    if (this.socket) {
      this.socket.close();
    }
  }

  onOpen(event) {
    console.log("WebSocket соединение установлено", event);
  }

  onMessage(event) {
    const data = JSON.parse(event.data);
    this.handleIncomingData(data);
  }

  onClose(event) {
    console.log("WebSocket соединение закрыто", event);
  }

  onError(event) {
    console.error("Ошибка WebSocket", event);
  }

  handleIncomingData(data) {
    // Обработка данных и обновление DOM
  }
}

Ключевые моменты:

  • connect() — метод, вызываемый при присоединении контроллера к DOM. Здесь создаётся WebSocket соединение.
  • disconnect() — метод, вызываемый при удалении контроллера из DOM. Закрытие соединения предотвращает утечки памяти.
  • onMessage() — обработка входящих данных. Обычно данные приходят в формате JSON и требуют десериализации.

Динамическое обновление интерфейса

Stimulus использует targets для связывания элементов DOM с логикой контроллера. Это позволяет легко обновлять содержимое при получении новых данных через WebSocket.

<div data-controller="chat" data-chat-url-value="wss://example.com/socket">
  <ul data-chat-target="messages"></ul>
</div>
handleIncomingData(data) {
  const messageElement = document.createElement("li");
  messageElement.textContent = `${data.user}: ${data.text}`;
  this.messagesTarget.appendChild(messageElement);
}

Выделенные моменты:

  • data-chat-target="messages" связывает DOM элемент с контроллером для упрощённого доступа.
  • Метод handleIncomingData обновляет DOM без использования сторонних библиотек, полностью опираясь на встроенные возможности Stimulus.

Отправка данных через WebSocket

Контроллер может отправлять данные на сервер по событию пользователя, сохраняя реактивность интерфейса.

sendMessage(text) {
  if (this.socket && this.socket.readyState === WebSocket.OPEN) {
    const payload = { action: "send_message", text };
    this.socket.send(JSON.stringify(payload));
  }
}

Особенности:

  • Проверка readyState предотвращает попытку отправки сообщений, когда соединение ещё не установлено.
  • Формат данных JSON обеспечивает универсальность и совместимость с большинством серверных WebSocket реализаций.

Повторное подключение и устойчивость соединения

Для стабильной работы реальных приложений необходимо предусматривать восстановление соединения при разрыве. Это достигается через повторные попытки с экспоненциальной задержкой.

onClose(event) {
  console.log("Соединение закрыто, повторное подключение через 3 секунды");
  setTimeout(() => this.connect(), 3000);
}

Советы по устойчивости:

  • Использование таймера с экспоненциальной задержкой снижает нагрузку на сервер.
  • Можно добавлять проверку maxAttempts для предотвращения бесконечных попыток подключения.

Интеграция с ActionCable и другими фреймворками

Stimulus легко сочетается с Rails ActionCable или любыми другими WebSocket-фреймворками. Контроллеры Stimulus выступают как адаптер между сервером и DOM, обеспечивая лёгкую синхронизацию состояния интерфейса с серверной логикой.

// Пример подключения к ActionCable
import { Controller } from "@hotwired/stimulus";
import consumer from "../channels/consumer";

export default class extends Controller {
  connect() {
    this.subscription = consumer.subscriptions.create(
      { channel: "ChatChannel" },
      {
        received: (data) => this.handleIncomingData(data)
      }
    );
  }

  disconnect() {
    if (this.subscription) {
      consumer.subscriptions.remove(this.subscription);
    }
  }
}

Масштабирование и производительность

При большом потоке сообщений важно:

  • Использовать виртуальные списки или лимитировать количество элементов DOM.
  • Группировать обновления через requestAnimationFrame или MutationObserver для снижения нагрузки на рендеринг.
  • Разделять логику обработки данных и обновления DOM, чтобы контроллер оставался лёгким и управляемым.

Заключение по архитектуре

Stimulus позволяет строить модульные, легко поддерживаемые WebSocket-интеграции. Контроллеры остаются минимальными, обеспечивая прямое связывание серверного потока данных с интерфейсом без лишнего шаблонизирования и сложных библиотек. Архитектура, основанная на targets и actions, гарантирует чёткую организацию кода и упрощает тестирование компонентов.