Stimulus — легковесный JavaScript-фреймворк, ориентированный на поведение компонентов в HTML. Одним из его ключевых преимуществ является простая интеграция с динамическими источниками данных, включая WebSocket, позволяющая создавать интерактивные интерфейсы с обновлением в реальном времени.
Для работы с 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.Контроллер может отправлять данные на сервер по событию пользователя, сохраняя реактивность интерфейса.
sendMessage(text) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
const payload = { action: "send_message", text };
this.socket.send(JSON.stringify(payload));
}
}
Особенности:
readyState предотвращает попытку отправки
сообщений, когда соединение ещё не установлено.Для стабильной работы реальных приложений необходимо предусматривать восстановление соединения при разрыве. Это достигается через повторные попытки с экспоненциальной задержкой.
onClose(event) {
console.log("Соединение закрыто, повторное подключение через 3 секунды");
setTimeout(() => this.connect(), 3000);
}
Советы по устойчивости:
maxAttempts для предотвращения
бесконечных попыток подключения.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);
}
}
}
При большом потоке сообщений важно:
requestAnimationFrame или
MutationObserver для снижения нагрузки на рендеринг.Stimulus позволяет строить модульные, легко поддерживаемые WebSocket-интеграции. Контроллеры остаются минимальными, обеспечивая прямое связывание серверного потока данных с интерфейсом без лишнего шаблонизирования и сложных библиотек. Архитектура, основанная на targets и actions, гарантирует чёткую организацию кода и упрощает тестирование компонентов.