React Native + MQTT 实战:智能家居模块设计
技术栈:React Native 0.75 + Redux + MQTT
适用:IoT App、智能家居、设备远程控制
一、功能是什么
业主在 App 上控制家里联网的智能设备(灯、空调、窗帘、地暖、新风、摄像头),核心场景:
| 场景 | 说明 |
|---|---|
| 设备控制 | 单设备开关/调节(空调模式、灯光亮度等) |
| 场景联动 | 一键执行"回家/离家/睡觉"等多设备组合动作 |
| 设备定时 | 指定设备在指定时间执行动作 |
| 状态实时同步 | UI 与设备状态最终一致 |
关键要求:
- UI 零延迟响应(用户操作完立即看到变化)
- 控制指令要实时到达设备
- 设备状态变化要实时回传到 UI
- 同一设备多端同步(家里人和业主同时控制)
二、用到的技术栈
| 技术 | 角色 | 为什么用它 |
|---|---|---|
| MQTT | 设备控制 + 状态推送 | Pub/Sub 模式天然适合 IoT,Topic 路由一对多,QoS 保证消息到达 |
| NB MQTT SDK(公司自研) | MQTT 客户端封装 | 屏蔽底层细节,提供 exec / fetchDeviceStates / subscribe 三个核心 API |
| Redux | 设备状态总线 | 多个页面共享同一份设备状态,避免组件间通信 |
| HOC 高阶组件 | 控制逻辑复用 | 8 种设备共用一套控制逻辑,只写 UI 不写业务 |
| 乐观更新(dispatch UPDATE_STATE) | UI 0ms 响应 | 不等服务器,先变 UI |
| HTTP API(getHostConfig) | 拿设备列表(静态数据) | 设备配置是低频数据,走 HTTP 便于 CDN/缓存 |
| WebSocket + 自家后端 | 不用于智能家居 | 项目里 WebSocket 只用于云对讲通知 |
三、核心协议原理(面试必备)
3.1 MQTT 是什么
MQTT = Message Queuing Telemetry Transport
- 应用层协议,跑在 TCP 之上(也可以走 WebSocket)
- 设计目标:IoT 设备在低带宽、不稳定网络下通信
- 三大角色:Publisher(发布者)、Subscriber(订阅者)、Broker(代理服务器)
核心思想:发布/订阅(Pub/Sub),不是客户端-服务端直连。
发布者 ──> Broker ──> 所有订阅了相同 Topic 的订阅者
3.2 Topic 是什么
Topic = 消息的"频道",用 / 分层,类似文件系统路径。
home_001/light_001/status ← 客厅灯状态
home_001/ac_001/status ← 空调状态
home_001/+/status ← + 通配符:home_001 下所有设备状态
订阅 home_001/+/status = 一次订阅所有设备状态变化。
3.3 为什么 IoT 用 MQTT 而不是 WebSocket
| 维度 | MQTT | WebSocket |
|---|---|---|
| 模型 | Pub/Sub(一对多) | C/S(一对一) |
| 一对多 | ✅ Topic 路由天然支持 | ❌ 要自己写循环 |
| 离线消息 | ✅ Broker 暂存 | ❌ 断了就丢 |
| 消息保留 | ✅ 新订阅者能拿最新 | ❌ 不支持 |
| QoS | ✅ 0/1/2 三级保证 | ❌ 没内建 |
| 头部大小 | 2 字节(最小) | 2-14 字节 |
| 适用 | IoT 设备 | Web 实时通信 |
3.4 HTTP vs MQTT 在本项目的取舍
| 业务 | 协议 | 原因 |
|---|---|---|
| 拿设备列表 | HTTP | 静态数据,一次性 |
| 拿设备初始状态 | MQTT | 一次拿全设备当前状态 |
| 订阅设备状态变化 | MQTT | 实时推送 |
| 控制设备 | MQTT | 实时双向 |
原则:静态/低频用 HTTP,实时/高频用 MQTT。
四、实现架构
┌─────────────────────────────────────────────────────────────────┐
│ 智能家居模块完整架构 │
└─────────────────────────────────────────────────────────────────┘
手机 App
│
▼
┌───────────────────────────────┐
│ withDevice HOC(核心) │
│ - 接收 device + control │
│ - connect Redux 拿状态 │
│ - 注入 onControl 函数 │
└───────────────────────────────┘
│
用户操作 │ 设备渲染
▼
┌───────────────────────────────┐
│ 设备组件(AC/Curtain/DimmingLight..)│
│ - 只关心 UI 渲染 │
│ - 调用 this.props.onControl(...) │
└───────────────────────────────┘
│
onControl │ 乐观更新 + 双通道下发
▼
┌────────────────────────────────────┐
│ 外网(99%):NB_SDK.exec(topic, data) │
│ ↓ MQTT │
│ 阿里云 MQTT Broker │
│ ↓ │
│ 家里智能家居主机 │
│ ↓ │
│ 设备执行 → 状态变化 → MQTT 上报 → │
│ NB_SDK.on('message') → dispatch │
└────────────────────────────────────┘
│
设备状态最终回到 Redux
▼
┌───────────────────────────────┐
│ totalState.states[DEVICE_ID] │
│ UI 自动重渲染(PureComponent) │
└───────────────────────────────┘
五、核心实现:withDevice HOC
5.1 完整代码
// src/components/Device/HOC/Device.js
export default withDevice = (WrappedComponent) => {
@connect(({
totalState, home, intranet}) => ({
states: totalState.states, // ← 设备实时状态(来自 Redux)
home, // ← 房屋列表 + 当前选中
isIntranet: intranet.isIntranet, // ← 是否内网模式
intranetIp: intranet.intranetIp,
}))
class Component extends React.PureComponent {
constructor(props) {
super(props);
const {
home: {
list, current}, device} = props;
const home = list && list.find(item => item.id == current);
this.topic = home && home.TOPIC; // ← MQTT 主题
this.device = device; // ← HOC 自己持有 device
}
onControl = (data) => {
const {
dispatch, intranetIp, isIntranet} = this.props;
const device = this.device;
data = {
deviceId: '' + device.DEVICE_ID, ...data};
// ① 乐观更新:UI 立即响应(0ms)
dispatch({
type: UPDATE_STATE, payload: data});
// ② 调光灯亮度转 16 进制(协议要求)
if (data.Brightness != null) {
data.Brightness = data.Brightness.toString(16);
}
// ③ 双通道:内网走 HTTP,外网走 MQTT
if (isIntranet) {
Api.IntranetControlDevice(intranetIp, device.DEVICE_ID, data);
} else {
NB_SDK.exec(this.topic, {
...data,
execMobile: global.phone,
platform: IS_IOS ? 'iOS' : 'Android',
appName: '星智家',
appVersion: global.version,
});
}
};
render() {
const {
states, status, ...resetProps} = this.props;
const state = states[device.DEVICE_ID] || {
};
return (
<WrappedComponent
device={
device} // ← 透传
onControl={
this.onControl} // ← 注入
state={
state} // ← 注入
{
...resetProps} // ← 其他 props 透传
/>
);
}
}
return hoistNonReactStatics(Component, WrappedComponent);
};
5.2 HOC 的 4 个核心设计
| 设计 | 作用 |
|---|---|
| 乐观更新 | UI 0ms 响应,不等云端 |
| 双通道 | 内网 HTTP / 外网 MQTT,根据 isIntranet 自动切换 |
| Topic 隔离 | 每个房屋有自己的 TOPIC,避免指令串扰 |
| PureComponent + hoistNonReactStatics | 避免不必要渲染 + 保留静态方法 |
5.3 设备组件用法
// src/components/Device/AC.js
@withDevice
export default class AC extends PureComponent {
setMode = mode => {
this.props.onControl({
Mode: mode}); // ← 调用 HOC 注入的
};
render() {
const {
device, state} = this.props; // ← 从 HOC 透传
return <View><Text>{
device.NAME}</Text></View>;
}
}
关键:8 种设备只用关心 UI,业务逻辑全部在 HOC 里。新增设备类型只需写 UI,零冗余。
六、完整调用流程
6.0 设备控制时序图

6.1 MQTT 初始化流程
用户登录成功
↓
Api.getHomesByToken(token)
↓
返回房屋列表(含 TOPIC、HOME_ID、hostId)
↓
dispatch(saveHomes(homes)) → Redux home.list
↓
dispatch(changeCurrentHome(homeId))
↓
dispatch(fetchTotalInfoAndStatus)
↓
┌────────────────────────────────────────────────────┐
│ 1. Api.getMqttConnectInfo(hostId) │
│ → 返回 {uri, userName, password, isEmqxCloud} │
│ 2. NB_SDK.initConnection({uri, clientId, ...}) │
│ 3. NB_SDK.connect({userName, password, ...}) │
│ 4. onConnected 回调 │
│ ├─ Api.getHostConfig(HOME_ID) → 拿设备列表 │
│ ├─ NB_SDK.fetchDeviceStates(topic) → 拿设备状态 │
│ └─ NB_SDK.connection.subscribe(topic) → 订阅变化 │
└────────────────────────────────────────────────────┘
6.2 设备控制流程
用户点击"调光灯 80%"
↓
DeviceCard 点击 → onModal({type, device})
↓
Home.js setState({modalProps: {type: 'DimmingLight', device}})
↓
<DimmingLight {...modalProps} /> (被 withDevice 包装)
↓
HOC 接 props(device, type...)
↓
用户操作:调用 this.props.onControl({Brightness: 80})
↓
HOC.onControl:
├─ dispatch UPDATE_STATE → Redux → UI 立即变 80%(乐观更新)
└─ NB_SDK.exec(topic, {Brightness: '50'}) ← 16 进制
↓
MQTT Broker → 主机 → 设备 → 状态变化 → MQTT 上报
↓
NB_SDK.on('message') → dispatch UPDATE_STATE → UI 最终一致
七、4 个关键技术参数
| 参数 | 来源 | 作用 |
|---|---|---|
| uri | Api.getMqttConnectInfo(hostId) 返回 |
MQTT Broker 地址(wss://...) |
| clientId | 自己生成(项目用 GID_newbest@@@XZJ_${timestamp}) |
客户端唯一标识 |
| username/password | Api.getMqttConnectInfo 返回 |
MQTT 鉴权 |
| TOPIC | 房屋对象里(home.TOPIC) |
MQTT 主题前缀 |
TOPIC 是登录返回的房屋列表里就有的,不需要连接 MQTT 后获取。
八、其他项目想做这个功能怎么落地
8.1 技术选型建议
| 场景 | 推荐方案 |
|---|---|
| 几百-几千设备 | MQTT(EMQX / Mosquitto) + Redis 持久化 |
| 几万设备 | MQTT + Kafka 削峰 + 分级 Topic |
| 设备数 < 100 | 甚至 WebSocket 都行 |
| 需要音视频通话 | MQTT + WebRTC(不要混 MQTT 音视频) |
8.2 协议设计
Topic 命名规范(推荐分层):
{产品线}/{家庭ID}/{设备类型}/{设备ID}/{动作}
例:smarthome/home_001/light/light_001/cmd
smarthome/home_001/light/light_001/status
Message 格式(推荐 JSON):
{
"deviceId": "light_001",
"POWER": "ON",
"Brightness": 80,
"timestamp": 1692345678901,
"execMobile": "13800138000"
}
8.3 实施步骤
第 1 步:搭 MQTT Broker
- 本地测试:docker run -d -p 1883:1883 eclipse-mosquitto
- 生产环境:EMQX Cloud / 阿里云 MQTT
第 2 步:定义 Topic 规范 + Message 格式
- 文档化所有设备类型的字段
- 定义 Topic 模板
第 3 步:实现设备端 SDK
- ESP32 / STM32 等用 C SDK(MQTT 库)
- 订阅自己 Topic 的 cmd 主题
- 控制硬件 + 上报 status
第 4 步:实现 App 端 SDK
- RN:react-native-mqtt-paho 或自研
- 订阅设备 status 主题
- publish 控制指令
第 5 步:实现控制逻辑
- 乐观更新(先变 UI)
- 异步下发指令
- 订阅推送保证最终一致
第 6 步:HOC 抽象(如有多种设备)
- 抽出 withXxx 高阶组件
- 新增设备类型零冗余
第 7 步:监控 + 告警
- Broker 连接数 / 消息 QPS / 错误率
- 设备离线检测
8.4 注意事项
| 注意点 | 说明 |
|---|---|
| TOPIC 必须后端预分配 | App 端不能自己生成,否则串户 |
| 消息体不要太大 | MQTT 限制 256KB,设备列表走 HTTP |
| QoS 级别 | 控制指令用 QoS 1(至少一次),状态推送用 QoS 0(最多一次) |
| ClientId 必须唯一 | 否则两个 App 连会被踢 |
| 心跳 + 重连 | MQTT Broker 默认 60s 心跳,断线要自动重连 |
| 离线消息保留 | 设置 cleanSession: false,离线消息保留 |
| 安全 | TOPIC 不要用敏感信息,用 UUID 即可 |
| 频控 | 高频控制指令要节流,防止设备执行不过来 |
九、本项目特殊点(避免踩坑)
| 坑 | 解决方案 |
|---|---|
| 8 设备代码重复 | 用 withDevice HOC 抽象 |
| UI 与状态不同步 | 乐观更新 + 订阅最终一致 |
| 调光灯 10/16 进制 | dispatch 用 10 进制,NB_SDK 用 16 进制 |
| 多个家庭切换 | 每个家庭独立 TOPIC + 独立 MQTT 连接 |
| 设备状态推送频繁 | 500ms 节流合并更新(项目 home.js 里实现) |
| 多端同时控制同一设备 | Redux 是单一数据源,最后一次 dispatch 生效 |
十、面试问答速记
Q1:智能家居为什么用 MQTT 不用 HTTP?
- MQTT Pub/Sub 天然支持一对多(一个设备状态变化推送所有订阅者)
- 内置 QoS 保证消息不丢
- 头部小(2 字节),IoT 设备带宽友好
- HTTP 是请求-响应,实时推送需要客户端轮询,效率低
Q2:8 种设备怎么共享一套控制逻辑?
用 withDevice HOC。HOC 内部 connect Redux 拿公共数据,提供 onControl 函数。8 种设备组件只需写 UI,业务逻辑统一。
Q3:乐观更新怎么实现的?
// HOC.onControl
dispatch({
type: UPDATE_STATE, payload: data}); // ① 立即更新 Redux
NB_SDK.exec(topic, data); // ② 异步下发指令
// ③ 设备响应 → MQTT 推送 → 回调 → dispatch UPDATE_STATE(最终一致)
Q4:MQTT Topic 怎么设计?
- 按业务分层:
{产品线}/{家庭ID}/{设备类型}/{设备ID}/{动作} - App 端不生成 Topic,必须后端预分配
- 用 UUID 避免敏感信息泄露
Q5:项目里 MQTT 和 HTTP 是怎么分工的?
- HTTP:拿设备列表、用户权限、房屋信息(静态/低频数据)
- MQTT:拿设备状态、订阅状态变化、控制设备(实时/高频数据)
十一、一句话总结
智能家居 = MQTT(实时控制 + 推送) + HTTP(静态数据) + Redux(状态总线) + HOC(逻辑复用) + 乐观更新(0ms 响应)。
十二、系统分层架构
整个项目的部署架构 4 层组成(和云对讲共用):

系列文章:① 云对讲模块 ② 智能家居模块(本文)