การเพิ่มแผนที่และเครื่องหมายในแอปพลิเคชันการโต้ตอบ

ภาพรวม

บทแนะนํานี้จะแสดงวิธีเพิ่มแผนที่และเครื่องหมายลงในแอปพลิเคชันแสดงความรู้สึกโดยใช้ @googlemaps/react-wrapper และผสานรวมแผนที่และเครื่องหมายไว้ในสถานะแอปพลิเคชัน

ติดตั้ง @googlemaps/react-wrapper

ติดตั้งและใช้ไลบรารี @googlemaps/react-wrapper เพื่อโหลด Maps JavaScript API แบบไดนามิกเมื่อคอมโพเนนต์แสดงผล

npm install @googlemaps/react-wrapper

คุณสามารถนําเข้าและใช้ไลบรารีนี้ร่วมกับรายการต่อไปนี้

import { Wrapper, Status } from "@googlemaps/react-wrapper";

การใช้งานพื้นฐานของคอมโพเนนต์นี้คือการรวมองค์ประกอบย่อยที่ใช้ Maps JavaScript API และคอมโพเนนต์ Wrapper ยังยอมรับคอมโพเนนต์ render สําหรับการแสดงผลคอมโพเนนต์การโหลดหรือการจัดการข้อผิดพลาดขณะโหลด Maps JavaScript API

const render = (status: Status) => {
  return <h1>{status}</h1>;
};

<Wrapper apiKey={"YOUR_API_KEY"} render={render}>
  <YourComponent/>
</Wrapper>

เพิ่มคอมโพเนนต์แผนที่

คอมโพเนนต์ฟังก์ชันพื้นฐานที่จะแสดงแผนที่มีแนวโน้มที่จะใช้ฮุก React ของ useRef, useState และ useEffect

คอมโพเนนต์แผนที่เริ่มต้นจะมีลายเซ็นต่อไปนี้

const Map: React.FC<{}> = () => {};

เนื่องจาก google.maps.Map ต้องใช้ Element เป็นพารามิเตอร์ตัวสร้าง จึงจําเป็นต้องมี useRef เพื่อรักษาออบเจ็กต์ที่เปลี่ยนแปลงได้ซึ่งจะคงอยู่ตลอดอายุการใช้งานของคอมโพเนนต์ ข้อมูลโค้ดต่อไปนี้จะสร้างอินสแตนซ์ของแผนที่ในเว็บฮุค useEffect ในเนื้อความของคอมโพเนนต์ Map

TypeScript

const ref = React.useRef<HTMLDivElement>(null);
const [map, setMap] = React.useState<google.maps.Map>();

React.useEffect(() => {
  if (ref.current && !map) {
    setMap(new window.google.maps.Map(ref.current, {}));
  }
}, [ref, map]);

JavaScript

const ref = React.useRef(null);
const [map, setMap] = React.useState();

React.useEffect(() => {
  if (ref.current && !map) {
    setMap(new window.google.maps.Map(ref.current, {}));
  }
}, [ref, map]);

ฮุก useEffect ข้างต้นจะทํางานเมื่อ ref มีการเปลี่ยนแปลงเท่านั้น ตอนนี้คอมโพเนนต์ Map จะแสดงผลสิ่งต่อไปนี้

return <div ref={ref} />

ขยายคอมโพเนนต์แผนที่ด้วยอุปกรณ์เพิ่มเติม

คอมโพเนนต์แผนที่พื้นฐานสามารถขยายตัวได้ด้วยตัวประกอบเพิ่มเติมสําหรับตัวเลือกแผนที่ Listener เหตุการณ์ และสไตล์ที่ใช้กับ div ที่มีแผนที่ โค้ดต่อไปนี้แสดงอินเทอร์เฟซแบบขยายของคอมโพเนนต์ฟังก์ชันนี้

interface MapProps extends google.maps.MapOptions {
  style: { [key: string]: string };
  onClick?: (e: google.maps.MapMouseEvent) => void;
  onIdle?: (map: google.maps.Map) => void;
}

const Map: React.FC<MapProps> = ({
  onClick,
  onIdle,
  children,
  style,
  ...options
}) => {}

ออบเจ็กต์ style สามารถส่งผ่านโดยตรงและตั้งค่าเป็นฉากในการแสดงผล div

return <div ref={ref} style={style} />;

onClick, onIdle และ google.maps.MapOptions ต้องใช้ฮุก useEffect เพื่อใช้การอัปเดตกับ google.maps.Map อย่างเต็มที่

TypeScript

// because React does not do deep comparisons, a custom hook is used
// see discussion in https://github.com/googlemaps/js-samples/issues/946
useDeepCompareEffectForMaps(() => {
  if (map) {
    map.setOptions(options);
  }
}, [map, options]);

JavaScript

// because React does not do deep comparisons, a custom hook is used
// see discussion in https://github.com/googlemaps/js-samples/issues/946
useDeepCompareEffectForMaps(() => {
  if (map) {
    map.setOptions(options);
  }
}, [map, options]);

Listener เหตุการณ์จําเป็นต้องมีโค้ดที่ซับซ้อนขึ้นเล็กน้อยเพื่อล้าง Listener ที่มีอยู่เมื่อมีการอัปเดตเครื่องจัดการผ่านอุปกรณ์ประกอบฉาก

TypeScript

React.useEffect(() => {
  if (map) {
    ["click", "idle"].forEach((eventName) =>
      google.maps.event.clearListeners(map, eventName)
    );

    if (onClick) {
      map.addListener("click", onClick);
    }

    if (onIdle) {
      map.addListener("idle", () => onIdle(map));
    }
  }
}, [map, onClick, onIdle]);

JavaScript

React.useEffect(() => {
  if (map) {
    ["click", "idle"].forEach((eventName) =>
      google.maps.event.clearListeners(map, eventName)
    );
    if (onClick) {
      map.addListener("click", onClick);
    }

    if (onIdle) {
      map.addListener("idle", () => onIdle(map));
    }
  }
}, [map, onClick, onIdle]);

สร้างคอมโพเนนต์ของตัวทําเครื่องหมาย

คอมโพเนนต์ตัวทําเครื่องหมายใช้รูปแบบที่คล้ายกับคอมโพเนนต์แผนที่ซึ่งมีฮุก useEffect และ useState

TypeScript

const Marker: React.FC<google.maps.MarkerOptions> = (options) => {
  const [marker, setMarker] = React.useState<google.maps.Marker>();

  React.useEffect(() => {
    if (!marker) {
      setMarker(new google.maps.Marker());
    }

    // remove marker from map on unmount
    return () => {
      if (marker) {
        marker.setMap(null);
      }
    };
  }, [marker]);

  React.useEffect(() => {
    if (marker) {
      marker.setOptions(options);
    }
  }, [marker, options]);

  return null;
};

JavaScript

const Marker = (options) => {
  const [marker, setMarker] = React.useState();

  React.useEffect(() => {
    if (!marker) {
      setMarker(new google.maps.Marker());
    }

    // remove marker from map on unmount
    return () => {
      if (marker) {
        marker.setMap(null);
      }
    };
  }, [marker]);
  React.useEffect(() => {
    if (marker) {
      marker.setOptions(options);
    }
  }, [marker, options]);
  return null;
};

คอมโพเนนต์จะแสดงผลค่า Null เมื่อ google.maps.Map จัดการการควบคุม DOM

เพิ่มตัวทําเครื่องหมายเป็นคอมโพเนนต์ย่อยของแผนที่

ในการเพิ่มตัวทําเครื่องหมายลงในแผนที่ ระบบจะส่งคอมโพเนนต์ Marker ไปยังคอมโพเนนต์ Map โดยใช้ตัวประกอบ children พิเศษดังต่อไปนี้

<Wrapper apiKey={"YOUR_API_KEY"}>
  <Map center={center} zoom={zoom}>
    <Marker position={position} />
  </Map>
</Wrapper>

ต้องเปลี่ยนแปลงเอาต์พุตเล็กน้อยของคอมโพเนนต์ Map เพื่อส่งออบเจ็กต์ google.maps.Map ไปยังเด็กทั้งหมดเป็นอุปกรณ์เพิ่มเติม

TypeScript

return (
  <>
    <div ref={ref} style={style} />
    {React.Children.map(children, (child) => {
      if (React.isValidElement(child)) {
        // set the map prop on the child component
        // @ts-ignore
        return React.cloneElement(child, { map });
      }
    })}
  </>
);

JavaScript

return (
  <>
    <div ref={ref} style={style} />
    {React.Children.map(children, (child) => {
      if (React.isValidElement(child)) {
        // set the map prop on the child component
        // @ts-ignore
        return React.cloneElement(child, { map });
      }
    })}
  </>
);

ลิงก์แผนที่และสถานะแอปพลิเคชัน

เมื่อใช้รูปแบบข้างต้นสําหรับการเรียกกลับ onClick และ onIdle แอปพลิเคชันจะสามารถขยายเพื่อรวมการทํางานของผู้ใช้ทั้งหมด เช่น การคลิกหรือเลื่อนแผนที่

TypeScript

const [clicks, setClicks] = React.useState<google.maps.LatLng[]>([]);
const [zoom, setZoom] = React.useState(3); // initial zoom
const [center, setCenter] = React.useState<google.maps.LatLngLiteral>({
  lat: 0,
  lng: 0,
});

const onClick = (e: google.maps.MapMouseEvent) => {
  // avoid directly mutating state
  setClicks([...clicks, e.latLng!]);
};

const onIdle = (m: google.maps.Map) => {
  console.log("onIdle");
  setZoom(m.getZoom()!);
  setCenter(m.getCenter()!.toJSON());
};

JavaScript

const [clicks, setClicks] = React.useState([]);
const [zoom, setZoom] = React.useState(3); // initial zoom
const [center, setCenter] = React.useState({
  lat: 0,
  lng: 0,
});

const onClick = (e) => {
  // avoid directly mutating state
  setClicks([...clicks, e.latLng]);
};

const onIdle = (m) => {
  console.log("onIdle");
  setZoom(m.getZoom());
  setCenter(m.getCenter().toJSON());
};

ตะขอเหล่านี้ผสานรวมกับองค์ประกอบแบบฟอร์มได้โดยใช้รูปแบบต่อไปนี้ตามที่แสดงในอินพุตละติจูด

<label htmlFor="lat">Latitude</label>
<input
  type="number"
  id="lat"
  name="lat"
  value={center.lat}
  onChange={(event) =>
    setCenter({ ...center, lat: Number(event.target.value) })
  }
/>

สุดท้าย แอปพลิเคชันจะติดตามคลิก และแสดงเครื่องหมายในตําแหน่งคลิกแต่ละตําแหน่ง โค้ดต่อไปนี้ใช้ตัวแปร position เพื่อส่งค่า LatLng จากเหตุการณ์การคลิก

{clicks.map((latLng, i) => (<Marker key={i} position={latLng} />))}

สํารวจโค้ด

โดยโค้ดตัวอย่างที่สมบูรณ์จะสํารวจผ่านสนามเด็กเล่นโค้ดออนไลน์ด้านล่างหรือโคลนที่เก็บ Git ก็ได้

ลองใช้ตัวอย่าง

ตัวอย่างการโคลน

ต้องใช้ Git และ Node.js เพื่อเรียกใช้ตัวอย่างนี้ในเครื่อง ทําตามวิธีการติดตั้ง Node.js และ NPM คําสั่งต่อไปนี้จะโคลน ติดตั้งทรัพยากร Dependency และเริ่มแอปพลิเคชันตัวอย่าง

  git clone -b sample-react-map https://github.com/googlemaps/js-samples.git
  cd js-samples
  npm i
  npm start

สามารถลองใช้ตัวอย่างอื่นๆ โดยเปลี่ยนไปใช้สาขาใดก็ได้ที่ขึ้นต้นด้วย sample-SAMPLE_NAME

  git checkout sample-SAMPLE_NAME
  npm i
  npm start