React中Hermes引擎报不可变对象赋值错误求助
问题:React Native中AWS IoT MQTT连接触发不可变对象修改错误
未尝试修改任何不可变值,但页面加载时反复出现错误:
Error: You attempted to set the key '_i' with the value '1' on an object that is meant to be immutable and has been frozen., js engine: hermes连接已成功建立,但单独运行页面代码时无错误,使用应用的Stack Navigator导航时则会出现该错误。以下是使用AWSIoTData建立MQTT连接的useEffect代码:
useEffect(() => { let dataPayload const clients = AWSIoTData.device({ clientId: '', protocol: 'wss', accessKeyId: accessKeyId, secretKey: secretKey, keyPath: privatePathIs, certPath: certificatePathIs, caPath: caPathIs, host: host, port: 443, debug: true }); clients.on('connect', function () { console.log('Connected'); clients.subscribe(topic); }); clients.on('reconnect', function () { console.log('reconnects'); // clients.subscribe('example-topic'); }); clients.on('error', function (err) { console.log('Error', err); }); clients.on('message', async function (topic, payload) { var parsedData = JSON.parse(payload.toString()); // let dataConvert = bin2String(payload) dataPayload = await VecMqttPayloadBeautify(topic, parsedData); dispatch({ type: actionTypes.SET_ALLDATA_PAYLOAD, payload: dataPayload, }); // console.log('inner message', parsedData); // console.log('inner payload', dataPayload); }); }, [])
原因分析与解决办法
核心原因
- 未清理MQTT客户端实例:Stack Navigator默认会保留栈中之前的页面组件实例,你的useEffect没有清理逻辑,导致多个MQTT客户端同时运行,客户端内部的重试或状态更新逻辑可能修改了被React冻结的对象(比如Redux状态或组件内部不可变状态)。
- 空clientId导致连接冲突:AWS IoT要求每个连接的clientId唯一,空字符串会导致重复连接,客户端的重试机制触发内部对象修改,进而触发冻结对象错误。
- 异步dispatch的不安全操作:
message回调中的异步函数完成后,组件可能已经被卸载或状态对象已被冻结,此时dispatch可能意外修改了不可变对象。
解决步骤
1. 添加组件卸载时的清理逻辑
在useEffect中返回清理函数,断开MQTT连接并移除所有监听,避免残留实例干扰:
useEffect(() => { let dataPayload // 生成唯一clientId,避免重复连接 const clientId = `mqtt-client-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const clients = AWSIoTData.device({ clientId: clientId, protocol: 'wss', accessKeyId: accessKeyId, secretKey: secretKey, keyPath: privatePathIs, certPath: certificatePathIs, caPath: caPathIs, host: host, port: 443, debug: true }); // 原有的回调逻辑保持不变 clients.on('connect', function () { console.log('Connected'); clients.subscribe(topic); }); clients.on('reconnect', function () { console.log('reconnects'); }); clients.on('error', function (err) { console.log('Error', err); }); clients.on('message', async function (topic, payload) { var parsedData = JSON.parse(payload.toString()); dataPayload = await VecMqttPayloadBeautify(topic, parsedData); dispatch({ type: actionTypes.SET_ALLDATA_PAYLOAD, payload: dataPayload, }); }); // 清理函数:组件卸载时断开连接 return () => { if (clients) { clients.end(); // 关闭MQTT连接 clients.removeAllListeners(); // 移除所有事件监听 } }; }, [accessKeyId, secretKey, privatePathIs, certificatePathIs, caPathIs, host, topic, dispatch])
注意:必须把useEffect依赖数组中用到的所有外部变量(如accessKeyId、topic、dispatch)都加进去,避免闭包导致的旧值引用问题。
2. 确保clientId唯一
绝对不能使用空字符串作为clientId,用时间戳+随机字符串生成唯一标识,避免AWS IoT端的连接冲突。
3. 安全处理异步dispatch
在dispatch前检查组件是否仍处于挂载状态,避免对已卸载组件的状态进行操作:
useEffect(() => { let isMounted = true; // 标记组件是否挂载 const clientId = `mqtt-client-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const clients = AWSIoTData.device({ /* 配置不变 */ }); clients.on('message', async function (topic, payload) { var parsedData = JSON.parse(payload.toString()); dataPayload = await VecMqttPayloadBeautify(topic, parsedData); // 仅在组件挂载时执行dispatch if (isMounted) { dispatch({ type: actionTypes.SET_ALLDATA_PAYLOAD, payload: dataPayload, }); } }); return () => { isMounted = false; // 组件卸载时标记为未挂载 clients.end(); clients.removeAllListeners(); }; }, [/* 依赖项 */])
内容的提问来源于stack exchange,提问作者Arpit
相关产品推荐
相关产品推荐

