React中如何等待useEffect完成数据读取后再更新UI?
解决React中等待所有数据读取处理完成后再更新UI的问题
问题场景
你的应用用于展示车辆事件及关联记录,目前三个异步请求(事件、车辆、记录数据)能成功读取,但UI会在数据关联处理完成前就渲染,导致DetailIncidentView中事件记录的长度显示为0。核心原因是:
- 三个
fetch请求异步执行,无法保证同时完成 - 直接修改
incident对象属性(如setIncidentNarratives)不会触发React状态更新,UI感知不到数据变化 - 没有统一加载状态控制UI渲染时机
解决方案
1. 修改Context中的fetch函数,使其返回Promise
让三个fetch函数返回Promise,方便用Promise.all等待所有请求完成后再处理数据关联:
// VisibleContextProvider.js const fetchIncidents = (incidentsLocation) => { return fetch(incidentsLocation, { method: 'GET', headers: { Accept: 'application/json', 'Content-Type': 'application/json', } }) .then( (response) => response.json() ) .then( (data) => { let incidentlist = []; for(var i=0; i<data.features.length; i++) { let incidentObj = new Incident(data.features[i]); incidentlist.push(incidentObj); } setIncidentData(incidentlist); return incidentlist; }) .catch( (error) => { console.log(error); throw error; }); }; const fetchVehicles = (vehicleLocation, serviceName) => { return fetch(vehicleLocation, { method: 'GET', headers: { Accept: 'application/json', 'Content-Type': 'application/json', } }) .then( (response) => response.json() ) .then( (data) => { let vehiclelist = []; for(var i=0; i<data.features.length; i++) { let vehicle = new Vehicle(data.features[i]); if(vehicle.service === serviceName) { vehiclelist.push(vehicle); } } setVehicleData(vehiclelist); return vehiclelist; }) .catch( (error) => { console.log(error); throw error; }); } const fetchNarratives = (narrativeLocation) => { return fetch(narrativeLocation, { method: 'GET', headers: { Accept: 'application/json', 'Content-Type': 'application/json', } }) .then( (response) => response.json() ) .then( (data) => { let narrativelist = []; for(var i=0; i<data.features.length; i++) { let narrative = new Narrative(data.features[i]); narrativelist.push(narrative); } setNarrativeData(narrativelist); return narrativelist; }) .catch( (error) => { console.log(error); throw error; }); }
2. 在Context中添加加载状态
用于标记所有数据是否加载并处理完成:
// VisibleContextProvider.js import { createContext, useContext, useState } from "react"; const VisibleContext = createContext({ // ... 原有属性 isLoading: true, }); export const useVisibilityStatus = () => useContext(VisibleContext); const VisibleContextProvider = ({ children }) => { // ... 原有状态 const [isLoading, setIsLoading] = useState(true); // ... 原有方法 return ( <VisibleContext.Provider value={{ // ... 原有属性 isLoading, setIsLoading }}> {children} </VisibleContext.Provider> ); }; export default VisibleContextProvider;
3. 修改ServiceContentView中的数据加载与关联逻辑
使用Promise.all等待所有请求完成,并且通过不可变更新方式处理关联数据(React依赖状态引用变化触发渲染):
// ServiceContentView.js import { useEffect } from "react"; import {useVisibilityStatus} from '../customhooks/VisibleContextProvider'; // ... 其他导入 function ServiceContentView(props) { const { isIncidentVisible, isFilterVisible, incidentData, fetchIncidents, vehicleData, fetchVehicles, narrativeData, fetchNarratives, isLoading, setIsLoading, setIncidentData // 从Context中获取更新事件数据的方法 } = useVisibilityStatus(); // ... getServiceFileLocation方法保持不变 var serviceSpecificLocation = getServiceFileLocation(props.viewid); useEffect(() => { const loadAndProcessData = async () => { try { // 等待三个请求全部完成 const [incidents, vehicles, narratives] = await Promise.all([ fetchIncidents(serviceSpecificLocation.incidents), fetchVehicles(serviceSpecificLocation.vehicles, serviceSpecificLocation.name), fetchNarratives(serviceSpecificLocation.narratives) ]); // 关联数据,创建新的incident对象(不可变更新) const processedIncidents = incidents.map(incident => { const incidentVehicles = vehicles.filter(v => v.vehicleIncidentNumber === incident.incidentNumber); const incidentNarratives = narratives.filter(n => n.incidentNumber === incident.incidentNumber); // 若Incident是类实例,可使用构造函数拷贝或添加toJSON方法 const newIncident = Object.assign(Object.create(Object.getPrototypeOf(incident)), incident); newIncident.incidentVehicles = incidentVehicles; newIncident.incidentNarratives = incidentNarratives; return newIncident; }); // 更新处理后的事件数据 setIncidentData(processedIncidents); } catch (error) { console.error('数据加载失败:', error); } finally { // 无论成功失败,都标记加载完成 setIsLoading(false); } }; loadAndProcessData(); }, []); // ... 原有渲染逻辑修改为根据加载状态显示 return( <div> <div> <div> <SideNavView /> </div> </div> {isLoading ? ( <div>加载中...</div> ) : ( <> {isIncidentVisible && <IncidentListView viewid={props.viewid} />} {isFilterVisible && <FilterListView /> } <SimpleMap /> <DetailIncidentView /> </> )} </div> ); } export default ServiceContentView;
4. 在DetailIncidentView中适配加载状态
// DetailIncidentView.js import { useVisibilityStatus } from "../customhooks/VisibleContextProvider"; function DetailIncidentView() { const {incidentData, isLoading} = useVisibilityStatus(); if(isLoading) return null; return ( incidentData.map( (incident) => ( <div className="modal fade" id={"inc-" + incident.incidentNumber} tabIndex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true" key={incident.incidentNumber}> {/* ... 原有modal代码 */} <div className="tab-pane fade" id={"narrative-tab-pane-" + incident.incidentNumber} role="tabpanel" aria-labelledby="narrative-tab" tabIndex="0"> {incident.incidentNarratives.length} </div> {/* ... 原有modal代码 */} </div> )) ); } export default DetailIncidentView;
关键说明
- 不可变更新:直接修改原
incident对象属性不会触发React重新渲染,必须创建新对象或数组更新状态 - Promise.all:确保三个异步请求全部完成后再处理数据关联,避免部分数据未加载就开始处理
- 加载状态:通过
isLoading统一控制UI渲染时机,防止未处理完成的数据被渲染
内容的提问来源于stack exchange,提问作者user1268139
相关产品推荐
相关产品推荐

