IoT Eğitimleri

React ile MQTT Eğitimi: Hook Tabanlı Gerçek Zamanlı Pano

MQTT.js etrafında yeniden kullanılabilir bir useMqtt hook'u oluşturun, React'i güvenli WebSocket üzerinden bir broker'a bağlayın ve yeniden render fırtınaları, StrictMode sürprizleri veya sızan bağlantılar olmadan canlı sensör değerlerini gösterin.

Güvenli WebSocket bağlantısı üzerinden react mqtt ile oluşturulmuş canlı sensör kartlı tarayıcı panosu
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

React ve MQTT'yi birleştirmek, bir cihaz yayın yaptığı anda güncellenen panolar elde etmenizi sağlar; polling yoktur, özel bir WebSocket sunucusu da gerekmez. Bu eğitimde MQTT.js etrafında yeniden kullanılabilir bir useMqtt hook'u oluşturacak, onu güvenli WebSocket üzerinden bir broker'a bağlayacak ve mesajlar saniyede birkaç kez gelse bile hızlı kalan canlı sensör kartları göstereceksiniz. Her şey ücretsiz public broker üzerinde çalışır; böylece bir saatten kısa sürede çalışan bir panoya sahip olabilirsiniz.

React Bir MQTT Broker ile Nasıl Konuşur?

Tarayıcılar ham TCP soketi açamaz, bu nedenle bir React uygulaması 1883 veya 8883 portunu kullanamaz. Bunun yerine MQTT.js WebSocket üzerinden bağlanır ve broker bu WebSocket içinde MQTT konuşur. ConnectMQ broker'ları güvenli WebSocket'i 8084 portunda /mqtt yoluyla sunar; dolayısıyla URL şudur:

wss://broker.connectmq.com:8084/mqtt

HTTPS üzerinden sunulan uygulamalarda her zaman wss:// kullanın; tarayıcılar güvenli sayfalardan düz ws:// bağlantılarını karma içerik (mixed content) olarak engeller. WebSocket üzerinden MQTT rehberi taşıma katmanını daha ayrıntılı açıklar.

Proje Kurulumu

Bir Vite React projesi oluşturun ve MQTT.js'i kurun:

npm create vite@latest react-mqtt-dashboard -- --template react
cd react-mqtt-dashboard
npm install
npm install mqtt
npm run dev

MQTT.js v5, Vite gibi bundler'ların otomatik olarak algıladığı bir tarayıcı derlemesiyle gelir; bu yüzden Node.js polyfill'lerine gerek yoktur.

useMqtt Hook'unu Tasarlamak

İyi bir hook'un küçük ve öngörülebilir bir sözleşmesi vardır. Bizimki bir URL, bağlantı seçenekleri ve bir topic filtresi listesi alır; bağlantı durumunu, topic başına son mesajın tutulduğu bir haritayı ve bir publish fonksiyonunu döndürür. Tasarım kuralları:

Konu Karar Neden
Bağlantı ömrü useEffect içinde oluşturulur, cleanup içinde sonlandırılır Unmount veya hot reload'da sızan soket olmaz
StrictMode çift mount Cleanup ilk client'ı tamamen sonlandırır Geliştirme ortamı effect'leri iki kez mount eder
State yapısı Büyüyen bir dizi değil, topic başına son mesaj Bellek sabit kalır, render'lar ucuz kalır
Render hızı Güncellemeleri 250 ms'de bir toplu uygula Hızlı yayıncılar arayüzü dondurmaz
Seçenek kimliği Seçenekleri bir bağımlılık anahtarına serileştir Satır içi nesneler her render'da yeniden bağlanmaya yol açardı

useMqtt Hook'u

src/useMqtt.js dosyasını oluşturun:

import { useCallback, useEffect, useRef, useState } from "react";
import mqtt from "mqtt";

export function useMqtt(url, options, topics) {
  const [status, setStatus] = useState("connecting");
  const [messages, setMessages] = useState({});
  const clientRef = useRef(null);
  const pendingRef = useRef({});

  // Satır içi nesneler/diziler yeniden bağlanmayı tetiklemesin diye sabit anahtarlar
  const optionsKey = JSON.stringify(options);
  const topicsKey = JSON.stringify(topics);

  useEffect(() => {
    const opts = JSON.parse(optionsKey);
    const filters = JSON.parse(topicsKey);
    const client = mqtt.connect(url, {
      protocolVersion: 5,
      reconnectPeriod: 3000,
      connectTimeout: 10000,
      ...opts,
    });
    clientRef.current = client;

    client.on("connect", () => {
      setStatus("connected");
      client.subscribe(filters, { qos: 0 });
    });
    client.on("reconnect", () => setStatus("reconnecting"));
    client.on("offline", () => setStatus("offline"));
    client.on("error", (err) => {
      console.error("MQTT error", err);
      setStatus("error");
    });
    client.on("message", (topic, payload) => {
      let value;
      try {
        value = JSON.parse(payload.toString());
      } catch {
        value = payload.toString();
      }
      pendingRef.current[topic] = { value, receivedAt: Date.now() };
    });

    // Tamponlanan mesajları React state'ine saniyede en fazla 4 kez aktar
    const timer = setInterval(() => {
      const pending = pendingRef.current;
      if (Object.keys(pending).length === 0) return;
      pendingRef.current = {};
      setMessages((prev) => ({ ...prev, ...pending }));
    }, 250);

    return () => {
      clearInterval(timer);
      client.end(true);
      clientRef.current = null;
    };
  }, [url, optionsKey, topicsKey]);

  const publish = useCallback((topic, message, publishOptions = { qos: 0 }) => {
    const client = clientRef.current;
    if (!client || !client.connected) return false;
    const body = typeof message === "string" ? message : JSON.stringify(message);
    client.publish(topic, body, publishOptions);
    return true;
  }, []);

  return { status, messages, publish };
}

Neden bu şekilde kurulduğu:

  • Mesajlar önce bir ref'e düşer. Her mesaj için setMessages çağırmak ağacı her seferinde yeniden render eder. Mesajları pendingRef içinde toplayıp 250 ms'lik bir zamanlayıcıyla aktarmak, bir cihaz saniyede 20 kez yayın yapsa bile arayüzü akıcı tutar.
  • Cleanup içindeki client.end(true) soketi hemen kapatır. Geliştirme ortamında React StrictMode effect'i, cleanup'ı ve effect'i tekrar çalıştırır; bu yüzden ikinci client aynı client ID ile bağlanmadan önce ilk client tamamen kapatılmalıdır. Sonlandırmayı zorlamak, aynı ID'ye sahip iki oturumun birbiriyle çekiştiği kısa bir zaman aralığını önler.
  • connect handler'ı içinde abone olmak, her yeniden bağlantıdan sonra abonelikleri geri yükler.

Panoyu Oluşturmak

src/App.jsx dosyasını, birazdan düzelteceğimiz bir hata içeren şu ilk sürümle değiştirin:

import { useMemo } from "react";
import { useMqtt } from "./useMqtt";

const BROKER_URL = "wss://broker.connectmq.com:8084/mqtt";
const PREFIX = "connectmq/demo/react-58d2";

function SensorCard({ device, reading }) {
  const ageSeconds = Math.round((Date.now() - reading.receivedAt) / 1000);
  const { temperature, humidity } = reading.value ?? {};
  return (
    <div className="card">
      <h3>{device}</h3>
      <p className="big">{temperature ?? "--"} &deg;C</p>
      <p>Humidity: {humidity ?? "--"} %</p>
      <small>{ageSeconds > 30 ? `stale (${ageSeconds}s)` : "live"}</small>
    </div>
  );
}

export default function App() {
  const { status, messages, publish } = useMqtt(
    BROKER_URL,
    { clientId: `react-${Math.random().toString(16).slice(2, 10)}` },
    [`${PREFIX}/+/telemetry`]
  );

  const devices = useMemo(
    () =>
      Object.entries(messages)
        .map(([topic, reading]) => ({ device: topic.split("/").at(-2), reading }))
        .sort((a, b) => a.device.localeCompare(b.device)),
    [messages]
  );

  return (
    <main>
      <header>
        <h1>Live Sensors</h1>
        <span className={`status ${status}`}>{status}</span>
      </header>
      <section className="grid">
        {devices.map(({ device, reading }) => (
          <SensorCard key={device} device={device} reading={reading} />
        ))}
      </section>
      <button onClick={() => publish(`${PREFIX}/fan1/cmd`, { action: "toggle" }, { qos: 1 })}>
        Toggle fan1
      </button>
    </main>
  );
}

Bu dosyada bilinçli bir hata var: clientId satır içinde üretiliyor, dolayısıyla değeri her render'da değişiyor. Seçenekleri serileştirmek yeni nesne kimliklerine karşı koruma sağlar ama yeni değerlere karşı sağlamaz; bu yüzden her render yeni bir seçenek anahtarı ve yepyeni bir bağlantı üretir. ID'yi bir kez üreterek düzeltin:

import { useMemo, useState } from "react";

// App() içinde
const [clientId] = useState(() => `react-${Math.random().toString(16).slice(2, 10)}`);
const { status, messages, publish } = useMqtt(BROKER_URL, { clientId }, [`${PREFIX}/+/telemetry`]);

React MQTT kodundaki en yaygın hata budur: her render'da değişen seçenekler, broker sorunu gibi görünen bir bağlan-kop döngüsüne yol açar.

.grid ve .card sınıfları için src/index.css dosyasına biraz minimal CSS ekleyin veya tercih ettiğiniz bileşen kütüphanesini kullanın.

Test Verisiyle Besleyin

Bir terminalden public MQTT test broker'a ölçümler yayınlayın:

for i in $(seq 1 30); do
  mosquitto_pub -h broker.connectmq.com -p 1883 -i react-pub-58d2 \
    -t connectmq/demo/react-58d2/room1/telemetry \
    -m "{\"temperature\": $((20 + RANDOM % 5)), \"humidity\": $((40 + RANDOM % 10))}"
  sleep 1
done

Kartlar, her cihaz için ilk mesaj gelir gelmez görünür. Düğmeye tıklayın ve komutu mosquitto_sub -h broker.connectmq.com -p 1883 -i react-sub-58d2 -v -t "connectmq/demo/react-58d2/+/cmd" ile izleyin. Kendi rastgele önekinizi kullanın: public alan herkes tarafından okunabilir.

Tek Bir Bağlantıyı Bileşenler Arasında Paylaşmak

Hook, mevcut haliyle onu çağıran her bileşen için bir bağlantı açar. Bu tek bir sayfa için mükemmeldir, ancak başlıkta bir durum rozeti, bir cihaz listesi ve bir ayrıntı paneli olan daha büyük bir pano, örtüşen verileri almak için üç bağlantı açardı. Bunun yerine bağlantıyı bir context provider'a taşıyın:

import { createContext, useContext, useState } from "react";
import { useMqtt } from "./useMqtt";

const MqttContext = createContext(null);

export function MqttProvider({ url, topics, children }) {
  const [clientId] = useState(() => `react-${Math.random().toString(16).slice(2, 10)}`);
  const mqttState = useMqtt(url, { clientId }, topics);
  return <MqttContext.Provider value={mqttState}>{children}</MqttContext.Provider>;
}

export function useMqttContext() {
  const value = useContext(MqttContext);
  if (!value) throw new Error("useMqttContext must be used inside MqttProvider");
  return value;
}

Uygulamanızı <MqttProvider url={BROKER_URL} topics={[...]}> ile sarın ve altındaki her yerde status, messages ve publish değerlerini okuyun. messages değiştiğinde her bileşen yeniden render edilir; 250 ms'lik aktarım aralığında bu kabul edilebilir. Yüzlerce widget'ınız varsa mesajları Zustand gibi harici bir store'da tutun veya seçicilerle useSyncExternalStore kullanın; böylece her widget yalnızca kendi topic'i değiştiğinde yeniden render edilir.

React MQTT Uygulamaları İçin Üretim Hususları

  1. Tarayıcıdaki kimlik bilgileri herkese açıktır. JavaScript paketinizdeki her şey kullanıcılar tarafından okunabilir. Pano için özel bir kimlik bilgisi oluşturun; kullanıcıların komut yayınlamaması gerekiyorsa komutları tarayıcı yerine bir backend API üzerinden yönlendirin.
  2. Bağlantı durumunu gösterin. Kullanıcılar sessizce eskimiş sayılar yerine reconnecting görmelidir. Hook bunun için status değerini sunar ve her kart verisinin yaşını gösterir.
  3. Başlangıç durumu için retained message'ları kullanın. Cihazlar son durumlarını retain bayrağıyla yayınlarsa yeni yüklenen bir pano bir sonraki raporu beklemek yerine anında dolar.
  4. Bağlantı sınırlarına dikkat edin. Açık her tarayıcı sekmesi bir MQTT bağlantısıdır. Broker'ınızı boyutlandırırken bunu hesaba katın.
  5. Grafikleri yavaşlatın. Grafik eklerseniz noktaları mesaj başına değil aktarım zamanlayıcısında ekleyin ve geçmiş uzunluğuna bir sınır koyun.
  6. Arka plandaki sekmelerde duraklatın. Tarayıcılar gizli sekmelerdeki zamanlayıcıları yavaşlatır. Bir pano saatlerce arka planda kalıyorsa visibilitychange olayında bağlantıyı sonlandırmayı ve sekme tekrar görünür olduğunda yeniden bağlanmayı düşünün; görünümü retained message'lar yeniden doldurur.

Kendi Broker'ınıza Geçiş

Gerçek bir pano için ConnectMQ üzerinde private bir broker oluşturun ve üretilen kimlik bilgilerini hook'a 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"]
);

Her sekmenin benzersiz bir client ID'ye ihtiyacı vardır; client ID'lerin kimlik bilgilerine bağlı olduğu bir broker'da pano kimlik bilgisinin buna izin verdiğinden emin olun veya tarayıcı ile broker arasına küçük bir backend koyun. WebSocket portunda Let's Encrypt sertifikası olan bir bulut MQTT broker, wss:// bağlantısının her tarayıcıda sertifika uyarısı olmadan çalışması anlamına gelir.

İlgili Eğitimler

Aynı hook modeli diğer framework'lere de uyarlanabilir: Vue MQTT composable'ı ve RxJS ile Angular MQTT servisi yazılarına bakın. Sunucu tarafı kod için Node.js'te MQTT.js ile başlayın; kütüphanelerin daha geniş bir karşılaştırması için MQTT client kütüphaneleri rehberine göz atın.

ConnectMQ ile Başlayın

React panonuza 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 hook'una ekleyin. Portalın tarayıcı içi MQTT client'ı, bileşenlerinizi tasarlarken test değerleri yayınlamak için idealdir.

Sık Sorulan Sorular

React, 1883 portunda MQTT'ye bağlanabilir mi?

Hayır. Tarayıcılar TCP soketi açamaz, bu yüzden React'in WebSocket üzerinden MQTT kullanması gerekir. ConnectMQ'da bu, 8084 portunda /mqtt yoluyla wss:// anlamına gelir.

React uygulamam geliştirme ortamında neden iki kez bağlanıyor?

React StrictMode, cleanup hatalarını ortaya çıkarmak için geliştirme ortamında effect'leri iki kez mount eder. client.end() çağıran düzgün bir cleanup ile ilk bağlantı hemen kapanır; üretim derlemeleri yalnızca bir kez bağlanır.

MQTT client'ını React context'ine koymalı mıyım?

Birçok bileşen aynı bağlantıya ihtiyaç duyuyorsa evet. Client'ı aynı effect mantığını kullanan bir provider içinde oluşturun ve status, messages ve publish değerlerini context üzerinden sunun; böylece sekme başına yine tek bir bağlantınız olur.

Resmi bir React MQTT kütüphanesi var mı?

Resmi bir kütüphane yok. Küçük topluluk sarmalayıcıları mevcuttur, ancak bu yazıdaki gibi MQTT.js etrafında yazılmış bir hook kısadır, tamamen sizin kontrolünüzdedir ve kolayca uyarlanabilir.

İlgili yazılar

Test, Betik ve Hata Ayıklama için En İyi MQTT CLI Araçları

mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, MQTT.js CLI, curl ve mqttui'nin uygulamalı karşılaştırması; betik yazma ve hata ayıklama için doğru aracı seçebilmeniz için aynı test broker.connectmq.com üzerinde çalıştırıldı.

7 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.