Dodawanie mapy i znaczników do aplikacji reakcyjnej

Omówienie

W tym samouczku pokazujemy, jak dodać mapę i znacznik do aplikacji React przy użyciu @googlemaps/react-wrapper i zintegrować mapę i znaczniki ze stanem aplikacji.

Zainstaluj @googlemaps/react-wrapper

Zainstaluj i użyj biblioteki @googlemaps/react-wrapper, aby dynamicznie wczytywać interfejs Maps JavaScript API podczas renderowania komponentu.

npm install @googlemaps/react-wrapper

Tę bibliotekę można importować i używać w przypadku następujących elementów:

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

Podstawowym zastosowaniem tego komponentu jest opakowywanie komponentów podrzędnych zależnych od interfejsu API JavaScript Map Google. Komponent Wrapper akceptuje również propozycję render do renderowania komponentów wczytywania lub obsługi błędów podczas wczytywania interfejsu Maps JavaScript API.

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

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

Dodaj komponent mapy

Podstawowym elementem służącym do renderowania mapy jest prawdopodobnie wykorzystanie useRef, useState i useEffect haków reakcji.

Początkowy komponent mapy będzie miał następujący podpis.

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

google.maps.Map wymaga parametru Element jako konstruktora, dlatego parametr useRef jest potrzebny do utrzymania obiektu zmiennego, który zostanie zachowany przez cały okres życia komponentu. Ten fragment kodu tworzy instancję mapy w obiekcie useResult w treści komponentu 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]);

Podany powyżej hak useEffect zostanie uruchomiony tylko wtedy, gdy zmieni się ref. Komponent Map zwraca teraz te wartości.

return <div ref={ref} />

Powiększ komponent mapy o dodatkowe rekwizyty

Podstawowy komponent mapy można rozszerzyć o dodatkowe rekwizyty opcji opcji, detektorów zdarzeń i stylów zastosowanych do elementu div zawierającego mapę. Poniżej znajduje się rozwinięty interfejs tego komponentu funkcjonalnego.

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

Obiekt style może zostać przekazany bezpośrednio i ustawiony jako wskazówka na renderowanym obiekcie div.

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

onClick, onIdle i google.maps.MapOptions wymagają punktów zaczepienia useEffect, aby zastosować aktualizacje do 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]);

Po zaktualizowaniu procedury obsługi rekwizytów detektory zdarzeń wymagają nieco bardziej złożonego kodu, by można było ich usunąć.

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

Tworzenie komponentu znacznika

Komponent Znaczniki korzysta z podobnych wzorców co komponent mapy z punktami zaczepieniami useEffect i 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;
};

Komponent zwraca wartość null, gdy google.maps.Map zarządza manipulacją DOM.

Dodawanie znaczników jako komponentu podrzędnego mapy

Aby dodać znaczniki do mapy, komponent Marker zostanie przekazany do komponentu Map za pomocą specjalnej właściwości children, jak w poniższym przykładzie.

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

Należy wprowadzić niewielką zmianę w danych wyjściowych komponentu Map, aby przekazać obiekt google.maps.Map do wszystkich elementów podrzędnych w ramach dodatkowej właściwości.

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

Mapa linku i stan aplikacji

Używając powyższego wzorca w wywołaniach zwrotnych onClick i onIdle, aplikację można rozszerzyć, aby w pełni zintegrować działania użytkownika, takie jak kliknięcie lub przesunięcie mapy.

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

Punkty te możesz integrować z elementami formularza, korzystając z podanego niżej wzoru, tak jak jest to podane w polu szerokości.

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

Na koniec aplikacja śledzi kliknięcia i renderuje znaczniki przy każdej lokalizacji kliknięcia. Ten kod korzysta ze zmiennej position do przekazywania wartości LatLng ze zdarzenia kliknięcia.

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

Przeglądaj kod

Pełny przykładowy kod można znaleźć w placach zabaw online poniżej lub sklonować repozytorium git.

Fragment

Klonuj próbkę

Do uruchomienia tego przykładu wymagane są Git i Node.js. Aby zainstalować Node.js i NPM, postępuj zgodnie z tymi instrukcjami. Poniższe polecenia sklonują, instalują zależności i uruchamiają przykładową aplikację.

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

Inne przykłady można wypróbować, przełączając się na dowolną gałąź zaczynającą się od sample-SAMPLE_NAME.

  git checkout sample-SAMPLE_NAME
  npm i
  npm start