React StrictMode下Google Maps地理围栏绘制功能失效求助
问题
我有一个React v18.2(TypeScript)应用,需要为用户展示Google地图并提供地理围栏(geofence)功能,允许用户在地图上绘制多边形。使用@react-google-maps/api v2.18.1集成地图时,不开启React.StrictMode一切正常,但开启后地理围栏UI无法渲染,用户无法使用绘制功能。
请问该问题与React StrictMode有何关联?是否有解决方法能让我在开启React.StrictMode的同时正常使用地理围栏功能?
以下是原组件代码:
Map组件
import { Polygon, GoogleMap, useLoadScript } from '@react-google-maps/api'; import { DrawingManager } from './drawing-manager'; interface MapsProps { lat: number; lng: number; isStatic: boolean; zoom?: number; isDrawable?: boolean; } interface Shape { type: string; path: google.maps.LatLngLiteral[]; radius?: number; } const { useRef, useMemo, useState, useCallback } = React; const __mapMandatoryStyles = { width: '100%', height: '100%' }; // ⌂ Keep it like this and style the parent if needed! const googleMapsApiKey = process.env.REACT_APP_GOOGLE_MAPS_API_KEY as string; const libraries: ('drawing' | 'geometry' | 'localContext' | 'places' | 'visualization')[] = [ 'drawing', ]; // ⌂ Do NOT touch this type! It comes from the '@react-google-maps/api' lib but it's not exported from there. export const Map = ({ lat, lng, isStatic, zoom = 15, isDrawable = false, }: MapsProps): JSX.Element | null => { const [shapes, setShapes] = useState<Array<Shape>>([]); const { isLoaded } = useLoadScript({ id: 'google-map-script', googleMapsApiKey, libraries, }); const mapRef = useRef<google.maps.Map | null>(null); const drawingManagerRef = useRef(null); const handleMapLoad = useCallback((map: google.maps.Map) => { if (mapRef?.current) { mapRef.current = map; } }, []); const handleDraw = useCallback( (shape: Shape) => { setShapes([...shapes, shape]); }, [shapes] ); const handleOnPolygonComplete = useCallback( (polygon: google.maps.Polygon) => { if (polygon.getPath() !== null) { const polygonPathArray = polygon.getPath().getArray(); if (polygonPathArray) { const path = polygonPathArray.map(latLngLiteral => { return { lat: latLngLiteral.lat(), lng: latLngLiteral.lng() }; }); handleDraw({ path, type: 'polygon' }); } } }, [handleDraw] ); const renderShapes = useCallback( () => shapes.map((shape, idx): JSX.Element | null => shape.type === 'polygon' ? ( <Polygon key={idx} path={shape.path.map(latLng => latLng)} /> ) : null ), [shapes] ); const getMapOptions = useMemo( () => ({ zoomControl: !isStatic, scrollwheel: !isStatic, rotateControl: !isStatic, clickableIcons: !isStatic, mapTypeControl: !isStatic, keyboardShortcuts: !isStatic, fullscreenControl: !isStatic, streetViewControl: !isStatic, disableDoubleClickZoom: isStatic, isFractionalZoomEnabled: !isStatic, gestureHandling: isStatic ? 'none' : 'auto', }), [isStatic] ); if (isDrawable) { return isLoaded ? ( <GoogleMap zoom={zoom} center={{ lat, lng }} options={getMapOptions} mapContainerStyle={__mapMandatoryStyles} onLoad={handleMapLoad} > <DrawingManager ref={drawingManagerRef} options={{ drawingControl: true, drawingControlOptions: { position: google.maps.ControlPosition.TOP_CENTER, drawingModes: [google?.maps?.drawing?.OverlayType.POLYGON], }, }} onPolygonComplete={handleOnPolygonComplete} /> {renderShapes()} </GoogleMap> ) : null; } return isLoaded ? ( <GoogleMap zoom={zoom} center={{ lat, lng }} options={getMapOptions} mapContainerStyle={__mapMandatoryStyles} /> ) : null; };
DrawingManager组件
import * as React from 'react'; import { DrawingManager as RactGoogleMapsDrawingManager, DrawingManagerProps, } from '@react-google-maps/api'; export const DrawingManager = React.forwardRef<RactGoogleMapsDrawingManager, DrawingManagerProps>( (props, ref): JSX.Element => <RactGoogleMapsDrawingManager ref={ref} {...props} /> ); DrawingManager.displayName = 'DrawingManager';
原因分析
React StrictMode在React 18中会双重执行组件初始化逻辑、副作用(useEffect)及回调函数,目的是检测未清理的副作用、非幂等初始化等潜在bug。
你的问题核心在于:
@react-google-maps/api的DrawingManager在StrictMode的双重渲染下出现实例冲突:第一次渲染创建的实例会被第二次校验渲染销毁,最终导致绘制控件未正确绑定到地图。handleDraw依赖shapes状态,StrictMode下重复执行会导致回调引用不稳定,影响绘制完成后的状态更新。
解决方法
针对StrictMode特性,通过以下调整修复问题:
1. 用函数式更新修复handleDraw的 stale state 问题
移除shapes依赖,直接通过函数式更新获取最新状态:
const handleDraw = useCallback( (shape: Shape) => { setShapes(prevShapes => [...prevShapes, shape]); }, [] // 不再依赖shapes );
2. 优化DrawingManager实例管理
添加useEffect确保实例只正确初始化一次,组件卸载时清理实例:
useEffect(() => { const drawingManager = drawingManagerRef.current; if (mapRef.current && drawingManager && isDrawable) { drawingManager.setMap(mapRef.current); } return () => { if (drawingManager) { drawingManager.setMap(null); } }; }, [isDrawable]);
3. 修正handleMapLoad逻辑
直接赋值map实例,无需多余判断:
const handleMapLoad = useCallback((map: google.maps.Map) => { mapRef.current = map; }, []);
4. 简化renderShapes(可选)
去掉不必要的useCallback,简化渲染逻辑:
const renderShapes = () => shapes.map((shape, idx) => shape.type === 'polygon' ? ( <Polygon key={idx} path={shape.path} /> ) : null );
修改后的完整Map组件
import { Polygon, GoogleMap, useLoadScript } from '@react-google-maps/api'; import { DrawingManager } from './drawing-manager'; interface MapsProps { lat: number; lng: number; isStatic: boolean; zoom?: number; isDrawable?: boolean; } interface Shape { type: string; path: google.maps.LatLngLiteral[]; radius?: number; } const { useRef, useMemo, useState, useCallback, useEffect } = React; const __mapMandatoryStyles = { width: '100%', height: '100%' }; const googleMapsApiKey = process.env.REACT_APP_GOOGLE_MAPS_API_KEY as string; const libraries: ('drawing' | 'geometry' | 'localContext' | 'places' | 'visualization')[] = ['drawing']; export const Map = ({ lat, lng, isStatic, zoom = 15, isDrawable = false, }: MapsProps): JSX.Element | null => { const [shapes, setShapes] = useState<Array<Shape>>([]); const { isLoaded } = useLoadScript({ id: 'google-map-script', googleMapsApiKey, libraries, }); const mapRef = useRef<google.maps.Map | null>(null); const drawingManagerRef = useRef<google.maps.drawing.DrawingManager | null>(null); const handleMapLoad = useCallback((map: google.maps.Map) => { mapRef.current = map; }, []); const handleDraw = useCallback( (shape: Shape) => { setShapes(prevShapes => [...prevShapes, shape]); }, [] ); const handleOnPolygonComplete = useCallback( (polygon: google.maps.Polygon) => { const path = polygon.getPath()?.getArray().map(latLng => ({ lat: latLng.lat(), lng: latLng.lng(), })); if (path) { handleDraw({ path, type: 'polygon' }); } // 可选:移除绘制完成后临时显示的多边形 polygon.setMap(null); }, [handleDraw] ); const renderShapes = () => shapes.map((shape, idx) => shape.type === 'polygon' ? ( <Polygon key={idx} path={shape.path} /> ) : null ); const getMapOptions = useMemo( () => ({ zoomControl: !isStatic, scrollwheel: !isStatic, rotateControl: !isStatic, clickableIcons: !isStatic, mapTypeControl: !isStatic, keyboardShortcuts: !isStatic, fullscreenControl: !isStatic, streetViewControl: !isStatic, disableDoubleClickZoom: isStatic, isFractionalZoomEnabled: !isStatic, gestureHandling: isStatic ? 'none' : 'auto', }), [isStatic] ); useEffect(() => { const drawingManager = drawingManagerRef.current; if (mapRef.current && drawingManager && isDrawable) { drawingManager.setMap(mapRef.current); } return () => { if (drawingManager) { drawingManager.setMap(null); } }; }, [isDrawable]); if (isDrawable) { return isLoaded ? ( <GoogleMap zoom={zoom} center={{ lat, lng }} options={getMapOptions} mapContainerStyle={__mapMandatoryStyles} onLoad={handleMapLoad} > <DrawingManager ref={drawingManagerRef} options={{ drawingControl: true, drawingControlOptions: { position: google.maps.ControlPosition.TOP_CENTER, drawingModes: [google.maps.drawing.OverlayType.POLYGON], }, }} onPolygonComplete={handleOnPolygonComplete} /> {renderShapes()} </GoogleMap> ) : null; } return isLoaded ? ( <GoogleMap zoom={zoom} center={{ lat, lng }} options={getMapOptions} mapContainerStyle={__mapMandatoryStyles} /> ) : null; };
内容的提问来源于stack exchange,提问作者XxXtraCookie
相关产品推荐
相关产品推荐

