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

React+Google Maps API:动态追踪多途经点地址及显示方案

动态追踪多个途经点并关联Google Maps API显示解决方案

核心问题修正

现有代码存在几个关键问题导致无法正确追踪多个途经点:

  • 单个stopRef无法同时绑定多个输入框,必须为每个途经点分配独立的引用
  • waypointInputList的状态字段不统一(初始用waypointInput,添加时用waypoint),导致状态更新混乱
  • 未利用Autocomplete的onPlaceChanged事件获取有效地址,直接取输入框值可能得到未验证的内容
  • 计算路线时仅处理单个途经点,未批量遍历所有输入

关键实现步骤

  1. 用ref数组管理多个Autocomplete实例:创建一个ref数组,每个元素对应一个途经点的Autocomplete组件,方便获取用户选中的地点详情
  2. 同步维护途经点状态数组:用useState存储每个途经点的输入值,确保和界面输入同步
  3. 绑定onPlaceChanged事件:每个Autocomplete触发选中事件时,更新对应索引的状态值,保证地址是Google Maps验证过的有效地点
  4. 批量生成途经点参数:计算路线时,过滤空值的途经点,转换成Google Maps API要求的格式

修改后的完整代码

import { React, useRef, useState, Fragment } from "react";
import styles from "./RouteForm.module.css";
import KeyboardDoubleArrowDownRoundedIcon from "@mui/icons-material/KeyboardDoubleArrowDownRounded";
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
import PlaceIcon from "@mui/icons-material/Place";
import AddCircleIcon from "@mui/icons-material/AddCircle";
import RemoveCircleIcon from "@mui/icons-material/RemoveCircle";
import {
  GoogleMap,
  useJsApiLoader,
  Autocomplete,
  DirectionsRenderer,
} from "@react-google-maps/api";

const libraries = ["places"];

const RouteForm = (props) => {
  const originRef = useRef(null);
  const destinationRef = useRef(null);
  // 创建ref数组存储每个途经点的Autocomplete实例
  const waypointAutocompleteRefs = useRef([]);

  const [stopContainerVisible, setStopContainerVisible] = useState(false);
  const [directionsRes, setDirectionsRes] = useState(null);
  const [routeFormVisible, setRouteFormVisible] = useState(false);
  // 统一状态字段,每个项存储输入值
  const [waypointInputList, setWaypointInputList] = useState([
    { value: "" },
  ]);

  const handleRouteSubmit = (e) => {
    e.preventDefault();
    calculateRoute();
  };

  const toggleFormVisibility = () => {
    setRouteFormVisible(!routeFormVisible);
  };

  const { isLoaded } = useJsApiLoader({
    id: "google-map-script",
    googleMapsApiKey: "AIzaSyBjizW_mGoQQI6jma6lTEWpaSsoYhKa6y8",
    libraries: libraries,
  });

  const toggleStopVisibility = () => {
    setStopContainerVisible((prevValue) => !prevValue);
  };

  const center = { lat: 42.4414, lng: 19.2624 };

  if (!isLoaded) {
    return (
      <div>
        <p>Loading...</p>
      </div>
    );
  }

  const handleWaypointAdd = () => {
    // 添加时保持字段统一
    setWaypointInputList([...waypointInputList, { value: "" }]);
  };

  const handleWaypointRemove = (index) => {
    const list = [...waypointInputList];
    list.splice(index, 1);
    setWaypointInputList(list);
    // 同步移除ref数组中的对应项
    waypointAutocompleteRefs.current.splice(index, 1);
  };

  // 处理Autocomplete选中事件
  const handlePlaceChanged = (index) => {
    const autocomplete = waypointAutocompleteRefs.current[index];
    if (!autocomplete) return;
    
    const place = autocomplete.getPlace();
    if (place.formatted_address) {
      const updatedList = [...waypointInputList];
      updatedList[index].value = place.formatted_address;
      setWaypointInputList(updatedList);
    }
  };

  async function calculateRoute() {
    const originValue = originRef.current?.value;
    const destinationValue = destinationRef.current?.value;
    
    if (!originValue || !destinationValue) {
      return;
    }

    const directionService = new google.maps.DirectionsService();
    // 生成途经点数组,过滤空值
    const waypoints = waypointInputList
      .filter(item => item.value.trim() !== "")
      .map(item => ({
        location: item.value,
        stopover: true,
      }));

    try {
      const results = await directionService.route({
        origin: originValue,
        destination: destinationValue,
        travelMode: google.maps.TravelMode.DRIVING,
        waypoints: waypoints,
      });
      setDirectionsRes(results);
    } catch (error) {
      console.error("路线计算失败:", error);
    }
  }

  return (
    <Fragment>
      <div className={styles["form-and-map-container"]}>
        <GoogleMap
          center={center}
          zoom={15}
          mapContainerStyle={{
            width: "100%",
            height: "100%",
          }}
          options={{
            zoomControl: false,
            streetViewControl: false,
            mapTypeControl: false,
            fullscreenControl: false,
          }}
        >
          {directionsRes && <DirectionsRenderer directions={directionsRes} />}
        </GoogleMap>

        <div className={styles.container}>
          <div className={styles.expand} onClick={toggleFormVisibility}>
            {routeFormVisible ? (
              <CloseRoundedIcon
                fontSize="inherit"
                className={styles["expand-icon"]}
              />
            ) : (
              <KeyboardDoubleArrowDownRoundedIcon
                fontSize="inherit"
                className={styles["expand-icon"]}
              />
            )}
            <span>Create Route</span>
          </div>
          <div className={styles["form-container"]}>
            {routeFormVisible && (
              <form onSubmit={handleRouteSubmit}>
                <div className={styles.inputs}>
                  <div className={styles["input1-container"]}>
                    <label>
                      <PlaceIcon />
                    </label>
                    <Autocomplete className={styles.input}>
                      <input
                        type="text"
                        placeholder="Origin"
                        ref={originRef}
                        required
                      />
                    </Autocomplete>
                  </div>
                  <div className={styles["input2-container"]}>
                    <label>
                      <PlaceIcon sx={{ color: "#beff0a" }} />
                    </label>
                    <Autocomplete className={styles.input}>
                      <input
                        type="text"
                        placeholder="Destination"
                        ref={destinationRef}
                        required
                      />
                    </Autocomplete>
                  </div>
                </div>
                <div className={styles.btns}>
                  <button type="submit">
                    Create Route
                  </button>
                  <span
                    className={styles["add-stop"]}
                    onClick={toggleStopVisibility}
                  >
                    Add Stop
                  </span>
                </div>
                {stopContainerVisible && (
                  <ul className={styles["stop-container"]}>
                    {waypointInputList.map((singleWaypoint, index) => (
                      <li key={index}>
                        {index < waypointInputList.length - 1 && (
                          <button
                            className={styles["remove-stop-btn"]}
                            onClick={() => handleWaypointRemove(index)}
                            type="button"
                          >
                            <RemoveCircleIcon />
                          </button>
                        )}
                        <Autocomplete 
                          className={styles.input}
                          ref={(el) => waypointAutocompleteRefs.current[index] = el}
                          onPlaceChanged={() => handlePlaceChanged(index)}
                        >
                          <input
                            type="text"
                            placeholder="Stopping place"
                            value={singleWaypoint.value}
                            onChange={(e) => {
                              // 手动输入时同步更新状态
                              const updatedList = [...waypointInputList];
                              updatedList[index].value = e.target.value;
                              setWaypointInputList(updatedList);
                            }}
                          />
                        </Autocomplete>
                        {waypointInputList.length - 1 === index &&
                          waypointInputList.length <= 4 && (
                            <button
                              type="button"
                              className={styles["add-stop-btn"]}
                              onClick={handleWaypointAdd}
                            >
                              <AddCircleIcon />
                            </button>
                          )}
                      </li>
                    ))}
                  </ul>
                )}
              </form>
            )}
          </div>
        </div>
      </div>
    </Fragment>
  );
};

export default RouteForm;

代码说明

  • 用waypointAutocompleteRefs数组存储每个途经点的Autocomplete实例,通过onPlaceChanged事件获取用户选中的标准地址
  • 同时支持手动输入和Autocomplete选择,手动输入时通过onChange同步更新状态
  • 计算路线时过滤空的途经点,避免无效参数传递给API
  • 修正了添加/删除按钮的type属性,防止触发表单默认提交行为

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:44:56