You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 03:08:10