Bir Vue MQTT client'ı, Vue 3 uygulamanızın cihaz verilerini bir API'yi yoklamadan, yayınlandığı anda göstermesini sağlar. Bu eğitimde MQTT.js etrafında yeniden kullanılabilir bir useMqtt composable'ı yazacak, onu güvenli WebSocket üzerinden bir broker'a bağlayacak ve komut düğmesi olan canlı bir sensör widget'ı oluşturacaksınız. Bu süreçte birçok Vue MQTT uygulamasını yavaşlatan veya bellek sızdıran reaktivite ve yaşam döngüsü hatalarından da kaçınacaksınız.
Vue MQTT'ye Nasıl Bağlanır?
Vue tarayıcıda çalışır ve tarayıcılar TCP soketi açamaz; bu yüzden MQTT bir WebSocket içinde taşınır. MQTT.js'e bir wss:// URL'si verdiğinizde bunu şeffaf bir şekilde halleder. ConnectMQ broker'ları güvenli WebSocket'i 8084 portunda /mqtt yoluyla sunar:
wss://broker.connectmq.com:8084/mqtt
broker.connectmq.com adresindeki ücretsiz public broker, boş olmayan ve benzersiz herhangi bir client ID ile anonim bağlantıları kabul eder; bu da onu geliştirme için ideal kılar. Public alanı herkes tarafından okunabilir; bu yüzden rastgele bir topic öneki ve yalnızca test verisi kullanın. Taşıma katmanına dair arka plan bilgisi için WebSocket üzerinden MQTT yazısına bakın.
Proje Kurulumu
npm create vue@latest vue-mqtt-demo
cd vue-mqtt-demo
npm install
npm install mqtt
npm run dev
İskelet oluşturma sorularında varsayılanları kabul edin; bu eğitim için TypeScript, Pinia ve router isteğe bağlıdır. MQTT.js v5, Vite'ın otomatik olarak çözümlediği bir tarayıcı derlemesi içerir.
useMqtt Composable'ını Yazmak
src/composables/useMqtt.js dosyasını oluşturun:
import { ref, reactive, readonly, onMounted, onBeforeUnmount } from "vue";
import mqtt from "mqtt";
export function useMqtt(url, options = {}, topics = []) {
const status = ref("idle");
const error = ref(null);
// topic başına son mesaj: { [topic]: { value, receivedAt } }
const messages = reactive({});
// düz değişken, reactive DEĞİL: Vue MQTT client'ını asla proxy'lememeli
let client = null;
function parse(payload) {
const text = payload.toString();
try {
return JSON.parse(text);
} catch {
return text;
}
}
function connect() {
status.value = "connecting";
client = mqtt.connect(url, {
protocolVersion: 5,
reconnectPeriod: 3000,
connectTimeout: 10000,
...options,
});
client.on("connect", () => {
status.value = "connected";
error.value = null;
if (topics.length) client.subscribe(topics, { qos: 0 });
});
client.on("reconnect", () => (status.value = "reconnecting"));
client.on("offline", () => (status.value = "offline"));
client.on("error", (err) => (error.value = err.message));
client.on("message", (topic, payload) => {
messages[topic] = { value: parse(payload), receivedAt: Date.now() };
});
}
function publish(topic, message, publishOptions = { qos: 0 }) {
if (!client || !client.connected) return false;
const body = typeof message === "string" ? message : JSON.stringify(message);
client.publish(topic, body, publishOptions);
return true;
}
function disconnect() {
if (client) {
client.end(true);
client = null;
}
status.value = "disconnected";
}
onMounted(connect);
onBeforeUnmount(disconnect);
return {
status: readonly(status),
error: readonly(error),
messages: readonly(messages),
publish,
disconnect,
};
}
Tasarım Tercihlerinin Açıklaması
| Tercih | Neden |
|---|---|
| Client düz bir değişkende tutulur | MQTT client'ını ref veya reactive içine sarmak Vue'nun iç yapısını proxy'lemesine yol açar; bu performansa mal olur ve olay işlemeyi bozabilir. State içinde saklamanız gerekiyorsa shallowRef veya markRaw kullanın. |
| Topic başına son değer | Her mesajı içeren büyüyen bir dizi, uzun süre çalışan panolarda bellek sızıntısına yol açar. |
connect handler'ı içinde abone olma |
Abonelikler yeniden bağlantılardan sonra otomatik olarak geri yüklenir. |
onBeforeUnmount cleanup |
Sayfadan ayrılmak, sınırınıza dahil edilen hayalet bir bağlantı bırakmak yerine soketi kapatır. |
readonly dönüş değerleri |
Bileşenler paylaşılan bağlantı durumunu yanlışlıkla değiştiremez. |
Vue, DOM güncellemelerini tick başına gruplar; bu nedenle saniyede birkaç mesaj için gelen her mesajda messages nesnesine yazmak sorun değildir. Bir cihaz çok daha hızlı yayın yapıyorsa mesajları düz bir nesnede tamponlayın ve React MQTT hook'u yazısında anlatıldığı gibi bir zamanlayıcıyla messages içine kopyalayın.
Canlı Bir Sensör Widget'ı
src/components/SensorWidget.vue dosyasını oluşturun:
<script setup>
import { computed } from "vue";
import { useMqtt } from "../composables/useMqtt";
const props = defineProps({
prefix: { type: String, required: true },
device: { type: String, required: true },
});
const clientId = `vue-${Math.random().toString(16).slice(2, 10)}`;
const telemetryTopic = `${props.prefix}/${props.device}/telemetry`;
const { status, messages, publish } = useMqtt(
"wss://broker.connectmq.com:8084/mqtt",
{ clientId },
[telemetryTopic]
);
const reading = computed(() => messages[telemetryTopic]?.value ?? {});
const updatedAt = computed(() => {
const ts = messages[telemetryTopic]?.receivedAt;
return ts ? new Date(ts).toLocaleTimeString() : "never";
});
function toggleFan() {
publish(`${props.prefix}/${props.device}/cmd`, { action: "toggle-fan" }, { qos: 1 });
}
</script>
<template>
<div class="widget">
<header>
<h3>{{ device }}</h3>
<span :class="['status', status]">{{ status }}</span>
</header>
<p class="big">{{ reading.temperature ?? "--" }} °C</p>
<p>Humidity: {{ reading.humidity ?? "--" }} %</p>
<small>Updated: {{ updatedAt }}</small>
<button :disabled="status !== 'connected'" @click="toggleFan">Toggle fan</button>
</div>
</template>
<style scoped>
.widget { border: 1px solid #ccc; border-radius: 8px; padding: 1rem; max-width: 280px; }
.big { font-size: 2rem; margin: 0.5rem 0; }
.status.connected { color: green; }
.status.reconnecting, .status.offline { color: orange; }
</style>
Bunu src/App.vue içinde kullanın:
<script setup>
import SensorWidget from "./components/SensorWidget.vue";
const prefix = "connectmq/demo/vue-c42e";
</script>
<template>
<main>
<h1>Vue MQTT Demo</h1>
<SensorWidget :prefix="prefix" device="greenhouse1" />
</main>
</template>
Client ID, bileşen örneği başına yalnızca bir kez çalışan <script setup> içinde oluşturulur; bu nedenle yeniden render'lar boyunca sabit kalır.
Test Verisi Gönderin
Bir terminalden ücretsiz public MQTT broker'a ölçümler yayınlayın:
for i in $(seq 1 30); do
mosquitto_pub -h broker.connectmq.com -p 1883 -i vue-pub-c42e \
-t connectmq/demo/vue-c42e/greenhouse1/telemetry \
-m "{\"temperature\": $((18 + RANDOM % 8)), \"humidity\": $((55 + RANDOM % 15))}"
sleep 1
done
Widget her saniye güncellenir. Düğmeye basın ve komutların geldiğini mosquitto_sub -h broker.connectmq.com -p 1883 -i vue-sub-c42e -v -t "connectmq/demo/vue-c42e/+/cmd" ile izleyin.
Tüm Uygulama İçin Tek Bağlantı
Yukarıdaki composable, onu kullanan her bileşen için bir bağlantı açar. Tek bir widget için bu sorun değildir, ancak yirmi widget'lı bir pano yirmi bağlantı açardı. Daha iyi iki model:
- Modül düzeyinde singleton. Client'ı ve
messagesnesnesini fonksiyonun dışına taşıyın ki her çağrı bunları paylaşsın; ne zaman bağlantıyı keseceğinizi bilmek için kullanıcıları sayın. Basit ve bağımlılıksızdır. - Pinia store. Client'ı bir store action'ında oluşturun,
statusvemessagesdeğerlerini store state'i olarak tutun ve bileşenlerin store'dan okumasını sağlayın. Bu, Vue Devtools ve sunucu tarafı render korumalarıyla uyumlu çalışır.
İşte singleton yaklaşımının src/composables/useSharedMqtt.js olarak kaydedilmiş kompakt bir sürümü:
import { ref, reactive, readonly, onBeforeUnmount } from "vue";
import mqtt from "mqtt";
const status = ref("idle");
const messages = reactive({});
const topicUsers = new Map();
let client = null;
let users = 0;
function ensureClient(url, options) {
if (client) return;
client = mqtt.connect(url, { protocolVersion: 5, reconnectPeriod: 3000, ...options });
client.on("connect", () => {
status.value = "connected";
const filters = [...topicUsers.keys()];
if (filters.length) client.subscribe(filters);
});
client.on("reconnect", () => (status.value = "reconnecting"));
client.on("offline", () => (status.value = "offline"));
client.on("message", (topic, payload) => {
let value = payload.toString();
try { value = JSON.parse(value); } catch { /* metin olarak bırak */ }
messages[topic] = { value, receivedAt: Date.now() };
});
}
export function useSharedMqtt(url, options, topicFilter) {
ensureClient(url, options);
users++;
const count = topicUsers.get(topicFilter) ?? 0;
topicUsers.set(topicFilter, count + 1);
if (count === 0 && client.connected) client.subscribe(topicFilter);
onBeforeUnmount(() => {
const left = topicUsers.get(topicFilter) - 1;
if (left === 0) {
topicUsers.delete(topicFilter);
client?.unsubscribe(topicFilter);
} else {
topicUsers.set(topicFilter, left);
}
if (--users === 0) {
client.end(true);
client = null;
status.value = "disconnected";
}
});
return { status: readonly(status), messages: readonly(messages) };
}
Her widget useSharedMqtt fonksiyonunu kendi topic filtresiyle çağırır. İlk çağıran bağlantıyı açar, her topic'e kaç widget gösterirse göstersin yalnızca bir kez abone olunur ve unmount edilen son widget soketi kapatır. messages, topic'e göre anahtarlanmış tek bir reaktif nesne olduğundan bir widget'ın computed özellikleri yalnızca okudukları belirli anahtar değiştiğinde yeniden hesaplanır; bu da büyük panoları duyarlı tutar.
Her iki durumda da her tarayıcı sekmesinde tek bir MQTT bağlantısı olur; bu da sizi bağlantı sınırlarının rahatça içinde tutar ve broker'ın client görünümünü anlamayı kolaylaştırır.
Vue MQTT Performans İpuçları
Birkaç alışkanlık, bir Vue MQTT panosu tek widget'tan düzinelercesine büyürken akıcı kalmasını sağlar:
- Son değeri saklayın, gerisini türetin. Ham mesajları composable'da tutun ve yuvarlanmış sayılar, birimler ve renkler gibi görüntü değerlerini
computedile hesaplayın. Computed özellikler önbelleğe alınır; yalnızca bağlı oldukları mesaj değiştiğinde yeniden çalışırlar. - Grafik geçmişine sınır koyun. Bir widget sparkline için kayan bir seri tutuyorsa yeni noktaları sabit uzunlukta bir diziye ekleyin ve en eskisini atın. Sınırsız diziler, birkaç saat sonra yavaşlayan panoların en yaygın nedenidir.
- İlk görüntü için retained message'ları kullanın. Cihazlar durumu retain bayrağıyla yayınladığında, sayfa geçişinden sonra mount edilen bir widget boş bir yer tutucu yerine güncel değeri hemen gösterir.
messagesüzerinde derin watcher'lardan kaçının.watch(messages, ..., { deep: true })her topic'teki her güncellemede tetiklenir. Bunun yerine ilgilendiğiniz tek anahtarı izleyin.- Eskimişliği gösterin. Dakikalardır değişmeyen bir değer cihazın çevrimdışı olduğu anlamına gelebilir. Widget'ın yaptığı gibi güncelleme zamanını göstermek, insanların donmuş sayılara güvenmesini önler.
Yaygın Vue MQTT Tuzakları
- Client
ref()içine sarılmış. Belirtiler arasında yavaş performans veya MQTT.js içinden gelen tuhaf hatalar vardır. Onu düz bir değişkende veyashallowRefiçinde tutun. - Bağlan-kop döngüsü. İki bileşen veya sekme sabit bir client ID'yi paylaşıyor. Bağlantı başına benzersiz ID'ler üretin.
- Karma içerik (mixed content) hataları. Sayfa HTTPS üzerinden sunuluyor ama URL
ws://kullanıyor. 8084 portundawss://kullanın. - Ağdaki kısa bir kesintiden sonra hiçbir şey güncellenmiyor. Abonelikler clean session ile
connecthandler'ı dışında yapılmış. - SSR çökmeleri. Nuxt veya başka bir SSR kurulumunda yalnızca client tarafında bağlanın;
onMountedbunu zaten garanti eder.
Üretim Kurulumu
Gerçek bir uygulama için kimlik bilgileri olan private bir broker'a bağlanın. ConnectMQ üzerinde portalda bir broker oluşturun, ardından üretilen kullanıcı adını ve parolayı Vite ortam değişkenleri aracılığıyla iletin:
const { status, messages } = useMqtt(
"wss://YOUR_BROKER_HOST:8084/mqtt",
{
clientId,
username: import.meta.env.VITE_MQTT_USERNAME,
password: import.meta.env.VITE_MQTT_PASSWORD,
},
["factory1/+/telemetry"]
);
Frontend koduna paketlenen her şeyin kullanıcılar tarafından okunabileceğini unutmayın. Pano için özel bir kimlik bilgisi kullanın ve hassas komutları doğrudan tarayıcıdan değil bir backend API üzerinden gönderin. WebSocket portunda geçerli bir Let's Encrypt sertifikası olan barındırılan bir MQTT broker, tarayıcıların uyarı veya ek yapılandırma olmadan bağlanması anlamına gelir.
İlgili Eğitimler
Ekibiniz başka framework'ler de kullanıyorsa bu composable'ı RxJS üzerine kurulu Angular MQTT servisi ile karşılaştırın. Topic'ler ve payload'lar için derleme zamanı kontrolleri eklemek isterseniz TypeScript ile tip güvenli MQTT yazısına bakın. MQTT client kütüphaneleri rehberi backend servisleriniz için JavaScript dışı seçenekleri ele alır; Vue composable dokümantasyonu ise burada kullanılan modeli ayrıntılı olarak açıklar.
ConnectMQ ile Başlayın
Vue uygulamanıza birkaç dakika içinde güvenli WebSocket destekli private bir broker kazandırın. app.connectmq.com adresinden kaydolun, kredi kartı gerektirmeyen ücretsiz Serverless planını seçin, bir broker oluşturun ve kimlik bilgilerini useMqtt composable'ına ekleyin. Portaldaki ConnectMQ canlı topic gezgini, widget'larınızın tam olarak hangi mesajları alması gerektiğini gösterir.
Sık Sorulan Sorular
Vue ile hangi MQTT kütüphanesini kullanmalıyım?
Standart seçim MQTT.js'dir. Tarayıcıda WebSocket üzerinden çalışır, MQTT 3.1.1 ve 5.0'ı destekler ve bu yazıdaki gibi bir composable içine düzgünce sarılır.
Bu Vue 2 ile çalışır mı?
Composable, Vue 2.7'de yerleşik olarak bulunan Composition API'yi kullanır. Daha eski Vue 2 sürümleri için aynı mantığı bir bileşenin mounted ve beforeDestroy hook'larına koyun.
Widget'ım neden sürekli "reconnecting" gösteriyor?
Genellikle başka bir bağlantı aynı client ID'yi kullanıyordur, bir güvenlik duvarı 8084 portunu engelliyordur veya URL'de /mqtt yolu eksiktir. MQTT.js hata mesajı için tarayıcı konsolunu kontrol edin.
Bir Vue uygulamasından yayın yapabilir miyim?
Evet, composable'ın döndürdüğü publish fonksiyonuyla. Gerçek ekipmanı etkileyen işlemler için tarayıcı kullanıcılarının rastgele mesaj yayınlayamaması adına bunları kimliği doğrulanmış bir backend üzerinden yönlendirmeyi tercih edin.



