Aggiunta di una mappa e di indicatori a un'applicazione reattiva

Panoramica

Questo tutorial mostra come aggiungere una mappa e un indicatore a un'applicazione React utilizzando @googlemaps/react-wrapper e integrare la mappa e gli indicatori nello stato dell'applicazione.

Installa @googlemaps/react-wrapper

Installa e utilizza la libreria @googlemaps/react-wrapper per caricare dinamicamente l'API Maps JavaScript quando viene eseguito il rendering del componente.

npm install @googlemaps/react-wrapper

La libreria può essere importata e utilizzata con quanto segue.

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

L'utilizzo di base di questo componente consiste nel wrapping dei componenti secondari che dipendono dall'API JavaScript di Maps. Il componente Wrapper accetta anche una proposta di render per il rendering dei componenti o per la gestione degli errori di caricamento dell'API Maps JavaScript.

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

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

Aggiungere un componente della mappa

È probabile che un componente funzionale di base per eseguire il rendering di una mappa utilizzi gli hook di reazione useRef, useState e useEffect.

Il componente mappa iniziale avrà la seguente firma.

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

Poiché google.maps.Map richiede un Element come parametro costruttore, è necessario useRef per mantenere un oggetto modificabile che persisterà per tutta la durata del componente. Il seguente snippet crea un'istanza di una mappa all'interno del hook useEffetto nel corpo del componente 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]);

Il hook useEffect precedente verrà eseguito solo quando ref è cambiato. Il componente Map restituisce ora il seguente.

return <div ref={ref} />

Estendi il componente mappa con altri oggetti

Il componente mappa di base può essere ampliato con ulteriori oggetti di scena per le opzioni della mappa, i listener di eventi e gli stili applicati al div contenente la mappa. Il codice seguente mostra l'interfaccia espansa di questo componente funzionale.

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

L'oggetto style può essere passato direttamente e impostato come un oggetto di scena sul valore di div visualizzato.

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

onClick, onIdle e google.maps.MapOptions richiedono useEffect hook per applicare imperativamente gli aggiornamenti a 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]);

I listener di eventi richiedono un codice leggermente più complesso per cancellare i listener esistenti quando viene gestito un gestore che è stato aggiornato.

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

Creare un componente dell'indicatore

Il componente indicatore utilizza pattern simili a quelli del componente mappa con hook useEffect e 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;
};

Il componente restituisce null poiché google.maps.Map gestisce la manipolazione del DOM.

Aggiungere indicatori come componente secondario della mappa

Per aggiungere gli indicatori a una mappa, il componente Marker viene passato al componente Map utilizzando l'elemento speciale children speciale, come nell'esempio seguente.

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

È necessario apportare una piccola modifica all'output del componente Map per passare l'oggetto google.maps.Map a tutti i componenti secondari come elemento di scena aggiuntivo.

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

Collega mappa e stato dell'applicazione

Utilizzando il pattern riportato sopra per i callback onClick e onIdle, l'applicazione può essere estesa per integrare completamente le azioni degli utenti, come fare clic sulla mappa o eseguire una panoramica.

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

Questi ganci possono essere integrati negli elementi del modulo utilizzando il seguente schema, come dimostrato dall'input della latitudine.

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

Infine, l'applicazione monitora i clic ed esegue il rendering degli indicatori in ogni posizione di clic. Il seguente codice utilizza la variabile position per trasmettere il valore LatLng dall'evento di clic.

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

Esplora il codice

Il codice campione completo può essere esplorato attraverso le aree giochi online di seguito o clonando il repository Git.

Prova anteprima

Clona un campione

Sono richiesti Git e Node.js per eseguire questo esempio in locale. Per installare Node.js e NPM, segui queste istruzioni. I seguenti comandi clonano, installano le dipendenze e avviano l'applicazione di esempio.

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

È possibile provare altri esempi passando a qualsiasi ramo che inizia con sample-SAMPLE_NAME.

  git checkout sample-SAMPLE_NAME
  npm i
  npm start