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

