不使用useEffect解决useState异步导致的蓝牙扫描列表重复问题
问题根源
你遇到的问题本质是两个原因叠加:
- React的状态更新是异步批量处理的,直接调用
setDeviceList([])后状态不会立刻生效 - 若存在上一轮未结束的扫描,旧的扫描回调会持续触发,闭包捕获的旧状态会污染新的列表
以下两种方案都不需要使用useEffect,从根源解决问题:
方案1:扫描批次标记法(无需额外ref,不侵入原有逻辑)
通过给状态增加扫描批次ID,让回调仅处理当前批次的更新,完全依赖React状态的函数式更新特性实现:
- 调整state结构,增加扫描批次标识:
const [deviceState, setDeviceState] = useState({ scanId: 0, list: [] }); // 原有获取设备列表的逻辑直接取 deviceState.list 即可,无需修改其他业务代码
- 改造扫描逻辑:
const scanForDevices = () => { // 先停止上一轮未结束的扫描,避免无效回调占用资源 manager.stopDeviceScan(); // 生成当前扫描的唯一ID,会被本次扫描的所有回调闭包捕获 const currentScanId = Date.now(); // 重置状态为当前批次+空列表 setDeviceState({ scanId: currentScanId, list: [] }); manager.startDeviceScan(null, null, (error, device) => { if (error) return; setDeviceState(old => { // 非当前批次的更新直接丢弃,彻底避免旧扫描回调污染新列表 if (old.scanId !== currentScanId) return old; return { ...old, list: [...old.list, device] }; }); }); }
方案2:同步镜像ref法(无需修改原有state结构)
通过useRef维护和state完全同步的本地列表镜像,ref的更新是同步的,不受React状态异步更新的影响:
const [deviceList, setDeviceList] = useState([]); // 维护设备列表的同步镜像,更新实时生效 const deviceListRef = useRef(deviceList); const scanForDevices = () => { manager.stopDeviceScan(); // 同步清空镜像,无需等待React state更新完成 deviceListRef.current = []; setDeviceList([]); manager.startDeviceScan(null, null, (error, device) => { if (error) return; // 所有更新先操作同步镜像,保证值的准确性 const newList = [...deviceListRef.current, device]; deviceListRef.current = newList; // 再同步到state触发视图渲染 setDeviceList(newList); }); }
两种方案都彻底避免了异步更新导致的旧值问题,不需要额外做列表去重处理。
内容的提问来源于stack exchange,提问作者T3db0t
相关产品推荐
相关产品推荐

