React中如何等待setState完成后再执行数组map操作避免异步渲染报错
问题解决思路
问题根源
你当前的渲染异常本质是setState执行时机早于异步数据获取回调:Firebase的on('value')监听是异步操作,你在循环中发起serial数据请求后,没有等待异步回调执行就直接调用setState更新serials,此时数组内的last_temps都是空数组,自然无法读取到对应属性。
另外render函数是纯函数,不能在里面执行异步等待操作,所有异步数据获取逻辑都应该放在生命周期中处理。
修复步骤
- 给state新增
isLoading加载状态,初始化值为true,用来标记数据是否加载完成 - 把单次数据查询的
on('value')改为返回Promise的once('value'),用Promise.all包装所有异步请求,等待所有数据返回后再统一执行setState - render函数中先判断加载状态,加载中渲染占位提示,加载完成后再执行列表渲染
修改后的代码示例
state初始化
constructor(props) { super(props); this.state = { user: {}, // 你的user数据原有初始化逻辑 serials: [], isLoading: true } }
componentDidMount异步逻辑
async componentDidMount() { const uid = this.state.user.uid; const gatewayRef = db.ref(uid + '/gateways'); // 先获取gateway列表 const gatewaysSnapshot = await gatewayRef.once('value'); const gatewayList = []; gatewaysSnapshot.forEach(item => gatewayList.push(item.val())); // 批量发起所有serial查询请求,等待全部返回 const serialsPromise = gatewayList.map(async (gatewayItem) => { const serialRef = db.ref(`gateways/${gatewayItem.gateway}`); const serialSnapshot = await serialRef.once('value'); const ser = []; serialSnapshot.forEach(serial => ser.push(serial.val().serial)); const lastTemps = [{a: gatewayItem.gateway, b: ser}]; return {s: lastTemps}; }); const serialsArr = await Promise.all(serialsPromise); // 所有数据加载完成后更新state this.setState({ serials: serialsArr, isLoading: false }); }
render函数
render() { const { isLoading, serials } = this.state; // 加载中渲染占位 if (isLoading) { return <div>数据加载中,请稍候</div> } return ( <> {serials.map((number, idx) => { // 可选链简化非空判断 return number?.s?.length ? ( <h2 key={idx}>{number.s[0].a}</h2> ) : null })} </> ) }
注意事项
- 如果业务需要持续监听Firebase数据变动,不能用
once的话,需要在每个on回调中统计已返回的请求数量,确认所有请求都返回后再执行setState,避免频繁重复渲染 - 列表渲染的
key属性尽量使用业务唯一id,不要直接用数组索引,可优化渲染性能 - 如需兼容低版本浏览器不支持可选链操作符,可以把
number?.s?.length改回number.s && number.s.length的写法
内容的提问来源于stack exchange,提问作者Nina
相关产品推荐
相关产品推荐

