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

React Leaflet地图多次双击后页面无响应问题求助

问题分析与解决方案

核心问题:事件监听未清理导致内存泄漏

你的代码中,MapComponent 每次渲染时都会调用 map.on("dblclick") 绑定新的双击事件,但从未移除旧的监听。随着双击次数增加,事件回调会被叠加执行,导致多次触发API请求、重复更新状态,最终引发页面卡顿甚至无响应。

另外,渲染餐厅标记时每次都新建L.icon实例,也会造成不必要的内存开销;同时用index作为key会影响React的diff性能。


修复步骤与代码调整

1. 正确绑定/清理地图事件监听

在MapComponent中使用useEffect管理事件监听,组件卸载或依赖变化时移除监听:

2. 缓存餐厅图标实例

用useMemo根据餐厅的logoImg缓存图标,避免重复创建:

3. 替换index为唯一标识作为key

用餐厅数据中的唯一ID(比如el.id)代替index,提升列表渲染性能:

修复后的完整代码

import { useState, useEffect, useMemo, useRef } from 'react';
import { useQuery } from '@tanstack/react-query';
import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet';
import L from 'leaflet';
import Spin from 'antd/es/spin';

let vendorMarkerIcon = L.icon({
  iconUrl: markerIconPng,
  iconSize: [25, 41],
  iconAnchor: [12, 41],
});

export const PureMapDrow = () => {
  const [markerPosition, setMarkerPosition] = useState(null);

  const { isLoading: loading, data } = useQuery(
    ["getVendorsOnMap", markerPosition?.[0], markerPosition?.[1]],
    getVendorsOnMap,
    {
      refetchOnWindowFocus: false,
      retry: 2,
      enabled: !!markerPosition,
    }
  );

  const MapComponent = () => {
    const map = useMap();
    const timeoutRef = useRef(null);

    useEffect(() => {
      // 清理之前的定时器
      if (timeoutRef.current) clearTimeout(timeoutRef.current);
      timeoutRef.current = setTimeout(() => {
        map.invalidateSize();
      }, 100);

      return () => {
        clearTimeout(timeoutRef.current);
      };
    }, [isCreateModal, polygon, map]);

    useEffect(() => {
      const handleDblClick = (e) => {
        map.panTo(e.latlng);
        setMarkerPosition([e.latlng.lat, e.latlng.lng]);
      };

      map.on("dblclick", handleDblClick);
      // 组件卸载时移除事件监听
      return () => {
        map.off("dblclick", handleDblClick);
      };
    }, [map]);

    return null;
  };

  // 缓存餐厅图标,避免重复创建
  const getVendorIcon = useMemo(() => {
    const iconCache = new Map();
    return (logoImg) => {
      const key = logoImg || 'default';
      if (!iconCache.has(key)) {
        iconCache.set(key, L.icon({
          className: "marker",
          iconUrl: logoImg || markerIconPng,
          iconSize: [40, 40],
        }));
      }
      return iconCache.get(key);
    };
  }, []);

  return (
    <div>
      <Spin spinning={loading}>
        <MapContainer
          center={[33.3152, 44.3661]}
          zoomControl={false}
          zoom={10}
          scrollWheelZoom={true}
          attributionControl={false}
          style={{ height: "500px" }}
        >
          <TileLayer
            attribution='&copy; OpenStreetMap contributors'
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          />
          {markerPosition && (
            <Marker position={markerPosition} icon={vendorMarkerIcon} />
          )}
          {data?.success && data?.data?.map((el) => (
            <Marker
              key={el.id} // 替换为餐厅唯一ID
              position={[el.lat, el.lon]}
              icon={getVendorIcon(el.logoImg)}
            >
              <Popup>{el.arName}</Popup>
            </Marker>
          ))}
          <MapComponent />
        </MapContainer>
      </Spin>
    </div>
  );
};

额外说明

  • 事件监听的清理是解决卡顿的核心:每次MapComponent渲染时不再叠加绑定双击事件,避免多次触发状态更新和API请求。
  • 图标缓存减少了Leaflet图标实例的重复创建,降低内存占用。
  • 用唯一ID作为key让React更高效地更新标记列表,减少不必要的DOM操作。

内容的提问来源于stack exchange,提问作者hadi muhammed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 01:37:51