Обзор
В этом руководстве рассказывается, как добавить карту и маркер в приложение на React с помощью @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} />
Как расширить компонент карты с помощью дополнительных свойств
Базовый компонент карты можно расширить с помощью свойств для параметров карты, прослушивателей событий и стилей, примененных к элементу 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]);
В прослушивателях событий нужно использовать немного более сложный код, чтобы очистить существующие прослушиватели при передаче обработчика, поскольку свойство было обновлено.
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, поскольку для обработки DOM используется google.maps.Map
.
Как добавить маркеры в виде дочернего компонента карты
Чтобы добавить маркеры к карте, компонент Marker
будет передан компоненту Map
с помощью специального свойства children
, как показано ниже.
<Wrapper apiKey={"YOUR_API_KEY"}>
<Map center={center} zoom={zoom}>
<Marker position={position} />
</Map>
</Wrapper>
Чтобы передавать объект google.maps.Map
всем дочерним элементам в виде дополнительного свойства, нужно немного изменить выходные данные компонента 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) })
}
/>
Наконец, приложение может отслеживать нажатия и отрисовывать маркеры в каждом месте, где было выполнено нажатие.
{clicks.map((latLng, i) => (<Marker key={i} position={latLng} />))}
Изучение кода
Чтобы изучить полный пример кода, можно использовать указанные ниже площадки для изучения кода в интернете или клонировать репозиторий Git.
Примеры кода
Как клонировать пример
Для запуска примера в локальной среде необходимы Git и Node.js. Чтобы установить Node.js и NPM, следуйте этим инструкциям. Следующие команды используются, чтобы клонировать пример приложения, установить зависимости для него, а затем запустить его.
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