快速挂载卸载React-Leaflet地图时出现TypeError: el is undefined错误
快速挂载卸载Leaflet地图时触发TypeError: el is undefined错误
快速挂载卸载Leaflet地图组件时,会抛出以下错误:
TypeError: el is undefined 244 | // this method is only used for elements previously positioned using setPosition, 245 | // so it's safe to cache the position for performance 246 | > 247 | return el._leaflet_pos || new Point(0, 0); 248 | } 249 | 250 | // @function disableTextSelection()
我的实现代码如下:
const MapComponent = ({ locations }) => { const map = useMap(); const centerAroundMarkers = useCallback(() => { if (locations?.length > 0) { const bounds = L.latLngBounds(); locations?.forEach((location) => { bounds.extend(L.latLng([location.y, location.x])); }); map.fitBounds(bounds.pad(0.15)); } }, [locations, map]); useEffect(() => { centerAroundMarkers(); }, [centerAroundMarkers]); const viewerResize = useCallback( () => debounce(() => { map.invalidateSize(); centerAroundMarkers(); }, 100), [centerAroundMarkers, map] ); useEffect(() => { const resizeObserver = new ResizeObserver(viewerResize); resizeObserver.observe(map._container); return () => { console.log('unmount'); console.log(map); resizeObserver.unobserve(map._container); }; }, [viewerResize, map]); return null; }; const HostsMap = ({ locations }) => { const classes = useStyles(); return ( <MapContainer className={classes.mapContainer} dragging={false} bounds={[ [-80, -160], [80, 160], ]} maxZoom={25} zoomControl={false} scrollWheelZoom={false} doubleClickZoom={false} boxZoom={false} touchZoom={false} keyboard={false} touchExtend={false} tap={false} zoomSnap={0.1} > <MapComponent locations={locations} /> <> <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" maxNativeZoom={18} maxZoom={25} noWrap={false} /> </> </MapContainer> ); };
问题分析
这个错误仅在快速挂载卸载场景下出现,核心原因是资源清理不及时:
- 防抖回调在组件卸载后仍可能执行,此时地图DOM容器已被销毁,调用Leaflet API会访问不存在的元素
- ResizeObserver的清理逻辑不够彻底,快速卸载时观察者的回调可能仍在触发,导致访问已销毁的
map._container
解决方案
通过以下几点优化修复问题:
- 用可取消的定时器替代防抖函数,在组件卸载时主动清除未执行的回调
- 在所有调用Leaflet API的位置,增加地图容器的存在性校验
- 彻底销毁ResizeObserver,避免残留回调触发
修改后的MapComponent代码:
const MapComponent = ({ locations }) => { const map = useMap(); const debounceTimerRef = useRef(null); const centerAroundMarkers = useCallback(() => { // 校验地图容器和数据是否有效 if (!map?._container || !locations?.length) return; const bounds = L.latLngBounds(); locations.forEach((location) => { bounds.extend(L.latLng([location.y, location.x])); }); map.fitBounds(bounds.pad(0.15)); }, [locations, map]); useEffect(() => { centerAroundMarkers(); }, [centerAroundMarkers]); const viewerResize = useCallback(() => { // 清除之前未执行的定时器 if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } debounceTimerRef.current = setTimeout(() => { // 确保地图容器存在再执行操作 if (map?._container) { map.invalidateSize(); centerAroundMarkers(); } }, 100); }, [centerAroundMarkers, map]); useEffect(() => { const resizeObserver = new ResizeObserver(viewerResize); if (map._container) { resizeObserver.observe(map._container); } return () => { // 清理定时器 if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } // 取消观察并销毁观察者 if (map._container) { resizeObserver.unobserve(map._container); } resizeObserver.disconnect(); }; }, [viewerResize, map]); return null; };
优化说明
- 用
useRef存储定时器ID,在组件卸载时主动清除,避免回调在销毁后执行 - 所有地图操作前增加
map?._container校验,确保操作的DOM元素存在 - 卸载时调用
resizeObserver.disconnect()彻底销毁观察者,杜绝残留回调触发
内容的提问来源于stack exchange,提问作者Daniel van der Maas
相关产品推荐
相关产品推荐

