You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在ReactJS中实现Google Places搜索框的代码问题排查

React Google Places SearchBox 代码问题排查与修复

存在的问题分析

  • LoadScript 包裹范围错误:输入框<input>在LoadScript组件外部,导致Google Maps API加载完成前,初始化SearchBox时可能无法获取输入框元素,且API未加载时google全局对象不存在会直接报错。
  • DOM 查询不符合React规范:使用document.getElementById('pac-input')获取输入框,React中应优先用useRef引用DOM元素,避免直接操作DOM。
  • 错误操作mapRef对象:mapRef.current是挂载地图的DOM元素,并非Google Maps实例,调用mapRef.current.getMarkers()和mapRef.current.addMarker()会抛出错误,需自行维护标记点状态。
  • 未绑定places_changed事件:未给SearchBox添加places_changed事件监听,导致选择地点时handlePlacesChanged函数不会触发。
  • 未处理空值情况:searchBox初始状态为null,直接调用searchBox.getPlaces()可能引发空指针错误。
  • useEffect依赖缺失:初始化逻辑的useEffect未考虑Google Maps API加载状态,可能在API未加载时执行代码导致报错。

修复后的代码

import React, { useEffect, useRef, useState } from 'react';
import { LoadScript } from '@react-google-maps/api';

export default function GoogleMapSearchBox() {
  const mapRef = useRef(null);
  const inputRef = useRef(null);
  const [map, setMap] = useState(null);
  const [searchBox, setSearchBox] = useState(null);
  const [markers, setMarkers] = useState([]);

  const handlePlacesChanged = () => {
    if (!searchBox) return;
    
    const places = searchBox.getPlaces();
    if (places.length === 0) return;

    const bounds = new google.maps.LatLngBounds();
    // 清除旧标记
    markers.forEach(marker => marker.setMap(null));
    const newMarkers = [];

    places.forEach((place) => {
      if (!place.geometry || !place.geometry.location) {
        console.log('返回的地点不包含地理信息');
        return;
      }

      const marker = new google.maps.Marker({
        map,
        title: place.name,
        position: place.geometry.location,
      });
      newMarkers.push(marker);

      if (place.geometry.viewport) {
        bounds.union(place.geometry.viewport);
      } else {
        bounds.extend(place.geometry.location);
      }
    });

    setMarkers(newMarkers);
    map.fitBounds(bounds);
  };

  useEffect(() => {
    if (!mapRef.current || !inputRef.current || !window.google) return;

    const mapInstance = new google.maps.Map(mapRef.current, {
      center: { lat: -33.8688, lng: 151.2195 },
      zoom: 13,
      mapTypeId: 'roadmap',
    });
    setMap(mapInstance);

    const searchBoxInstance = new google.maps.places.SearchBox(inputRef.current);
    mapInstance.controls[google.maps.ControlPosition.TOP_LEFT].push(inputRef.current);

    // 绑定事件监听
    const boundsChangedListener = mapInstance.addListener('bounds_changed', () => {
      searchBoxInstance.setBounds(mapInstance.getBounds());
    });
    const placesChangedListener = searchBoxInstance.addListener('places_changed', handlePlacesChanged);

    setSearchBox(searchBoxInstance);

    // 清理函数
    return () => {
      boundsChangedListener.remove();
      placesChangedListener.remove();
      markers.forEach(marker => marker.setMap(null));
    };
  }, [markers]);

  return (
    <div style={{ height: '300px', width: '100%', position: 'relative' }}>
      <LoadScript
        googleMapsApiKey={process.env.GOOGLE_API_KEY}
        libraries={['places']}
      >
        <div 
          id="map" 
          ref={mapRef} 
          style={{ height: '100%', width: '100%' }}
        />
        <input
          className="mt-5"
          type="text"
          ref={inputRef}
          placeholder="搜索地点"
          style={{ position: 'absolute', top: '10px', left: '10px', zIndex: 1000, padding: '8px', borderRadius: '4px', border: '1px solid #ccc' }}
        />
      </LoadScript>
    </div>
  );
}

修复说明

  • 调整LoadScript包裹范围:将输入框和地图容器都放在LoadScript内部,确保API加载完成后再初始化相关组件。
  • 使用useRef引用DOM元素:用inputRef获取输入框,替代document.getElementById,符合React DOM操作规范。
  • 维护标记点状态:用markers状态数组管理地图标记点,添加新标记前清除旧标记,避免内存泄漏。
  • 绑定并清理事件监听:给SearchBox添加places_changed事件监听,组件卸载时移除所有监听和标记点,防止内存泄漏。
  • 增加空值判断:在handlePlacesChanged开头判断searchBox是否存在,避免空指针错误。
  • 优化输入框样式:给输入框添加绝对定位,使其显示在地图上方,和官方示例布局一致。

内容的提问来源于stack exchange,提问作者Movindu G

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 01:42:26