如何在react-google-maps挂载后为地图/聚合器增删标记?
我之前也踩过这个坑,那个Uncaught TypeError: Cannot read property '__SECRET_MARKER_CLUSTERER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' of undefined错误,本质是因为你搞错了react-google-maps组件的工作逻辑——它是受控组件,不能直接操作底层Google Maps原生实例来修改标记,必须通过React的状态管理来同步标记列表。
为什么你的原有方法会失败?
react-google-maps封装了MarkerClusterer的底层实例,组件内部依赖这个“私有”属性来维护React组件和原生地图实例的状态同步。你直接调用原生的addMarker方法或者创建原生Marker挂载到地图上,完全绕过了React的生命周期,导致组件状态和底层实例不一致,最终触发这个错误。
正确解决方案:用React状态管理标记列表
MarkerClusterer是通过子组件<Marker>来管理标记的,所以你只需要维护一个标记数据的数组,通过修改这个数组(更新状态)让React自动重新渲染MarkerClusterer和内部的标记,底层的聚合逻辑会自动处理。
具体步骤
用状态维护标记数据
用useState(函数组件)或者this.state(类组件)来存储标记数组,比如在父组件中初始化:const [markers, setMarkers] = useState(props.hotels.results.map((item, index) => ({ id: index, // 给每个标记设置唯一ID,方便后续操作 lat: parseFloat(item.latitude), lng: parseFloat(item.longitude), title: item.name // 可选:标记的标题 })));调整Map组件,接收标记props
修改你的高阶组件,让它接收markers数组和点击回调:const MapWithAMarkerClusterer = compose( withProps({ googleMapURL: "https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=AIzaSyATVkXuYRZCkB73ZsPEJisDy74GnVusIJw", loadingElement: <div style={{ height: `100%` }} />, containerElement: <div className="mapContainer" />, mapElement: <div style={{ height: `100%` }} />, }), withScriptjs, withGoogleMap )(props => ( <GoogleMap defaultZoom={3} defaultCenter={{ lat: 25.0391667, lng: 121.525 }}> <MarkerClusterer onClick={props.onClusterClick} averageCenter enableRetinaIcons gridSize={60} > {props.markers.map(marker => ( <Marker key={marker.id} position={{ lat: marker.lat, lng: marker.lng }} title={marker.title} /> ))} </MarkerClusterer> </GoogleMap> ));动态添加标记:更新状态数组
在点击按钮或MarkerClusterer的点击事件中,不要操作原生实例,而是更新标记数组的状态:const handleClusterClick = () => { // 定义新标记的数据 const newMarker = { id: Date.now(), // 用时间戳作为唯一ID lat: 56.565123, lng: 9.030908, title: 'Hello World!' }; // 追加新标记到数组,更新状态 setMarkers(prevMarkers => [...prevMarkers, newMarker]); };动态移除标记:过滤状态数组
如果需要移除某个标记,只需过滤掉对应ID的项即可:const removeMarker = (markerId) => { setMarkers(prevMarkers => prevMarkers.filter(marker => marker.id !== markerId)); };
完整修正示例(函数组件版本)
import React, { useState } from 'react'; import { compose, withProps, withScriptjs, withGoogleMap, GoogleMap, Marker, MarkerClusterer } from 'react-google-maps'; const MapWithAMarkerClusterer = compose( withProps({ googleMapURL: "https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=AIzaSyATVkXuYRZCkB73ZsPEJisDy74GnVusIJw", loadingElement: <div style={{ height: `100%` }} />, containerElement: <div style={{ height: '80vh' }} />, mapElement: <div style={{ height: `100%` }} />, }), withScriptjs, withGoogleMap )((props) => ( <GoogleMap defaultZoom={3} defaultCenter={{ lat: 25.0391667, lng: 121.525 }}> <MarkerClusterer onClick={props.onClusterClick} averageCenter enableRetinaIcons gridSize={60} > {props.markers.map((marker) => ( <Marker key={marker.id} position={{ lat: marker.lat, lng: marker.lng }} title={marker.title} /> ))} </MarkerClusterer> </GoogleMap> )); export default function HotelMap(props) { // 初始化酒店标记列表 const [markers, setMarkers] = useState(props.hotels.results.map((item, index) => ({ id: index, lat: parseFloat(item.latitude), lng: parseFloat(item.longitude), title: item.name }))); const handleClusterClick = () => { // 点击聚合器时添加新标记 const newMarker = { id: Date.now(), lat: 56.565123, lng: 9.030908, title: 'Hello World!' }; setMarkers(prev => [...prev, newMarker]); }; return ( <div> <button onClick={handleClusterClick}>点击添加测试标记</button> <MapWithAMarkerClusterer markers={markers} onClusterClick={handleClusterClick} /> </div> ); }
关键注意事项
- 永远不要直接操作react-google-maps组件的refs来调用原生方法,必须通过React状态同步组件和底层实例。
- 给每个
<Marker>设置唯一的key,避免React渲染时出现重复或错误。 - 标记数据最好包含唯一ID,方便后续的移除、修改操作。
内容的提问来源于stack exchange,提问作者dotmlj

