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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 01:30:26