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='© 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
相关产品推荐
相关产品推荐

