如何在react-leaflet渲染30000个多边形时实现加载状态提示?
问题描述
使用react-leaflet渲染从数据库获取的大量预配置多边形热力图,切换特定热力图时需渲染30000个多边形,导致浏览器卡顿,且卡顿从渲染循环启动时就开始,无法展示加载状态(如等待光标)。
当前依赖版本
{ // dependencies "leaflet": "^1.9.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-leaflet": "^4.2.1", // devDependencies "vite": "^5.1.5" }
核心渲染代码
{ visibleHeatmap && heatmapsData.map( ( { pollutant, polygonSimData } ) => { if( visibleHeatmap !== pollutant ) return <></> return polygonSimData.map( ( { color, coordinates }, index ) => ( <Polygon key={ index } positions={ coordinates } pathOptions={ styleHeatmapPolygon( color ) }/> ) ); } ) }
说明:
visibleHeatmap是useState变量,用户点击父组件按钮时触发变更polygonSimData包含所有所需数据
已尝试切换到L.canvas渲染多边形,但问题未解决,需实现热力图渲染时的加载状态提示。
解决方案
1. 用requestIdleCallback分批渲染多边形
一次性渲染30000个React组件会占满主线程导致卡顿,通过分批渲染让浏览器在空闲时间处理渲染任务,同时在批次间隙更新加载状态。
示例代码:
import { useState, useEffect } from 'react'; import { Polygon } from 'react-leaflet'; // 每批次渲染数量,可根据浏览器性能调整 const BATCH_SIZE = 500; function HeatmapComponent({ visibleHeatmap, heatmapsData, styleHeatmapPolygon }) { const [isLoading, setIsLoading] = useState(false); const [renderedPolygons, setRenderedPolygons] = useState([]); const [targetPolygons, setTargetPolygons] = useState([]); useEffect(() => { if (!visibleHeatmap) return; // 初始化加载状态和待渲染数据 setIsLoading(true); setRenderedPolygons([]); const targetData = heatmapsData.find(item => item.pollutant === visibleHeatmap)?.polygonSimData || []; setTargetPolygons(targetData); let currentIndex = 0; function renderBatch() { const endIndex = Math.min(currentIndex + BATCH_SIZE, targetPolygons.length); const newBatch = targetPolygons.slice(currentIndex, endIndex); setRenderedPolygons(prev => [...prev, ...newBatch]); currentIndex = endIndex; // 未完成则继续分批渲染 if (currentIndex < targetPolygons.length) { requestIdleCallback(renderBatch); } else { setIsLoading(false); } } requestIdleCallback(renderBatch); // 切换热力图时终止当前渲染 return () => { setIsLoading(false); setRenderedPolygons([]); }; }, [visibleHeatmap, heatmapsData]); return ( <> {/* 全局加载提示,可自定义样式 */} {isLoading && ( <div style={{ position: 'fixed', inset: 0, backgroundColor: 'rgba(0,0,0,0.5)', display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999, cursor: 'wait' }}> <div style={{ color: 'white', fontSize: '20px' }}>加载中...</div> </div> )} {/* 渲染已处理的多边形批次 */} {renderedPolygons.map(({ color, coordinates }, index) => ( <Polygon key={index} positions={coordinates} pathOptions={styleHeatmapPolygon(color)} /> ))} </> ); }
2. 用Leaflet原生GeoJSON替代React组件
react-leaflet的Polygon组件每个实例都有React组件的额外开销,直接使用Leaflet原生L.geoJSON渲染能大幅提升性能,同时更容易控制加载状态。
示例代码:
import { useState, useEffect } from 'react'; import { useMap } from 'react-leaflet'; function HeatmapComponent({ visibleHeatmap, heatmapsData, styleHeatmapPolygon }) { const [isLoading, setIsLoading] = useState(false); const map = useMap(); const [currentLayer, setCurrentLayer] = useState(null); useEffect(() => { if (!visibleHeatmap || !map) return; // 移除上一次的热力图图层 if (currentLayer) { map.removeLayer(currentLayer); } setIsLoading(true); // 将多边形数据转换为GeoJSON格式 const targetData = heatmapsData.find(item => item.pollutant === visibleHeatmap)?.polygonSimData || []; const geoJsonData = { type: 'FeatureCollection', features: targetData.map(({ color, coordinates }) => ({ type: 'Feature', geometry: { type: 'Polygon', coordinates: coordinates // 确保坐标格式符合Leaflet要求:[纬度, 经度] }, properties: { color } })) }; // 延迟执行渲染,给浏览器时间绘制加载提示 setTimeout(() => { const layer = L.geoJSON(geoJsonData, { style: (feature) => styleHeatmapPolygon(feature.properties.color) }).addTo(map); setCurrentLayer(layer); setIsLoading(false); }, 0); // 组件卸载或切换热力图时清理图层 return () => { if (currentLayer) map.removeLayer(currentLayer); setIsLoading(false); }; }, [visibleHeatmap, heatmapsData, map]); return ( <> {isLoading && ( <div style={{ position: 'fixed', inset: 0, backgroundColor: 'rgba(0,0,0,0.5)', display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999, cursor: 'wait' }}> <div style={{ color: 'white', fontSize: '20px' }}>加载中...</div> </div> )} </> ); }
3. 强制优先显示加载状态
如果加载提示仍无法及时显示,可在visibleHeatmap变更时先同步设置加载状态,再延迟执行渲染逻辑,给浏览器足够时间绘制提示:
useEffect(() => { if (!visibleHeatmap) return; // 先同步触发加载状态 setIsLoading(true); // 延迟100ms开始渲染,确保加载提示先显示 const timer = setTimeout(() => { // 这里执行分批渲染或GeoJSON渲染逻辑 }, 100); return () => clearTimeout(timer); }, [visibleHeatmap]);
内容的提问来源于stack exchange,提问作者BranTheBroken
相关产品推荐
相关产品推荐

