Next.js中MQTT事件内useState的setData失效及WebSocket连接问题
Next.js中async-mqtt接收消息无法更新React状态问题
问题描述
在Next.js应用中使用async-mqtt库实现MQTT协议,能正常接收MQTT消息(日志显示在本地终端而非浏览器),但在MQTT的message事件中调用useState的setData方法时,UI无任何变化;手动点击测试按钮调用setData却能正常工作。尝试将data作为依赖项加入useEffect,也仅在手动设置data时触发。
编辑:按建议修改后,客户端出现新错误:WebSocket connection to 'ws://broker.hivemq.com/' failed
组件代码
"use client" import React, { useEffect, useState } from 'react'; const MQTTSubscriber = () => { const[data, setData] = useState() const MQTT = require("async-mqtt"); const client = MQTT.connect("tcp://broker.hivemq.com"); const doStuff = async () => { console.log("Starting"); try { //await client.publish("my topic", "It works!"); await client.subscribe("my topic") console.log("Hello world") // This line doesn't run until the server responds to the publish //await client.end(); // This line doesn't run until the client has disconnected without error console.log("Done"); } catch (e){ // Do something about it! console.log(e.stack); process.exit(); } } client.on("message", (topic, message) => { const payload = { topic, message: message.toString() }; console.log(message.toString()); setData(message.toString()) //Here the issue, nothing happens. }); client.on("connect", doStuff); return ( <div> <button onClick={() => setData("Test")}> Set data manually test </button> <p>{"Data: " + data}</p> </div> ); }; export default MQTTSubscriber;
package.json
{ "name": "next-webgl-unity-mqtt", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "async-mqtt": "^2.6.3", "autoprefixer": "10.4.16", "bufferutil": "^4.0.7", "eslint": "8.49.0", "eslint-config-next": "13.5.2", "mqtt": "^5.0.5", "next": "13.5.2", "postcss": "8.4.30", "react": "18.2.0", "react-dom": "18.2.0", "tailwindcss": "3.3.3", "utf-8-validate": "^5.0.10" } }
原因与解决方法
核心问题分析
- MQTT客户端脱离React生命周期:组件函数内部直接创建客户端会导致每次重渲染都生成新实例,旧实例的事件监听捕获的是旧的
setData函数,调用后无法触发组件重渲染;同时浏览器不支持tcp://协议,只能用WebSocket,所以日志只在终端输出。 - WebSocket连接参数错误:HiveMQ公共代理的WebSocket默认端口不是80,直接用
ws://broker.hivemq.com会连接失败,需要指定正确端口。
修复后的代码
"use client" import React, { useEffect, useState } from 'react'; import mqtt from 'async-mqtt'; const MQTTSubscriber = () => { const [data, setData] = useState(''); useEffect(() => { // 组件挂载时创建MQTT客户端 const client = mqtt.connect('ws://broker.hivemq.com:8000'); // 消息处理函数 const handleMessage = (topic, message) => { const payloadStr = message.toString(); console.log(payloadStr); setData(payloadStr); }; // 连接成功后的订阅逻辑 const doStuff = async () => { try { await client.subscribe('my topic'); console.log('已成功订阅主题'); } catch (error) { console.error('订阅失败:', error); } }; // 绑定事件监听 client.on('connect', doStuff); client.on('message', handleMessage); // 组件卸载时清理资源 return () => { client.off('message', handleMessage); client.end(); }; }, []); // 空依赖数组确保只执行一次 return ( <div> <button onClick={() => setData('Test')}>手动设置数据测试</button> <p>数据: {data}</p> </div> ); }; export default MQTTSubscriber;
关键修复点
- 生命周期绑定:将客户端创建和事件监听放在
useEffect内,确保组件挂载时仅初始化一次,避免重复实例和闭包问题。 - WebSocket协议适配:使用
ws://broker.hivemq.com:8000(明文)或wss://broker.hivemq.com:8883(加密)连接,适配浏览器环境。 - 资源清理:在
useEffect返回函数中移除事件监听并关闭客户端,防止内存泄漏。 - 模块规范对齐:改用ES模块导入
async-mqtt,符合React组件的模块使用规范。
内容的提问来源于stack exchange,提问作者Mimmo
相关产品推荐
相关产品推荐

