miot-sdk RN插件,数据流与状态管理

本文以 miot-sdk RN插件 项目为例,完整展示从设备协议初始化、首次数据拉取,到 MobX 状态驱动 UI,以及与米家宿主(Native)交互的全部核心代码


一、协议层初始化与首次数据拉取(含完整代码)

1. 建立 MobX 设备状态树

// productionState.js(关键片段)
import { createDeviceStore } from './deviceStore';
import { PROP_LIST } from './deviceProtocol/protocol';

function initDefaultValue() {
  const defaultValues = {};
  Object.keys(PROP_LIST).forEach(key => {
    defaultValues[key] = PROP_LIST[key][2]; // 默认值取数组第三项
  });
  return defaultValues;
}

const state = {
  deviceState: createDeviceStore(initDefaultValue()),
  componentState: createComponentStore(),
};

function createDeviceStore(defaultValues) {
  const store = makeAutoObservable({
    ...defaultValues,
    // 其他 observable 属性、action 等
  });
  // 关键:挂载协议方法
  addMQTTMethod(store);
  return store;
}

2. addMQTTMethod:挂载协议单例和发送方法

// productionState.js
import DeviceProtocol from './deviceProtocol/deviceProtocol';

export function addMQTTMethod(store) {
  // 创建协议单例(传入 store 引用)
  const protocol = new DeviceProtocol(store);
  
  // 将发送方法挂载到 store 上,供 UI 调用
  store.sendSetProperty = (params) => {
    protocol.deviceProtocolEmitter.emit('protocolSetProperty', params);
  };
  store.sendAction = (params) => {
    protocol.deviceProtocolEmitter.emit('protocolAction', params);
  };
}

3. DeviceProtocol 构造函数(首次初始化)

// deviceProtocol.js
class DeviceProtocol {
  constructor(store) {
    if (DeviceProtocol.instance) {
      return DeviceProtocol.instance;
    }
    this.deviceState = store;
    this.initDeviceProtocol();
    DeviceProtocol.instance = this;
  }

  initDeviceProtocol() {
    this.initProtocolMap();          // 建立 siid.piid → 业务字段映射
    this.createEventEmitter();       // 创建 mitt 事件总线
    this.subscribeMessage();         // 订阅设备上报
    
    // 设备在线则立即拉取首屏数据(leading: true)
    if (Device.isOnline) {
      this.debounceInitProtocolState(); // 内部使用 lodash.debounce,leading: true
    }
  }

  initProtocolMap() {
    this.protocolMap = new Map();
    Object.entries(PROP_LIST).forEach(([key, value]) => {
      const [siid, piid] = value;
      this.protocolMap.set(`prop.${siid}.${piid}`, key);
    });
  }

  createEventEmitter() {
    this.deviceProtocolEmitter = mitt();   // 创建内部事件总线
    // 监听 setState 事件,直接写入 MobX
    this.deviceProtocolEmitter.on('setState', (newState) => {
      this.deviceState.setState(newState);
    });
    // 监听协议动作,调用云端方法
    this.deviceProtocolEmitter.on('protocolSetProperty', (params) => {
      this.callSetProperty(params);
    });
    // ... 类似 protocolAction
  }
}

🔔 关于 emit 和事件总线

上述代码中的 deviceProtocolEmitter = mitt() 创建了一个极简的发布/订阅事件总线。

  • emit('setState', toRefreshData) 表示:在该总线上触发名为 'setState' 的事件,并携带 toRefreshData 作为参数。
  • 谁在监听?createEventEmitter() 中注册的 on('setState', (state) => { this.deviceState.setState(state) }) 会收到这条消息,并执行 MobX 的 setState 更新 store。

为什么要用 emit 而不直接调用 setState
因为协议层有多个不同来源(订阅回调、get_properties 成功回调、sendAction 失败后的刷新等)需要更新设备状态。统一使用 emit('setState', data) 发事件,再由唯一监听者 onSetState 负责写入 MobX,可以解耦数据来源与存储逻辑,避免到处持有 store 引用。同时,同一个 emitter 也用于发送 protocolActionprotocolSetProperty 等控制指令,保持内部通信一致。

4. subscribeMessage:订阅设备推送(完整代码)

// deviceProtocol.js
subscribeMessage() {
  // 监听原生层推送的消息
  const { remove } = DeviceEvent.deviceReceivedMessages.addListener(
    (device, messages = new Map()) => {
      const toRefreshData = {};
      messages.forEach((value, key) => {
        // key 格式如 "prop.2.1",通过 protocolMap 转为业务字段名
        if (this.protocolMap.has(key)) {
          toRefreshData[this.protocolMap.get(key)] = value;
        }
      });
      if (Object.keys(toRefreshData).length > 0) {
        this.deviceProtocolEmitter.emit('setState', toRefreshData);
      }
    },
  );
  this.removeSubscription = remove;

  // 向原生层注册订阅(告诉云端需要推送哪些属性和事件)
  const subscribeData = this.createSubscribeData(); // 从 PROP_LIST/EVENT_LIST 生成
  Device.getDeviceWifi().subscribeMessages(subscribeData)
    .catch(err => console.error('订阅失败', err));
}

createSubscribeData() {
  const props = Object.values(PROP_LIST).map(([siid, piid]) => `prop.${siid}.${piid}`);
  const events = Object.values(EVENT_LIST).map(([siid, eiid]) => `event.${siid}.${eiid}`);
  return [...props, ...events];
}

5. 首次拉取设备属性(initProtocolStateFromCloud

// deviceProtocol.js
async initProtocolStateFromCloud() {
  // 筛选需要主动拉取的属性(规则:数组长度≤3 或 第4项为 true)
  const toFetch = Object.entries(PROP_LIST)
    .filter(([_, def]) => def.length <= 3 || def[3] === true)
    .map(([key, [siid, piid]]) => ({ siid, piid, key }));

  // 分批,每批最多30个
  const batchSize = 30;
  const batches = [];
  for (let i = 0; i < toFetch.length; i += batchSize) {
    batches.push(toFetch.slice(i, i + batchSize));
  }

  const results = await Promise.all(batches.map(batch => {
    const params = batch.map(({ siid, piid }) => ({ siid, piid }));
    return Device.getDeviceWifi().callMethodFromCloud('get_properties', params);
  }));

  const newState = {};
  results.forEach(res => {
    res.result.forEach(item => {
      const key = toFetch.find(({ siid, piid }) => 
        siid === item.siid && piid === item.piid
      )?.key;
      if (key) newState[key] = item.value;
    });
  });

  newState.loadingVisible = false;
  this.deviceProtocolEmitter.emit('setState', newState);
}

首屏数据闭环示意

createDeviceStore → addMQTTMethod → new DeviceProtocol → 
subscribeMessage (监听+订阅) → debounceInitProtocolState (leading) → 
initProtocolStateFromCloud → get_properties → emit('setState') → 
deviceState.setState → UI 通过 observer 自动刷新

二、与米家宿主的交互(Native 上下文代码示例)

1. 插件入口

// index.js
import { Package, DarkMode } from 'miot';
import Root from './root';

DarkMode.preparePluginOwnDarkMode();
Package.entry(Root, () => console.log('root loaded'));

2. 获取设备信息与调用宿主功能

// main/page/DeviceInfo/index.js
import { Device, Host } from "miot";

// 设备名称展示及修改
{
  title: '设备名称',
  showArrow: Device.isOwner,
  rightExtend: miotSdkProp.Device.name,   // 动态名称
  onPress: () => {
    if (Device.isOwner) {
      Host.ui.openChangeDeviceName();      // 调用宿主改名界面
    }
  }
},
{
  title: '设备类型',
  rightExtend: Device.device.displayName   // 静态展示名
},
{
  title: '设备编号',
  rightExtend: Device.deviceID
}

3. 退出插件

// main/page/index.js
<HeaderBackButton onPress={Package.exit} />

三、MobX 状态管理与界面使用(含完整 Hook 和组件示例)

1. 定义全局 Store 与 Context

// productionState.js
import React, { createContext, useContext } from 'react';

const state = {
  deviceState: createDeviceStore(initDefaultValue()),
  componentState: createComponentStore(),
};

const StateContext = createContext(state);

export const StateContextProvider = ({ children }) => (
  <StateContext.Provider value={state}>{children}</StateContext.Provider>
);

const useStores = () => {
  const stores = useContext(StateContext);
  if (!stores) throw new Error('useStores must be used within a StoreProvider');
  return stores;
};

export const useDeviceState = () => useStores().deviceState;
export const useComponentState = () => useStores().componentState;

2. 在根组件注入 Provider

// root.js
import { StateContextProvider } from './main/useHooks/productionState';

export default function Root() {
  return (
    <SafeAreaProvider>
      <StateContextProvider>
        <MenuProvider>
          <App />
        </MenuProvider>
      </StateContextProvider>
    </SafeAreaProvider>
  );
}

3. 界面读取状态(完整组件示例)

示例 A:根据设备状态切换页面

// main/page/Home/index.js
import { observer } from 'mobx-react-lite';
import { useDeviceState, useComponentState } from '../../useHooks/productionState';

const PageSwitcher = observer(({ navigation }) => {
  const deviceState = useDeviceState();
  const componentState = useComponentState();

  if ([3, 4].includes(deviceState.deviceStatus) || 
      (deviceState.notification && deviceState.deviceStatus === 1)) {
    return <WorkingStatus />;
  }
  if ([0, 1, 2, 5, 6, 400].includes(deviceState.deviceStatus)) {
    return <DeviceController navigation={navigation} />;
  }
  return null;
});

示例 B:动态控制 Tab 禁用状态

// main/page/index.js
const App = observer((props) => {
  const deviceState = useDeviceState();
  const tabList = [
    { name: '设备' },
    { name: '菜谱', disabled: [400, 5, 6].includes(deviceState.deviceStatus) }
  ];
  // ... 渲染 Tabs
});

四、下发指令(写属性 / 调动作)完整代码

// 任意 UI 组件中
const SomeControl = observer(() => {
  const deviceState = useDeviceState();

  const turnOnLight = () => {
    deviceState.sendSetProperty({
      name: 'light',           // 必须与 PROP_LIST 中的 key 一致
      value: 1,
      resolveCallback: () => console.log('成功'),
      rejectCallback: (err) => console.error(err),
    });
  };

  const startWork = () => {
    deviceState.sendAction({
      name: 'startWork',       // 必须与 EVENT_LIST 中的 key 一致
      value: { mode: 2 },
      resolveCallback: () => {},
      rejectCallback: () => {},
    });
  };

  return <Button onPress={turnOnLight} title="开灯" />;
});

内部调用链

sendSetProperty → deviceProtocolEmitter.emit('protocolSetProperty') → 
callSetProperty → Device.getDeviceWifi().callMethodFromCloud('set_properties', params)

五、协议字段定义(PROP_LIST 示例)

// main/useHooks/deviceProtocol/protocol.js
export const PROP_LIST = {
  'deviceStatus':    [2, 1, 400],      // [siid, piid, 默认值]
  'errorCode':       [2, 2, 0],
  'workMode':        [2, 3, 0],
  'temperature1':    [2, 5, 0],
  'power':           [2, 7, 0],
  'step':            [2, 8, 0],
  'toWorkConfig':    [2, 9, ''],       // 字符串默认值
  // 若需要首次主动拉取,可加第4个参数 true,如:
  'someImportant':   [3, 1, 0, true],
};

六、完整数据流总结图(文字版)

方向 触发点 核心代码 终点
首次拉取 debounceInitProtocolState initProtocolStateFromCloudget_propertiesemit('setState') MobX deviceState
设备上报 DeviceEvent.deviceReceivedMessages subscribeMessage 监听 → protocolMap 转换 → emit('setState') MobX deviceState
UI 下发 sendSetProperty / sendAction callMethodFromCloud('set_properties'/'action') 云端
UI 响应 observer 组件使用 useDeviceState() MobX 自动通知重渲染 界面刷新

关键文件索引

  • productionState.js – MobX store、Context、addMQTTMethod
  • deviceProtocol.js – 协议单例、订阅、get_propertiescallMethodFromCloud,以及内部 mitt 事件总线
  • protocol.jsPROP_LIST / EVENT_LIST 与 SIID/PIID 映射

七、useHooks 目录角色与依赖关系

为了更清晰地理解各模块职责,下面列出 main/useHooks 下主要文件的作用及其依赖关系。

1. 目录角色一览

文件 / 目录 作用(一句话)
deviceProtocol/protocol.js 纯配置:属性表 PROP_LIST、事件表 EVENT_LIST 及相关名称常量,不依赖本目录其它业务文件。
deviceProtocol/deviceProtocol.js 协议实现:DeviceProtocol 类,负责订阅/拉取/下发,内部用 protocol.js 做 siid/piid 映射,用 mitt 发事件。
productionState.js 全局设备状态(MobX):创建 store、挂 sendSetProperty / sendAction / setState,内部 new DeviceProtocol(store),页面用 useDeviceState()
mockState.js 本地 Mock 用的 MobX store,与真实协议解耦(主要配合调试/演示)。
useMiotSdk/index.js 封装 MIOT Device / 设备改名监听等小 hook,不直接依赖 deviceProtocol
useDeviceData.js 占位/未完成的小 hook,与其它模块几乎无耦合。

2. 依赖关系图(谁引用谁)

hooks文件依赖关系

依赖要点

  • protocol.js 在最底层:只导出常量,被 deviceProtocol.jsproductionState.js 引用。
  • deviceProtocol.js 在中间:只依赖 protocol.js(以及 miotmitt、RN 等外部包),实现「读属性 / 写属性 / 动作」的具体逻辑。
  • productionState.js 在最上层(业务入口之一):把 MobX store 交给 DeviceProtocol,并在 store 上挂 sendSetProperty / sendAction(内部是 deviceProtocolEmitter.emit(...))。

数据流可以理解为:

UI → sendSetProperty(store 上的方法)→ mittDeviceProtocol 里监听 → 查 PROP_LISTset_properties 等。

3. 以另外一种工程(多一个层次)

在另外一个工程中,使用的是 deviceProtocol/useProtocol.js,其设计略有不同:不经过 productionState,而是用 useProtocol(routeName) 在页面级维护 protocolState,同样内部 new DeviceProtocol + 同样的 emit('protocolSetProperty') 链路。

依赖关系仍然为:

useProtocol.jsprotocol.js + deviceProtocol.js(+ 页面侧 setIsloadingRef 等)。

4. 一句话记忆

  • protocol.js:字典(属性/动作编号表)。
  • deviceProtocol.js:按字典跟设备/云端打交道 + 事件总线。
  • productionState.js(微波)或 useProtocol.js(其他机器):把「状态 + 发送方法」接到 React/MobX 上给界面用;mockState / useMiotSdk / useDeviceData 则是旁路工具,不在主协议链上。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容