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

React中如何销毁Google Maps按需配送(ODRD)地图组件

如何在React组件卸载时正确销毁Google Maps ODRD地图并停止API轮询?

我按照Google Maps按需配送解决方案(ODRD)的文档实现了React地图组件,组件运行正常,但卸载后应用仍持续调用API更新行程信息。我尝试在useEffect中添加清理函数,但方案无效,求解决方法。

我的MapComponent代码

const MapComponent = ({ styles }) => {
  const ref = useRef(null);
  const tripId = useRef<string>('');
  const locationProvider =
    useRef<google.maps.journeySharing.FleetEngineTripLocationProvider>();
  const [error, setError] = useState<string | undefined>();
  const mapOptions = useRef<MapOptionsModel>({
    showAnticipatedRoutePolyline: true,
    showTakenRoutePolyline: true,
    destinationMarker: ICON_OPTIONS.USE_DEFAULT,
    vehicleMarker: ICON_OPTIONS.USE_DEFAULT,
  });
  const [trip, setTrip] = useState<TripModel>({
    status: null,
    dropOff: null,
    waypoints: null,
  });

  const setTripId = (newTripId: string) => {
    tripId.current = newTripId;
    if (locationProvider.current) locationProvider.current.tripId = newTripId;
  };

  const setMapOptions = (newMapOptions: MapOptionsModel) => {
    mapOptions.current.showAnticipatedRoutePolyline =
      newMapOptions.showAnticipatedRoutePolyline;
    mapOptions.current.showTakenRoutePolyline =
      newMapOptions.showTakenRoutePolyline;
    mapOptions.current.destinationMarker = newMapOptions.destinationMarker;
    mapOptions.current.vehicleMarker = newMapOptions.vehicleMarker;
    setTripId(tripId.current);
  };

  const authTokenFetcher = async () => {
    const response = await fetch(
      `${PROVIDER_URL}/token/consumer/${tripId.current}`
    );
    const responseJson = await response.json();

    return {
      token: responseJson.jwt,
      expiresInSeconds: 3300,
    };
  };

  useEffect(() => {
    locationProvider.current =
      new google.maps.journeySharing.FleetEngineTripLocationProvider({
        projectId: PROVIDER_PROJECT_ID,
        authTokenFetcher,
        tripId: tripId.current,
        pollingIntervalMillis: DEFAULT_POLLING_INTERVAL_MS,
      });

    locationProvider.current.addListener(
      'error',
      (e: google.maps.ErrorEvent) => {
        setError(e.error.message);
      }
    );

    locationProvider.current.addListener(
      'update',
      (
        e: google.maps.journeySharing.FleetEngineTripLocationProviderUpdateEvent
      ) => {
        if (e.trip) {
          setTrip({
            status: e.trip.status,
            dropOff: e.trip.dropOffTime,
            waypoints: e.trip.remainingWaypoints,
          });
          setError(undefined);
        }
      }
    );

    const mapViewOptions: google.maps.journeySharing.JourneySharingMapViewOptions =
      {
        element: ref.current as unknown as Element,
        locationProvider: locationProvider.current,
        anticipatedRoutePolylineSetup: ({ defaultPolylineOptions }) => {
          return {
            polylineOptions: defaultPolylineOptions,
            visible: mapOptions.current.showAnticipatedRoutePolyline,
          };
        },
        takenRoutePolylineSetup: ({ defaultPolylineOptions }) => {
          return {
            polylineOptions: defaultPolylineOptions,
            visible: mapOptions.current.showTakenRoutePolyline,
          };
        },
        destinationMarkerSetup: ({ defaultMarkerOptions }) => {
          if (
            mapOptions.current.destinationMarker !== ICON_OPTIONS.USE_DEFAULT
          ) {
            defaultMarkerOptions.icon =
              mapOptions.current.destinationMarker.icon;
          }
          return { markerOptions: defaultMarkerOptions };
        },
        vehicleMarkerSetup: ({ defaultMarkerOptions }) => {
          if (mapOptions.current.vehicleMarker !== ICON_OPTIONS.USE_DEFAULT) {
            // Preserve some default icon properties.
            if (defaultMarkerOptions.icon) {
              defaultMarkerOptions.icon = Object.assign(
                defaultMarkerOptions.icon,
                mapOptions.current.vehicleMarker.icon
              );
            }
          }
          return { markerOptions: defaultMarkerOptions };
        },
      };

    const mapView = new google.maps.journeySharing.JourneySharingMapView(
      mapViewOptions
    );

    // Provide default zoom & center so the map loads even if trip ID is bad or stale.
    mapView.map.setOptions(DEFAULT_MAP_OPTIONS);
  }, []);

  return (
        <div style={styles.map} ref={ref} />
  );
};

我的App组件代码

import React from 'react';
import { Wrapper, Status } from '@googlemaps/react-wrapper';
import MapComponent from './src/components/MapComponent';
import { API_KEY } from './src/utils/consts';

const render = (status: Status) => <Text>{status}</Text>;

const App = () => {
  return (
      <Wrapper
        apiKey={API_KEY}
        render={render}
        version={'beta'}
        // @ts-ignore
        libraries={['journeySharing']}
      >
        <MapComponent />
      </Wrapper>
  );
};

我尝试的清理函数(无效)

useEffect(() => {
    locationProvider.current =
      new google.maps.journeySharing.FleetEngineTripLocationProvider({
        projectId: PROVIDER_PROJECT_ID,
        authTokenFetcher,
        tripId: tripId.current,
        pollingIntervalMillis: DEFAULT_POLLING_INTERVAL_MS,
      });

    locationProvider.current.addListener(
      'error',
      (e: google.maps.ErrorEvent) => {
        setError(e.error.message);
      }
    );

    const updateEvent = locationProvider.current.addListener(
      'update',
      (
        e: google.maps.journeySharing.FleetEngineTripLocationProviderUpdateEvent
      ) => {
        if (e.trip) {
          setTrip({
            status: e.trip.status,
            dropOff: e.trip.dropOffTime,
            waypoints: e.trip.remainingWaypoints,
          });
          setError(undefined);
        }
      }
    );

    const mapViewOptions: google.maps.journeySharing.JourneySharingMapViewOptions =
      {
        element: ref.current as unknown as Element,
        locationProvider: locationProvider.current,
        anticipatedRoutePolylineSetup: ({ defaultPolylineOptions }) => {
          return {
            polylineOptions: defaultPolylineOptions,
            visible: mapOptions.current.showAnticipatedRoutePolyline,
          };
        },
        takenRoutePolylineSetup: ({ defaultPolylineOptions }) => {
          return {
            polylineOptions: defaultPolylineOptions,
            visible: mapOptions.current.showTakenRoutePolyline,
          };
        },
        destinationMarkerSetup: ({ defaultMarkerOptions }) => {
          if (
            mapOptions.current.destinationMarker !== ICON_OPTIONS.USE_DEFAULT
          ) {
            defaultMarkerOptions.icon =
              mapOptions.current.destinationMarker.icon;
          }
          return { markerOptions: defaultMarkerOptions };
        },
        vehicleMarkerSetup: ({ defaultMarkerOptions }) => {
          if (mapOptions.current.vehicleMarker !== ICON_OPTIONS.USE_DEFAULT) {
            // Preserve some default icon properties.
            if (defaultMarkerOptions.icon) {
              defaultMarkerOptions.icon = Object.assign(
                defaultMarkerOptions.icon,
                mapOptions.current.vehicleMarker.icon
              );
            }
          }
          return { markerOptions: defaultMarkerOptions };
        },
      };

    const mapView = new google.maps.journeySharing.JourneySharingMapView(
      mapViewOptions
    );

    // Provide default zoom & center so the map loads even if trip ID is bad or stale.
    mapView.map.setOptions(DEFAULT_MAP_OPTIONS);

    return () => {
     mapView.map = null // or mapView.map.setmap(null);
     google.maps.event.removeListener(updateEvent);
    };
  }, []);

解决方案

问题核心是你没有正确停止FleetEngineTripLocationProvider的轮询,也没有完整销毁JourneySharingMapView实例。以下是修正后的实现:

步骤1:保存必要的引用

在组件顶部添加mapView和errorEvent的ref,方便在清理函数中访问:

const mapViewRef = useRef<google.maps.journeySharing.JourneySharingMapView>();
const errorEventRef = useRef<google.maps.MapsEventListener>();

步骤2:完善useEffect的清理逻辑

修改useEffect,保存监听器引用,并在清理函数中执行完整的销毁流程:

useEffect(() => {
  locationProvider.current = new google.maps.journeySharing.FleetEngineTripLocationProvider({
    projectId: PROVIDER_PROJECT_ID,
    authTokenFetcher,
    tripId: tripId.current,
    pollingIntervalMillis: DEFAULT_POLLING_INTERVAL_MS,
  });

  // 保存error监听器引用
  errorEventRef.current = locationProvider.current.addListener(
    'error',
    (e: google.maps.ErrorEvent) => {
      setError(e.error.message);
    }
  );

  // 保存update监听器引用
  const updateEvent = locationProvider.current.addListener(
    'update',
    (e: google.maps.journeySharing.FleetEngineTripLocationProviderUpdateEvent) => {
      if (e.trip) {
        setTrip({
          status: e.trip.status,
          dropOff: e.trip.dropOffTime,
          waypoints: e.trip.remainingWaypoints,
        });
        setError(undefined);
      }
    }
  );

  const mapViewOptions: google.maps.journeySharing.JourneySharingMapViewOptions = {
    element: ref.current as unknown as Element,
    locationProvider: locationProvider.current,
    anticipatedRoutePolylineSetup: ({ defaultPolylineOptions }) => {
      return {
        polylineOptions: defaultPolylineOptions,
        visible: mapOptions.current.showAnticipatedRoutePolyline,
      };
    },
    takenRoutePolylineSetup: ({ defaultPolylineOptions }) => {
      return {
        polylineOptions: defaultPolylineOptions,
        visible: mapOptions.current.showTakenRoutePolyline,
      };
    },
    destinationMarkerSetup: ({ defaultMarkerOptions }) => {
      if (mapOptions.current.destinationMarker !== ICON_OPTIONS.USE_DEFAULT) {
        defaultMarkerOptions.icon = mapOptions.current.destinationMarker.icon;
      }
      return { markerOptions: defaultMarkerOptions };
    },
    vehicleMarkerSetup: ({ defaultMarkerOptions }) => {
      if (mapOptions.current.vehicleMarker !== ICON_OPTIONS.USE_DEFAULT) {
        if (defaultMarkerOptions.icon) {
          defaultMarkerOptions.icon = Object.assign(
            defaultMarkerOptions.icon,
            mapOptions.current.vehicleMarker.icon
          );
        }
      }
      return { markerOptions: defaultMarkerOptions };
    },
  };

  const mapView = new google.maps.journeySharing.JourneySharingMapView(mapViewOptions);
  mapViewRef.current = mapView;
  mapView.map.setOptions(DEFAULT_MAP_OPTIONS);

  // 清理函数
  return () => {
    // 1. 停止FleetEngine的轮询
    if (locationProvider.current) {
      locationProvider.current.stopPolling();
    }

    // 2. 移除所有监听器
    if (updateEvent) {
      google.maps.event.removeListener(updateEvent);
    }
    if (errorEventRef.current) {
      google.maps.event.removeListener(errorEventRef.current);
    }

    // 3. 销毁JourneySharingMapView
    if (mapViewRef.current) {
      // 先移除地图实例
      mapViewRef.current.map.setMap(null);
      // 清空引用释放资源
      mapViewRef.current = null;
    }

    // 4. 清空locationProvider引用
    locationProvider.current = null;
  };
}, []);

关键说明

  • stopPolling():这是FleetEngineTripLocationProvider的核心方法,调用后会立即停止API轮询请求。
  • 移除所有监听器:确保没有残留的事件监听导致内存泄漏。
  • setMap(null):将地图实例从DOM元素中移除,释放相关资源。

这样修改后,组件卸载时会完整清理所有地图相关资源,停止API调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 16:05:53