数据处理时Loader组件无法正常显示的问题求助
问题原因
你遇到的核心问题是同步长任务阻塞了主线程:虽然把数据处理逻辑放进了useEffect,但里面嵌套的while循环是同步执行的,会一直霸占JS主线程,导致浏览器根本没时间渲染Loader组件——直到整个循环跑完,React才会更新DOM,这时候Loader刚显示就被关闭,看起来就像Loader没工作。
修复方案
把同步循环改成异步分步执行,给浏览器留出渲染时间。关键是用async/await配合setTimeout(或requestIdleCallback)拆分长任务,同时用useRef保存需要跨异步步骤修改的变量,避免闭包和重渲染导致的变量重置问题。
修改后的代码
import { useState, useEffect, useRef } from 'react'; // 导入你的其他组件(Modal、Box、CircularProgress等) const Simulate = (props) => { const [isLoading, setIsLoading] = useState(true); const steps = props.nodes[0].data.fields.arrivals; const duracionPrueba = props.nodes[0].data.fields.duracionPrueba; const stepsTime = arrivalSteps(steps, duracionPrueba); // 用useRef保存异步操作中需要修改的变量,避免闭包和重渲染问题 const visitasRef = useRef([]); const llegadasRef = useRef({}); const finalizadasRef = useRef({}); const tiempoRespuestaRef = useRef({}); const erroresRef = useRef({}); const filtradosRef = useRef({}); const concurrenciaRef = useRef({}); const colaRef = useRef({}); const segundoRef = useRef(1); const [nodesPath, tree] = NodesPath(props.edges); const leaves = useRef([]); findLeaves(tree, leaves.current); weightNodes(tree, props.nodes, props.edges); useEffect(() => { // 异步执行模拟逻辑 const runSimulation = async () => { // 处理steps循环:替换forEach为for循环,支持异步等待 for (let i = 0; i < steps.length; i++) { const step = steps[i]; let timeStep = 1; while (timeStep <= stepsTime[i]) { console.log(segundoRef.current); // 调用原逻辑,改用ref的current属性访问变量 runPath(nodesPath, props.nodes, step, llegadasRef.current, visitasRef.current, finalizadasRef.current, tree); checkFinish(visitasRef.current, leaves.current, erroresRef.current, filtradosRef.current, concurrenciaRef.current, colaRef.current, tiempoRespuestaRef.current, props.nodes); segundoRef.current++; timeStep++; // 让出主线程,让浏览器渲染Loader await new Promise(resolve => setTimeout(resolve, 0)); } } // 处理剩余的visitas while (visitasRef.current.length > 0) { console.log(segundoRef.current); runPath(nodesPath, props.nodes, 0, llegadasRef.current, visitasRef.current, finalizadasRef.current, tree); checkFinish(visitasRef.current, leaves.current, erroresRef.current, filtradosRef.current, concurrenciaRef.current, colaRef.current, tiempoRespuestaRef.current, props.nodes); segundoRef.current++; // 再次让出主线程 await new Promise(resolve => setTimeout(resolve, 0)); } // 整理结果并回调 const simulateResults = { duracionPrueba, tiempoTotal: segundoRef.current, tiempoRespuesta: tiempoRespuestaRef.current, concurrencia: concurrenciaRef.current, cola: colaRef.current, errores: erroresRef.current, filtrados: filtradosRef.current, llegadas: llegadasRef.current, finalizadas: finalizadasRef.current, }; props.handleSimulate(simulateResults); setIsLoading(false); }; runSimulation(); }, [props.nodes, props.edges, steps, duracionPrueba, stepsTime, nodesPath, tree]); if (isLoading) { return( <Modal open={isLoading} aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description" > <Box sx={{style}} className='loader'> <CircularProgress style={{transform: 'translate(-50%, -50%)'}} /> </Box> </Modal> ) } // 这里返回组件的其他内容,原代码未提供,暂用null占位 return null; }
关键修改说明
- 用useRef保存变量:原代码中的
visitas、llegadas等变量会在组件重渲染时重新初始化,用useRef可以保留它们的引用,避免异步操作中拿到旧值。 - 异步拆分长任务:每次循环后用
await setTimeout(...)让出主线程,让浏览器有时间渲染Loader组件,解决阻塞问题。 - 替换forEach为for循环:forEach不支持异步等待,改用for循环可以确保每一步循环完成后再执行下一步。
可选优化
如果想更高效地利用浏览器空闲时间,可以把await new Promise(resolve => setTimeout(resolve, 0))替换为:
await new Promise(resolve => requestIdleCallback(resolve));
requestIdleCallback会在浏览器空闲时执行回调,不会影响页面响应,但要注意它的兼容性(IE不支持)。
内容的提问来源于stack exchange,提问作者Nicolas Jorquera Martinez
相关产品推荐
相关产品推荐

