Karte und Markierungen zu einer React-Anwendung hinzufügen

Übersicht

In dieser Anleitung erfahren Sie, wie Sie mit @googlemaps/react-wrapper eine Karte und Markierungen zu einer React-Anwendung hinzufügen und in den Anwendungsstatus einbinden.

@googlemaps/react-wrapper installieren

Installieren und verwenden Sie die Bibliothek @googlemaps/react-wrapper, um die Maps JavaScript API dynamisch zu laden, wenn die Komponente gerendert wird.

npm install @googlemaps/react-wrapper

Diese Bibliothek kann mit den folgenden Objekten importiert und verwendet werden.

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

Diese Komponente wird im Wesentlichen verwendet, um untergeordnete Komponenten zu umschließen, die von der Maps JavaScript API abhängig sind. Die Wrapper-Komponente akzeptiert auch ein render-Attribut für das Rendern ladender Komponenten oder das Verarbeiten von Fehlern beim Laden der Maps JavaScript API.

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

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

Kartenkomponente hinzufügen

Eine einfache Funktionskomponente zum Rendern einer Karte nutzt wahrscheinlich die React-Hooks useRef, useState und useEffect.

Die anfängliche Kartenkomponente hat die folgende Signatur.

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

Da für google.maps.Map ein Element als Konstruktorparameter benötigt wird, ist useRef erforderlich, um ein änderbares Objekt beizubehalten, das für die Lebensdauer der Komponente bestehen bleibt. Mit dem folgenden Snippet wird eine Karte innerhalb des Hooks useEffect im Text der Map-Komponente instanziiert.

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]);

Der obige useEffect-Hook wird nur ausgeführt, wenn sich ref geändert hat. Die Map-Komponente gibt jetzt Folgendes zurück:

return <div ref={ref} />

Kartenkomponente mit zusätzlichen Attributen erweitern

Die grundlegende Kartenkomponente lässt sich mit zusätzlichen Attributen für Kartenoptionen, Event-Listener und Stile erweitern, die auf das „div“-Element mit der Karte angewendet werden. Mit dem folgenden Code wird die erweiterte Oberfläche dieser funktionalen Komponente präsentiert.

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
}) => {}

Das style-Objekt kann direkt übergeben und als Attribut für das gerenderte div-Objekt festgelegt werden.

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

Für onClick, onIdle und google.maps.MapOptions sind useEffect-Hooks erforderlich, um das Anwenden von Updates auf google.maps.Map zu erzwingen.

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]);

Für die Event-Listener ist etwas komplexerer Code erforderlich, wenn vorhandene Listener gelöscht werden sollen, sobald ein als Attribut übergebener Handler aktualisiert wurde.

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]);

Markierungskomponente erstellen

Die Markierungskomponente verwendet ähnliche Muster wie die Kartenkomponente mit den Hooks useEffect und 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;
};

Die Komponente gibt null zurück, da die DOM-Änderungen von google.maps.Map verwaltet werden.

Markierungen als untergeordnete Komponente der Karte hinzufügen

Zum Hinzufügen der Markierungen zu einer Karte wird die Marker-Komponente mithilfe des speziellen children-Attributs wie unten gezeigt an die Map-Komponente übergeben.

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

Eine kleine Änderung an der Ausgabe der Map-Komponente ist erforderlich, damit das Objekt google.maps.Map als zusätzliches Attribut an alle untergeordneten Elemente übergeben werden kann.

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 });
      }
    })}
  </>
);

Karten- und Anwendungsstatus verknüpfen

Wenn das Muster oben für onClick- und onIdle-Callbacks verwendet wird, kann die Anwendung so erweitert werden, dass Nutzeraktionen wie das Klicken oder Schwenken der Karte vollständig eingebunden sind.

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());
};

Diese Hooks können mit den folgenden Mustern in die Formularelemente eingebunden werden (wie anhand der Breitengradeingabe gezeigt).

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

Die Anwendung kann schließlich Klicks erfassen und Markierungen an jeder Klickposition rendern.

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

Code ansehen

Wenn Sie sich den kompletten Beispielcode ansehen möchten, können Sie einen der folgenden Code-Playgrounds nutzen oder das Git-Repository klonen.

Testbeispiel

Klonbeispiel

Git und Node.js sind erforderlich, um dieses Beispiel lokal auszuführen. Folgen Sie dieser Anleitung, um Node.js und NPM zu installieren. Mit den folgenden Befehlen werden der Klonvorgang durchgeführt, Abhängigkeiten installiert und die Beispielanwendung gestartet.

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

Sie können andere Beispiele ausprobieren, indem Sie zu einem beliebigen Zweig wechseln, der mit sample-SAMPLE_NAME beginnt.

  git checkout sample-SAMPLE_NAME
  npm i
  npm start