Visão geral
Neste tutorial, mostramos como adicionar um mapa e um marcador a um aplicativo React usando @googlemaps/react-wrapper e como integrar esses recursos ao estado do aplicativo.
Instalar @googlemaps/react-wrapper
Instale e use a biblioteca @googlemaps/react-wrapper para carregar dinamicamente a API Maps JavaScript quando o componente for renderizado.
npm install @googlemaps/react-wrapper
Essa biblioteca pode ser importada e usada com o código a seguir.
import { Wrapper, Status } from "@googlemaps/react-wrapper";
O uso básico desse componente é unir os componentes filhos que dependem da API Maps JavaScript. O componente Wrapper
também aceita uma propriedade render
para renderizar componentes de carregamento ou processar erros no carregamento da API Maps JavaScript.
const render = (status: Status) => {
return <h1>{status}</h1>;
};
<Wrapper apiKey={"YOUR_API_KEY"} render={render}>
<YourComponent/>
</Wrapper>
Adicionar um componente de mapa
Um componente funcional básico para renderizar um mapa provavelmente vai usar os hooks useRef
, useState
e useEffect
do React.
O componente inicial do mapa terá a seguinte assinatura.
const Map: React.FC<{}> = () => {};
Como google.maps.Map
requer um Element
como parâmetro construtor, useRef é necessário para manter um objeto mutável que vai persistir durante o ciclo de vida do componente. O snippet a seguir instancia um mapa no hook useEffect no corpo do 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]);
O hook useEffect
acima só será executado quando ref
mudar. O componente Map
agora retorna o seguinte.
return <div ref={ref} />
Estender componente do mapa com mais propriedades
O componente básico do mapa pode ser estendido com outras propriedades para opções de mapas, listeners de eventos e estilos aplicados à div que contém o mapa. O código a seguir mostra a interface expandida desse componente funcional.
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
}) => {}
O objeto style
pode ser transmitido diretamente e definido como uma propriedade no div
renderizado.
return <div ref={ref} style={style} />;
onClick
, onIdle
e google.maps.MapOptions
exigem que os hooks useEffect
apliquem atualizações de forma imperativa 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]);
Os listeners de eventos exigem um código um pouco mais complexo para apagar os listeners quando um gerenciador transmitido como uma propriedade é atualizado.
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]);
Criar um componente de marcador
O componente de marcador usa padrões semelhantes ao do mapa com hooks 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; };
O componente retorna nulo, porque google.maps.Map
gerencia a manipulação do DOM.
Adicionar marcadores como um componente filho do mapa
Para adicionar os marcadores a um mapa, o componente Marker
vai ser transmitido para o componente Map
usando a propriedade children
especial, como no exemplo a seguir.
<Wrapper apiKey={"YOUR_API_KEY"}>
<Map center={center} zoom={zoom}>
<Marker position={position} />
</Map>
</Wrapper>
Uma pequena mudança na saída do componente Map
precisa ser feita para transmitir o objeto google.maps.Map
a todos os filhos como uma propriedade extra.
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 de link e estado do aplicativo
Usando o padrão acima para chamadas de retorno de onClick
e onIdle
, o aplicativo pode ser estendido para integrar totalmente as ações do usuário, como clicar ou movimentar o mapa.
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()); };
Esses hooks podem ser integrados aos elementos do formulário usando o seguinte padrão, como demonstrado com a entrada de latitude.
<label htmlFor="lat">Latitude</label>
<input
type="number"
id="lat"
name="lat"
value={center.lat}
onChange={(event) =>
setCenter({ ...center, lat: Number(event.target.value) })
}
/>
Por fim, o aplicativo pode rastrear cliques e marcadores de renderização em cada local do clique.
{clicks.map((latLng, i) => (<Marker key={i} position={latLng} />))}
Explorar código
Analise o exemplo de código completo nos playgrounds de códigos on-line abaixo ou clonando o repositório git.
Testar amostra
Clonar amostra
O git e o Node.js são necessários para executar a amostra localmente. Siga estas instruções para instalar o Node.js e o NPM. Os comandos a seguir clonam, instalam dependências e iniciam o aplicativo de amostra.
git clone -b sample-react-map https://github.com/googlemaps/js-samples.git
cd js-samples
npm i
npm start
Para testar outras amostras, alterne para qualquer ramificação que comece com sample-SAMPLE_NAME
.
git checkout sample-SAMPLE_NAME
npm i
npm start