Expo(React Native) 物联网开发:关系、路径与常见坑
前言
物联网应用的核心不是 UI,而是“设备连接 + 网络可靠性 + 协议稳定性”。React Native 负责跨平台渲染与业务逻辑,Expo 负责工程化和常见原生能力封装。当这两者进入 IoT 场景,你会发现它们的价值和边界都变得更清晰。
这篇文章从关系、路径与常见坑出发,帮助你在 RN/Expo + IoT 的组合中少走弯路。
关系:RN 是运行时,Expo 是工程平台
在 IoT 项目里可以这样理解:
- React Native 负责 UI 与业务逻辑跨端
- Expo 负责项目初始化、构建发布、权限配置与常用能力
- 设备通信层(BLE/Wi-Fi/MQTT)是业务真正的核心约束
因此,Expo 不会改变 IoT 的复杂度,它只是让你更快进入业务问题本身。
路径:IoT 连接模型的三种主流方案
1) BLE 直连
适用于近场设备,常见于传感器、可穿戴、简单家居设备。特点是低功耗、低带宽、连接不稳定。
典型流程:
- 扫描 -> 连接 -> 发现服务 -> 读写/订阅 -> 断线重连
BLE 直连如何实现(Expo Prebuild 版)
说明:BLE 能力通常需要 Prebuild/Bare,Managed 模式可能不足。
- 安装依赖并预生成原生工程
npm i react-native-ble-plx
expo prebuild
- 配置权限
- iOS:在
Info.plist中加入蓝牙说明NSBluetoothAlwaysUsageDescriptionNSBluetoothPeripheralUsageDescription
- Android:Android 12+ 需要
BLUETOOTH_SCAN、BLUETOOTH_CONNECT权限;Android 11 及以下需定位权限
- 最小可运行逻辑(扫描/连接/读写)
import { useEffect, useMemo, useState } from "react";
import { Platform, PermissionsAndroid } from "react-native";
import { BleManager } from "react-native-ble-plx";
const SERVICE_UUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
const WRITE_CHAR_UUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
export function useBleConnection() {
const manager = useMemo(() => new BleManager(), []);
const [deviceId, setDeviceId] = useState<string | null>(null);
useEffect(() => {
return () => manager.destroy();
}, [manager]);
async function ensurePermissions() {
if (Platform.OS !== "android") return true;
if (Platform.Version >= 31) {
const scan = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN
);
const connect = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT
);
return scan === "granted" && connect === "granted";
}
const fine = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
);
return fine === "granted";
}
async function scanAndConnect() {
const ok = await ensurePermissions();
if (!ok) return null;
return new Promise<string | null>((resolve) => {
const timeout = setTimeout(() => {
manager.stopDeviceScan();
resolve(null);
}, 10000);
manager.startDeviceScan([SERVICE_UUID], null, async (error, device) => {
if (error || !device) return;
manager.stopDeviceScan();
clearTimeout(timeout);
const connected = await device.connect();
await connected.discoverAllServicesAndCharacteristics();
setDeviceId(connected.id);
resolve(connected.id);
});
});
}
async function write(dataBase64: string) {
if (!deviceId) return;
const device = await manager.connectToDevice(deviceId);
await device.writeCharacteristicWithResponseForService(
SERVICE_UUID,
WRITE_CHAR_UUID,
dataBase64
);
}
return { scanAndConnect, write };
}
- 断线重连
- 建议为每个连接加超时
- 断线后延迟重试,并限制最大次数
- UI 与连接状态机分离,避免“显示已连但写失败”
2) 云端中转(MQTT/HTTP)
适用于联网设备,通过服务端中转指令与状态。特点是远程控制稳定,但引入服务端复杂度。
典型流程:
- 设备上云 -> App 订阅/发布 -> 服务端协作
云端中转如何实现(MQTT 版)
说明:App 端用 MQTT 订阅/发布,设备端连接 Broker 上报与接收指令。
- 设备端接入 Broker
- 设备连接 MQTT Broker,定期上报状态
- 订阅指令主题,收到后执行并回传结果
- 服务端职责
- 设备鉴权与主题隔离
- 保留设备最新状态(缓存/数据库)
- 下发指令并记录执行结果
- App 端最小示例(订阅/发布)
import { useEffect, useRef } from "react";
import mqtt, { MqttClient } from "mqtt";
const BROKER_URL = "wss://broker.example.com:8083/mqtt";
const TOPIC_STATUS = "device/123/status";
const TOPIC_COMMAND = "device/123/command";
export function useMqtt() {
const clientRef = useRef<MqttClient | null>(null);
useEffect(() => {
const client = mqtt.connect(BROKER_URL, {
clientId: `app-${Date.now()}`,
keepalive: 30,
reconnectPeriod: 2000,
clean: true,
});
client.on("connect", () => {
client.subscribe(TOPIC_STATUS);
});
client.on("message", (_topic, payload) => {
const message = payload.toString();
console.log("device status", message);
});
clientRef.current = client;
return () => client.end(true);
}, []);
function publishCommand(data: string) {
clientRef.current?.publish(TOPIC_COMMAND, data, { qos: 1 });
}
return { publishCommand };
}
- 可靠性建议
- 关键指令启用 QoS 1,避免丢失
- 断线重连后补一次状态拉取
- 对同一指令做幂等处理(服务端/设备端)
3) 混合模式(BLE + 云)
最常见的商业路径:近场 BLE 做配网和配置,远程通过云端控制。
典型流程:
- BLE 配网 -> 设备入云 -> 云端控制与同步
混合模式如何实现(配网 + 云控)
- BLE 配网
- App 扫描设备,连接后写入 Wi-Fi SSID/密码
- 设备收到配置后连接路由器
- 设备入云
- 设备连上网络后向服务端注册
- 返回设备 ID/Token,用于后续鉴权
- App 切换到云端控制
- 本地配网完成后,App 直接使用 MQTT/HTTP 控制
- 本地 BLE 作为“近场兜底”或高级配置通道
- 状态一致性策略
- 云端保存设备最新状态
- App 前台启动时拉取最新状态
- 关键操作通过云端确认回执
混合模式最小可运行示例(JSON + MQTT)
说明:BLE 配网使用 JSON 字符串,通过特征值写入;云端通信使用 MQTT。
- BLE 配网写入(示例)
import { BleManager } from "react-native-ble-plx";
const SERVICE_UUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
const WRITE_CHAR_UUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
function toBase64(data: string) {
return Buffer.from(data, "utf8").toString("base64");
}
export async function sendWifiConfig(
manager: BleManager,
deviceId: string,
ssid: string,
password: string
) {
const payload = {
ssid,
password,
timestamp: Date.now(),
};
const json = JSON.stringify(payload);
const base64 = toBase64(json);
const device = await manager.connectToDevice(deviceId);
await device.discoverAllServicesAndCharacteristics();
await device.writeCharacteristicWithResponseForService(
SERVICE_UUID,
WRITE_CHAR_UUID,
base64
);
}
- 设备端配网回执(约定)
- 设备写回 JSON:
{ "ok": true, "code": 0 } - App 订阅 notify 特征值并解析结果
- MQTT 主题设计(示例)
- 状态上报:
device/{deviceId}/status - 指令下发:
device/{deviceId}/command - 执行回执:
device/{deviceId}/reply
- App 端云控最小示例
import mqtt from "mqtt";
const BROKER_URL = "wss://broker.example.com:8083/mqtt";
export function connectMqtt(deviceId: string) {
const client = mqtt.connect(BROKER_URL, {
clientId: `app-${Date.now()}`,
keepalive: 30,
reconnectPeriod: 2000,
});
const statusTopic = `device/${deviceId}/status`;
const commandTopic = `device/${deviceId}/command`;
client.on("connect", () => {
client.subscribe(statusTopic);
});
client.on("message", (_topic, payload) => {
console.log("status", payload.toString());
});
function sendCommand(cmd: string) {
client.publish(commandTopic, cmd, { qos: 1 });
}
return { client, sendCommand };
}
- 一致性处理建议
- App 启动时走一次 HTTP 拉取最新状态
- MQTT 仅做实时推送与指令通道
- 关键操作要求设备回执,不做“单向写入”
Expo 在 IoT 项目中的常见定位
Expo 的能力可以覆盖大部分 IoT App 的“外围问题”,但设备通信本身经常需要更深的原生能力:
- 适合 Expo Managed:快速原型、轻量 BLE/网络通信、对原生依赖少
- 需要 Prebuild/Bare:复杂 BLE 协议、长期后台、厂商 SDK、深度权限控制
简单判断:只要连接链路或设备 SDK 需要原生定制,就要提前准备进入 Prebuild 形态。
常见坑与现实限制
1) BLE 在真实环境下不稳定
实验室能连上,现场却经常断开。干扰、距离、电量、系统省电都会破坏连接稳定性。
建议:
- 设计明确的超时与重试策略
- UI 与真实连接状态分离
- 以状态机驱动连接流程
2) 权限与系统策略导致“扫不到”
Android 不同版本权限差异大,iOS 也有蓝牙权限弹窗限制。
建议:
- 统一封装权限检查流程
- 明确提示权限拒绝后的引导路径
- 扫描前检查系统蓝牙开关
3) 后台与省电策略导致连接失效
IoT App 常被误认为需要“后台长连接”,但系统对后台连接限制非常严格。
建议:
- 关键交互尽量在前台完成
- 不把后台连接当作业务刚需
- 设计“回到前台自动恢复”的体验
4) 设备协议变更导致功能失效
固件升级、协议字段变更、服务 UUID 变化都会让 App 看似连接成功却无法操作。
建议:
- 在通信层加入能力检测
- 版本号协商或配置拉取
- 关键错误码要有可追踪日志
5) MQTT 断线与消息顺序问题
移动网络波动会导致 MQTT 重连频繁,消息乱序或丢失。
建议:
- 配置合理的重连与退避策略
- 对关键消息做幂等与重发处理
- 重要状态走“拉取补偿”而不是只依赖推送
工程实践建议
- 构建清晰的连接状态机,避免 UI 与连接状态不一致
- 上报关键日志与设备状态,方便现场问题定位
- 建立设备型号与固件兼容表,避免不可控升级
- 分环境配置与灰度策略,不要一次性放量
结语
React Native 与 Expo 是 IoT App 的效率引擎,但 IoT 的核心复杂度来自设备与网络。理解它们的分工,再把连接链路当作“不稳定输入”去设计,你就能把真正的稳定性交付给用户。