我正在构建一个React Native(Expo(应用程序,用于扫描蓝牙设备。当检测到设备时,蓝牙API公开了一个回调,我用它将非重复设备放入一个数组:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if(!deviceList.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
...etc...
};
setDeviceList(old => [...old, newDevice]);
}
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
问题是回调的调用速度比setDeviceList
更新快很多倍,所以重复检查不起作用(如果我记录deviceList
,它只是空的(。
如果我使用一个额外的、独立的常规(non-useState(数组,重复检查可以工作,但状态不会持续更新:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
var deviceList2 = [];
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if(!deviceList2.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
...etc...
};
deviceList2.push(newDevice);
setDeviceList(old => [...old, newDevice]);
}
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
这段代码几乎可以工作,但deviceList
状态没有正确更新:它显示了第一对设备,但除非其他组件导致重新渲染,否则不会再次更新。
我需要做些什么才能使它按预期工作?
我建议将重复检查封装在状态集函数本身中,如果没有找到新设备,则返回相同的设备列表。这将竞争条件处理卸载到底层的react实现本身,我发现这在大多数情况下都足够好。
因此,它看起来像这样:
const DeviceListView = () => {
const [deviceList, setDeviceList] = useState([]);
const startScanning = () => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
setDeviceList(old => {
if(!old.some(d => d.device.id == device.id)){
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
// ...etc...
};
return [...old, newDevice]
}
return old
});
});
}
// map deviceList to components
componentList = deviceList.map(...);
return <View> {componentList} </View>
}
由于old
没有变化,如果没有发现新的唯一设备,它也将根据文档跳过下一次重新渲染(这是一个巧妙的优化:(
这是根据文档实现依赖于先前状态的状态更新的首选方式https://reactjs.org/docs/hooks-reference.html#functional-更新
将您的回调转换为promise,以便在您获得完整的设备列表之前,检查以下代码(PS.未测试,请根据需要更改(
const [deviceList, setDeviceList] = useState([]);
const [scanning, setScanning] = useState(false);
useEffect(() => {
if(scanning) {
setDeviceList([]);
startScanning();
}
}, [scanning]);
const subscription = manager.onStateChange(state => {
if (state === "PoweredOn" && scanning === false) {
setCanScan(true);
subscription.remove();
}
}, true);
const fetchScannedDevices = () => {
return new Promise((resolve, reject) => {
manager.startDeviceScan(null, null, (error, device) => {
// Add to device list if not already in list
if (!deviceList.some(d => d.device.id == device.id)) {
console.log(`Adding ${device.id} to list`);
const newDevice = {
device: device,
// ...etc...
};
resolve(newDevice);
}
if (error) {
reject({});
}
});
});
};
const startScanning = async () => {
try {
const newDevice = await fetchScannedDevices();
setDeviceList(old => [...old, newDevice]);
} catch (e) {
//
}
};
const handleScan = () => {
setScanning(true);
};
// map deviceList to components
componentList = deviceList.map(() => {});
return (
<View>
<Button
onPress={() => handleScan()}>
Scan
</Button>
<View>{componentList}</View>
</View>
);
};