React18 + @react-google-maps/api路线残留问题求助
解决@react-google-maps/api切换路线后旧路线残留问题
问题描述
使用Next.js 12.3.1和React.js 18.2.0开发项目,集成@react-google-maps/api实现地图路线功能。选择起点A和终点B时,DirectionsService与DirectionsRenderer可正常渲染路线及标记;但切换起点或终点后,新路线能正常显示,最初的路线/线条却残留在地图上。
问题原因
- 旧路线状态未清除:起点、终点或途经点变化时,
directions状态未重置为null,导致DirectionsRenderer同时渲染新旧路线。 - 途经点格式错误:生成
waypoints时,将location对象转成字符串,不符合Google Maps API要求的格式,可能引发路线计算异常或重复渲染。 - 组件更新逻辑不严谨:
useCallback和useEffect的依赖数组不完整,加上call状态触发时机问题,导致路线请求与状态更新不同步。 - Marker的Key设置不合理:仅用坐标值作为Key,无法精准触发组件重新渲染。
解决方案
步骤1:重置路线状态
在起点、终点或途经点变化时,立即清空旧的路线数据,确保新路线渲染前旧路线被移除。
步骤2:修复途经点格式
移除location对象的toString()转换,直接传入坐标对象。
步骤3:完善组件依赖项
补全useCallback和useEffect的依赖数组,避免因依赖缺失导致的状态更新不及时。
步骤4:优化Marker的Key值
使用包含坐标和类型的复合Key,确保位置变化时Marker能正确重新渲染。
修改后的代码
CustomGoogleMap组件
import React, { useEffect, useState, memo } from "react"; import { GoogleMap, MarkerF, DirectionsRenderer, DirectionsService, InfoWindow, LoadScriptNext, } from "@react-google-maps/api"; import LoadingMarker from "../SvgIcons/loading-marker.svg"; import UnLoadingMarker from "../SvgIcons/unloading-marker.svg"; import StoppageIcon from "../SvgIcons/stopageIcon.svg"; import { isEmptyArray } from "@services/utils"; import { anyObjectType, Dispatcher } from "@services/types"; const containerStyle = { width: "100%", height: "100%", }; const center = { lat: 23.810331, lng: 90.412521, }; interface CustomLatLong { latitude: number; longitude: number; textEn: string; } type CustomDirectionsWaypoint = google.maps.DirectionsWaypoint & CustomLatLong; interface CustomGoogleMapProps { call: boolean; setCall: Dispatcher<boolean>; handleStopageDragLocation?: (a: number, b: number, c: number) => void; handleLoadingPointDragLocation?: (a: number, b: number) => void; handleUnLoadingPointDragLocation?: (a: number, b: number) => void; waypointsList2: CustomDirectionsWaypoint[]; loadingP: anyObjectType; unloadingP: anyObjectType; isAddressModal?: boolean; } const CustomGoogleMap: React.FC<CustomGoogleMapProps> = (props) => { const { call, setCall, loadingP, unloadingP, waypointsList2, handleLoadingPointDragLocation, handleUnLoadingPointDragLocation, handleStopageDragLocation, isAddressModal = false, } = props; const [infoBox, setInfoWindow] = useState({ position: { lat: 0, lng: 0, }, info: "", }); const [directions, setDirections] = useState<google.maps.DirectionsResult | null>(null); // 路线参数变化时重置状态并触发请求 useEffect(() => { if (loadingP.latitude && unloadingP.latitude) { setDirections(null); setCall(true); } }, [loadingP.latitude, unloadingP.latitude, waypointsList2, setCall]); const directionsCallback = (response: google.maps.DirectionsResult | null) => { if (response !== null) { if (response.status === "OK") { setCall(false); setDirections(response); } } }; const getDirectionRender = () => { return ( <DirectionsService options={{ destination: { lat: unloadingP.latitude, lng: unloadingP.longitude, }, origin: { lat: loadingP.latitude, lng: loadingP.longitude, }, travelMode: google.maps.TravelMode.DRIVING, waypoints: waypointsList2.map(({ latitude, longitude }) => ({ location: { lat: latitude, lng: longitude }, stopover: false, })), }} callback={directionsCallback} /> ); }; const renderMap = () => { return ( <LoadScriptNext googleMapsApiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!} > <GoogleMap mapContainerStyle={containerStyle} center={ loadingP.latitude ? { lat: loadingP.latitude, lng: loadingP.longitude, } : center } zoom={10} onClick={({ latLng }) => { isAddressModal && handleLoadingPointDragLocation && handleLoadingPointDragLocation(latLng!.lat(), latLng!.lng()); }} > {loadingP.latitude && unloadingP.latitude && call && getDirectionRender()} {directions && ( <DirectionsRenderer options={{ directions: directions, suppressMarkers: true, }} /> )} {infoBox.position.lat !== 0 && ( <InfoWindow position={infoBox.position} onCloseClick={() => setInfoWindow({ position: { lat: 0, lng: 0 }, info: "", }) } > <p>{infoBox.info}</p> </InfoWindow> )} <MarkerF key={`loading-${loadingP.latitude}-${loadingP.longitude}`} draggable onDragEnd={({ latLng }) => { handleLoadingPointDragLocation && handleLoadingPointDragLocation(latLng!.lat(), latLng!.lng()); }} position={{ lat: loadingP.latitude, lng: loadingP.longitude }} icon={LoadingMarker} onClick={() => setInfoWindow({ ...infoBox, position: { lat: loadingP.latitude, lng: loadingP.longitude }, info: loadingP.textEn, }) } /> <MarkerF key={`unloading-${unloadingP.latitude}-${unloadingP.longitude}`} draggable onDragEnd={({ latLng }) => { handleUnLoadingPointDragLocation && handleUnLoadingPointDragLocation(latLng!.lat(), latLng!.lng()); }} position={{ lat: unloadingP.latitude, lng: unloadingP.longitude }} icon={UnLoadingMarker} onClick={() => setInfoWindow({ ...infoBox, position: { lat: unloadingP.latitude, lng: unloadingP.longitude }, info: unloadingP.textEn, }) } /> {!isEmptyArray(waypointsList2) && waypointsList2.map((item, index) => ( <MarkerF key={`waypoint-${index}-${item.latitude}-${item.longitude}`} draggable visible onDragEnd={({ latLng }) => { handleStopageDragLocation && handleStopageDragLocation(latLng!.lat(), latLng!.lng(), index); }} position={{ lat: item.latitude, lng: item.longitude }} icon={StoppageIcon} onClick={() => setInfoWindow({ ...infoBox, position: { lat: item.latitude, lng: item.longitude }, info: item.textEn, }) } /> ))} </GoogleMap> </LoadScriptNext> ); }; return renderMap(); }; export default memo(CustomGoogleMap);
BookingMap组件
import React, { useCallback, useState } from "react"; import CustomGoogleMap from "@components/core/CustomGoogleMap"; import { Box } from "@mui/material"; import { anyObjectType } from "@services/types"; import MapAPI from "@services/api/map"; import { CreateBookingActions } from "@services/constants"; import { useCreateBookingContext } from "@contexts/CreateBookingContext"; const BookingMap: React.FC<BookingMapProps> = ({ memoizedloadingPoint, memoizedunloadingPoint, memoizedwaypointsList, setToast, setLoadingPoint, setUnLoadingPoint, }) => { const cbCtx = useCreateBookingContext(); const { state, dispatch } = cbCtx; const { waypointsList } = state; const [call, setCall] = useState(false); const handleLoadingPointDragLocation = useCallback( async (lat: number, lng: number) => { if (lat && lng) { const query = { latlng: `${lat},${lng}` }; const { response, error }: any = await MapAPI.onDrag(query); if (response) { setLoadingPoint({ ...response.data.data }); } else { setToast("Something went wrong", "error"); } } }, [setLoadingPoint, setToast] ); const handleUnLoadingPointDragLocation = useCallback( async (lat: number, lng: number) => { if (lat && lng) { const query = { latlng: `${lat},${lng}` }; const { response, error }: any = await MapAPI.onDrag(query); if (response) { setUnLoadingPoint({ ...response.data.data }); } else { setToast("Something went wrong", "error"); } } }, [setUnLoadingPoint, setToast] ); const setWaypointsList = useCallback((val: any[]) => { dispatch({ type: CreateBookingActions.SET_WAYPOINT_LIST, payload: val, }); }, [dispatch]); const handleStopageDragLocation = useCallback( async (lat: number, lng: number, index: number) => { if (lat && lng) { const query = { latlng: `${lat},${lng}` }; const { response, error }: any = await MapAPI.onDrag(query); if (response) { const updatedWaypoints = [...waypointsList]; updatedWaypoints[index] = { ...response.data.data }; setWaypointsList(updatedWaypoints); setCall(true); } else { setToast("Something went wrong", "error"); } } }, [waypointsList, setWaypointsList, setToast] ); return ( <Box sx={{ height: "420px" }}> <CustomGoogleMap call={call} setCall={setCall} loadingP={memoizedloadingPoint} unloadingP={memoizedunloadingPoint} waypointsList2={memoizedwaypointsList} handleLoadingPointDragLocation={handleLoadingPointDragLocation} handleUnLoadingPointDragLocation={handleUnLoadingPointDragLocation} handleStopageDragLocation={handleStopageDragLocation} /> </Box> ); }; export default BookingMap; interface BookingMapProps { memoizedloadingPoint: anyObjectType; memoizedunloadingPoint: anyObjectType; memoizedwaypointsList: any[]; setToast: (a: string, b: string) => void; setLoadingPoint: (a: anyObjectType) => void; setUnLoadingPoint: (a: anyObjectType) => void; }
内容的提问来源于stack exchange,提问作者Kamrul Islam
相关产品推荐
相关产品推荐

