如何在react-google-maps中完全控制InfoWindow的显隐状态?
InfoWindow点击外部关闭后残留三角元素的解决方案
我通过状态displayInfoWindow控制<InfoWindow>的显示:状态为true时渲染组件,false时卸载。点击Marker可以打开窗口,点击窗口自带的关闭按钮能关闭,但我需要实现点击页面其他区域也关闭窗口,于是加了一个监听外部点击的自定义钩子,结果现在窗口主体关闭后,会残留一个由::after伪元素生成的白色三角。
现有代码
Point.js
const Point = ({ point }) => { const [displayInfoWindow, setDisplayInfoWindow] = useState(false); const handlePointClick = () => { setDisplayInfoWindow(true); }; return ( <Marker onClick={handlePointClick} position={{ lat: point.coordinates.lat, lng: point.coordinates.lng }} key={point.key} > {displayInfoWindow && ( <InfoWindow onCloseClick={() => setDisplayInfoWindow(!displayInfoWindow)} > <div className="Point"> <h1>Info Window</h1> </div> </InfoWindow> )} </Marker> ); };
useOnOutsideClick.js
const useOnOutsideClick = (refArray, callback) => { useEffect(() => { const listener = (e) => { for (let i = 0; i < refArray.length; i++) { const ref = refArray[i]; if ( !ref.current || (ref.current.contains && ref.current.contains(e.target)) ) { return; } } callback(); }; document.addEventListener("mousedown", listener); document.addEventListener("touchstart", listener); return () => { document.removeEventListener("mousedown", listener); document.removeEventListener("touchstart", listener); }; }, [refArray, callback]); };
问题根源
残留的三角是<InfoWindow>组件内部生成的DOM元素(或伪元素),直接通过条件渲染卸载组件时,Google Maps API的异步DOM操作可能没来得及清理所有关联元素;另外自定义外部点击钩子没有触发组件的原生关闭流程,导致部分元素残留。
推荐解决方案:利用组件原生能力
绝大多数React版Google Maps库(比如@react-google-maps/api)的<InfoWindow>已经内置了「点击地图其他区域自动关闭」的逻辑,不需要自己写外部点击监听。只需要调整状态绑定方式:
import { Marker, InfoWindow } from '@react-google-maps/api'; const Point = ({ point }) => { const [isInfoWindowOpen, setIsInfoWindowOpen] = useState(false); const handleMarkerClick = () => { setIsInfoWindowOpen(true); }; const handleInfoWindowClose = () => { setIsInfoWindowOpen(false); }; return ( <> <Marker onClick={handleMarkerClick} position={{ lat: point.coordinates.lat, lng: point.coordinates.lng }} key={point.key} /> <InfoWindow position={{ lat: point.coordinates.lat, lng: point.coordinates.lng }} visible={isInfoWindowOpen} onCloseClick={handleInfoWindowClose} > <div className="Point"> <h1>Info Window</h1> </div> </InfoWindow> </> ); };
关键调整点:
- 把
<InfoWindow>从<Marker>的子元素中移出,作为同级组件,通过position属性绑定位置(这是库的推荐用法)。 - 用组件的
visible(部分库是open)属性控制显示,而非条件渲染。这样组件会调用Google Maps API的原生关闭方法,确保所有关联DOM元素(包括三角伪元素)被彻底清理。 - 此时点击地图任意非窗口区域,API会自动触发
onCloseClick,同步更新你的状态,完美实现外部点击关闭。
如果你坚持用条件渲染+自定义钩子
如果一定要保留原有的条件渲染逻辑,需要确保外部点击钩子能正确识别<InfoWindow>的所有DOM节点:
const Point = ({ point }) => { const [displayInfoWindow, setDisplayInfoWindow] = useState(false); const infoWindowRef = useRef(null); // 传入InfoWindow的DOM引用 useOnOutsideClick([infoWindowRef], () => { setDisplayInfoWindow(false); }); const handlePointClick = () => { setDisplayInfoWindow(true); }; return ( <Marker onClick={handlePointClick} position={{ lat: point.coordinates.lat, lng: point.coordinates.lng }} key={point.key} > {displayInfoWindow && ( <InfoWindow ref={infoWindowRef} onCloseClick={() => setDisplayInfoWindow(false)} > <div className="Point"> <h1>Info Window</h1> </div> </InfoWindow> )} </Marker> ); };
这种方式需要确保useOnOutsideClick中的contains判断能覆盖InfoWindow的所有子元素(包括三角所在容器),但仍可能因为库的内部DOM结构问题出现残留,所以优先推荐第一种方案。
内容的提问来源于stack exchange,提问作者Willey Ohana
相关产品推荐
相关产品推荐

