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

快速挂载卸载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

解决方案

通过以下几点优化修复问题:

  1. 用可取消的定时器替代防抖函数,在组件卸载时主动清除未执行的回调
  2. 在所有调用Leaflet API的位置,增加地图容器的存在性校验
  3. 彻底销毁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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 19:50:46