Composition API в Vue 3 позволяет выносить поведение, связанное с подключением, подписками и обработкой сообщений STOMP.js, в переиспользуемые композиционные функции. Это критично для WebSocket-архитектур, где логика соединения должна быть отделена от компонентов интерфейса.
STOMP.js в таких системах выступает транспортным слоем поверх WebSocket, предоставляя модель pub/sub с брокером сообщений. При использовании вместе с Vue Composition API возникает типичная задача: изолировать жизненный цикл соединения, управление подписками и состояние соединения от UI-компонентов.
Ключевая идея заключается в создании composable-функции, которая инкапсулирует:
import { ref, shallowRef, onBeforeUnmount } from "vue";
import { Client } from "@stomp/stompjs";
export function useStomp(options) {
const client = shallowRef(null);
const connected = ref(false);
const error = ref(null);
const subscriptions = new Map();
function connect() {
client.value = new Client({
brokerURL: options.url,
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
onConnect: () => {
connected.value = true;
error.value = null;
if (options.onConnect) {
options.onConnect();
}
},
onStompError: (frame) => {
error.value = frame;
},
onWebSocketClose: () => {
connected.value = false;
}
});
client.value.activate();
}
function disconnect() {
if (client.value) {
client.value.deactivate();
client.value = null;
connected.value = false;
}
}
onBeforeUnmount(() => {
disconnect();
});
return {
client,
connected,
error,
connect,
disconnect
};
}
STOMP предполагает работу с подписками через destination-каналы. В Composition API это удобно инкапсулировать через Map, где ключом выступает маршрут, а значением — объект подписки.
function subscribe(destination, callback) {
if (!client.value || !connected.value) {
throw new Error("STOMP client is not connected");
}
const subscription = client.value.subscribe(destination, (message) => {
let parsed = message.body;
try {
parsed = JSON.parse(message.body);
} catch (e) {
// оставляем строку
}
callback(parsed, message);
});
subscriptions.set(destination, subscription);
return () => unsubscribe(destination);
}
function unsubscribe(destination) {
const sub = subscriptions.get(destination);
if (sub) {
sub.unsubscribe();
subscriptions.delete(destination);
}
}
Composition API позволяет строить реактивные потоки данных поверх STOMP сообщений.
import { ref } from "vue";
export function useChatChannel(stomp, roomId) {
const messages = ref([]);
function init() {
stomp.subscribe(`/topic/chat/${roomId}`, (data) => {
messages.value.push(data);
});
}
function sendMessage(text) {
stomp.client.value.publish({
destination: `/app/chat/${roomId}`,
body: JSON.stringify({ text })
});
}
return {
messages,
init,
sendMessage
};
}
В больших приложениях STOMP слой не должен смешиваться с бизнес-логикой. Обычно выделяются три уровня:
Отвечает только за соединение и подписки.
Определяет конкретные каналы и бизнес-семантику сообщений.
Подписывается на реактивные данные.
STOMP.js поддерживает reconnectDelay, но этого недостаточно для сложных сценариев. В Composition API часто добавляется расширенная логика контроля состояния.
const retryCount = ref(0);
const maxRetries = 10;
function handleReconnect() {
if (retryCount.value >= maxRetries) {
error.value = "Max reconnect attempts reached";
return;
}
retryCount.value++;
}
Подобная логика позволяет реализовать более строгий контроль стабильности соединения, чем стандартный механизм клиента.
При использовании Pinia STOMP клиент часто выносится в глобальное хранилище, чтобы обеспечить единое соединение на всё приложение.
import { defineStore } from "pinia";
import { Client } from "@stomp/stompjs";
export const useStompStore = defineStore("stomp", {
state: () => ({
client: null,
connected: false
}),
actions: {
init(url) {
this.client = new Client({
brokerURL: url,
reconnectDelay: 5000,
onConnect: () => {
this.connected = true;
},
onWebSocketClose: () => {
this.connected = false;
}
});
this.client.activate();
},
subscribe(destination, callback) {
return this.client.subscribe(destination, (msg) => {
callback(JSON.parse(msg.body));
});
}
}
});
Composition API требует строгого соответствия жизненному циклу компонента и соединения STOMP.
onMounted → connect / subscribeonBeforeUnmount → unsubscribe / disconnectimport { onMounted, onBeforeUnmount } from "vue";
export function useRealtime(stomp) {
function start() {
stomp.connect();
}
function stop() {
stomp.disconnect();
}
onMounted(start);
onBeforeUnmount(stop);
return {
start,
stop
};
}
При большом количестве каналов возникает проблема избыточных подписок. Composition API позволяет централизовать контроль.
const activeTopics = new Set();
function safeSubscribe(topic, handler) {
if (activeTopics.has(topic)) {
return;
}
activeTopics.add(topic);
subscribe(topic, handler);
}
STOMP соединения должны учитывать:
onStompError: (frame) => {
error.value = {
type: "STOMP_ERROR",
message: frame.headers["message"]
};
}
В TypeScript-проектах STOMP слой становится строго типизированным.
type StompMessage<T> = {
body: T;
raw: any;
};
function subscribe<T>(
destination: string,
callback: (msg: StompMessage<T>) => void
) {
return client.subscribe(destination, (message) => {
callback({
body: JSON.parse(message.body),
raw: message
});
});
}
Одной из ключевых задач Composition API в связке со STOMP является предотвращение утечки транспортных деталей в UI.
UI не должен знать:
Вся эта информация инкапсулируется в composable, который предоставляет только:
export function useNotifications(stomp) {
const notifications = ref([]);
function init() {
stomp.subscribe("/topic/notifications", (data) => {
notifications.value.unshift(data);
});
}
function markAsRead(id) {
stomp.client.value.publish({
destination: "/app/notifications/read",
body: JSON.stringify({ id })
});
}
return {
notifications,
init,
markAsRead
};
}
Composition API позволяет строить слой WebSocket-коммуникаций как набор независимых composables:
Каждый из них использует общий транспорт, но не знает о других доменах.
Такой подход формирует масштабируемую модель realtime-приложения, где STOMP.js выступает единым протоколом доставки сообщений, а Vue Composition API — механизмом структурирования поведения.