Работа с Google Maps JavaScript API в связке с React требует учёта особенностей жизненного цикла компонентов, асинхронной загрузки внешних скриптов и управления императивным API внутри декларативной модели UI.
Основная сложность интеграции заключается в том, что Google Maps API построен на императивном подходе: карта создаётся как объект, которому напрямую передаются DOM-узлы и конфигурации, тогда как React оперирует виртуальным DOM и пересоздаёт UI через рендер-функции. Это приводит к необходимости изоляции карты от повторных рендеров и строгого контроля инициализации.
Подключение через <script> в
index.html допустимо, но в современных приложениях чаще
используется динамическая загрузка. Это позволяет контролировать момент
инициализации и избежать блокировки рендера.
Распространённый подход — использование загрузчика
@googlemaps/js-api-loader:
npm install @googlemaps/js-api-loader
Далее создаётся модуль загрузки:
import { Loader } from "@googlemaps/js-api-loader";
export const loader = new Loader({
apiKey: "YOUR_API_KEY",
version: "weekly",
libraries: ["places"]
});
Ключевой момент: загрузка API должна происходить один раз на уровне приложения или кэширующего слоя, иначе возможны дублирующиеся запросы и гонки инициализации.
Базовый паттерн строится вокруг useRef, чтобы сохранить
ссылку на DOM-узел и экземпляр карты.
import { useEffect, useRef } from "react";
import { loader } from "./googleMapsLoader";
function Map() {
const mapRef = useRef(null);
const mapInstance = useRef(null);
useEffect(() => {
let isMounted = true;
loader.load().then(() => {
if (!isMounted || !mapRef.current) return;
mapInstance.current = new google.maps.Map(mapRef.current, {
center: { lat: 40.7128, lng: -74.0060 },
zoom: 10
});
});
return () => {
isMounted = false;
};
}, []);
return <div ref={mapRef} style={{ width: "100%", height: "400px" }} />;
}
useRef используется вместо state, чтобы избежать лишних
ререндеровisMounted предотвращает утечки при
размонтировании компонентаКарты Google не уничтожаются автоматически при размонтировании DOM-узла. Это требует явной очистки.
Хотя API не предоставляет прямого destroy, можно:
useEffect(() => {
return () => {
if (mapInstance.current) {
google.maps.event.clearInstanceListeners(mapInstance.current);
mapInstance.current = null;
}
};
}, []);
Особенно важно это при частом монтировании компонентов, например в SPA с маршрутизацией.
Маркер — отдельный императивный объект, который также не должен пересоздаваться при каждом рендере.
const markerRef = useRef(null);
useEffect(() => {
if (!mapInstance.current) return;
markerRef.current = new google.maps.Marker({
position: { lat: 40.7128, lng: -74.0060 },
map: mapInstance.current,
title: "Marker"
});
return () => {
if (markerRef.current) {
markerRef.current.setMap(null);
markerRef.current = null;
}
};
}, []);
В React важно отделять данные от визуальных объектов. Например, изменение центра карты не должно приводить к пересозданию карты.
function updateCenter(map, lat, lng) {
map.setCenter({ lat, lng });
}
Использование state:
const [center, setCenter] = useState({ lat: 0, lng: 0 });
useEffect(() => {
if (mapInstance.current) {
mapInstance.current.setCenter(center);
}
}, [center]);
Таким образом React управляет только данными, а API — визуальным состоянием.
Для масштабируемых проектов выделяется кастомный хук.
import { useEffect, useRef } from "react";
import { loader } from "./googleMapsLoader";
export function useGoogleMap(options) {
const ref = useRef(null);
const mapRef = useRef(null);
useEffect(() => {
let mounted = true;
loader.load().then(() => {
if (!mounted || !ref.current) return;
mapRef.current = new google.maps.Map(ref.current, options);
});
return () => {
mounted = false;
};
}, []);
return { ref, map: mapRef };
}
Использование:
function MapView() {
const { ref, map } = useGoogleMap({
center: { lat: 51.5074, lng: -0.1278 },
zoom: 12
});
return <div ref={ref} style={{ height: 500 }} />;
}
Google Maps использует собственную систему событий.
useEffect(() => {
if (!mapInstance.current) return;
const listener = google.maps.event.addListener(
mapInstance.current,
"click",
(e) => {
console.log(e.latLng.lat(), e.latLng.lng());
}
);
return () => {
google.maps.event.removeListener(listener);
};
}, []);
Важно:
Основная ошибка — создание карты внутри render-функции или зависимостей эффекта:
// неправильный подход
const map = new google.maps.Map(...)
Это приводит к:
Правильная модель:
useRefПри использовании TypeScript важно учитывать глобальные типы
google.
npm install --save-dev @types/google.maps
Пример:
const mapRef = useRef<google.maps.Map | null>(null);
Это позволяет строго контролировать API и предотвращает обращения к
null.
Частый сценарий — загрузка точек с сервера.
useEffect(() => {
if (!mapInstance.current) return;
fetch("/api/points")
.then(res => res.json())
.then(points => {
points.forEach(p => {
new google.maps.Marker({
position: p,
map: mapInstance.current
});
});
});
}, []);
Проблема здесь — повторное добавление маркеров при обновлении данных. Решение:
refПри большом количестве объектов на карте:
MarkerClusterer)setStateuseMemoconst mapOptions = useMemo(() => ({
center,
zoom: 10
}), [center]);
В средах с серверным рендерингом window недоступен,
поэтому инициализация должна происходить только на клиенте.
useEffect(() => {
if (typeof window === "undefined") return;
loader.load().then(initMap);
}, []);
Также часто требуется динамический импорт:
import dynamic from "next/dynamic";
const Map = dynamic(() => import("./Map"), {
ssr: false
});
В крупных приложениях логично разделять уровни:
Это предотвращает смешение императивного API и декларативной модели интерфейса.
При наличии нескольких экземпляров карт:
refconst maps = useRef(new Map());
Custom overlays создаются через OverlayView:
class CustomOverlay extends google.maps.OverlayView {
onAdd() {}
draw() {}
onRemove() {}
}
В React важно создавать такие объекты вне рендера и контролировать их
жизненный цикл через useEffect.
window.google без проверки
загрузкиРабота с Google Maps API в React требует строгого разделения ответственности между декларативной моделью интерфейса и императивной моделью визуализации карты, где React управляет только структурой и данными, а API отвечает за отрисовку и взаимодействие с географическими объектами.