Wrapper-библиотека поверх API карт представляет собой слой
абстракции, скрывающий прямое взаимодействие с глобальными объектами
google.maps, упрощающий управление картой, маркерами,
сервисами геокодирования и событиями, а также стандартизирующий
интерфейс для приложения.
В основе wrapper-подхода лежит принцип изоляции внешнего SDK от бизнес-логики приложения. Это снижает связанность кода, упрощает тестирование и позволяет при необходимости заменить реализацию картографического провайдера без масштабных изменений в кодовой базе.
Нативный API Google Maps JavaScript API предоставляет широкий набор
классов: Map, Marker, InfoWindow,
LatLng, Geocoder,
DirectionsService. Однако прямое использование этих
объектов приводит к нескольким проблемам:
googleWrapper решает эти проблемы через единый интерфейс:
const map = new MapWrapper({
containerId: "map",
center: { lat: 40.7128, lng: -74.0060 },
zoom: 12
});
Типичная структура wrapper-слоя включает несколько модулей:
Прямой API требует работы с new google.maps.Map. Wrapper
скрывает этот вызов:
class MapWrapper {
constructor(options) {
this.options = options;
this.map = null;
}
async init() {
await this.loadAPI();
this.map = new google.maps.Map(
document.getElementById(this.options.containerId),
{
center: this.options.center,
zoom: this.options.zoom,
mapTypeId: "roadmap"
}
);
}
loadAPI() {
if (window.google && window.google.maps) return Promise.resolve();
return new Promise((resolve) => {
const script = document.createElement("script");
script.src = `https://maps.googleapis.com/maps/api/js?key=${this.options.apiKey}`;
script.onl oad = resolve;
document.head.appendChild(script);
});
}
}
Wrapper обеспечивает контроль загрузки SDK, предотвращая повторные подключения и гонки инициализации.
Работа с маркерами в нативном API часто разрознена: создание, обновление позиции, удаление и обработка событий выполняются разными вызовами.
Wrapper централизует управление:
class MarkerManager {
constructor(mapInstance) {
this.map = mapInstance;
this.markers = new Map();
}
add(id, position, options = {}) {
const marker = new google.maps.Marker({
position,
map: this.map,
...options
});
this.markers.set(id, marker);
return marker;
}
updatePosition(id, position) {
const marker = this.markers.get(id);
if (marker) marker.setPosition(position);
}
remove(id) {
const marker = this.markers.get(id);
if (marker) {
marker.setMap(null);
this.markers.delete(id);
}
}
clear() {
this.markers.forEach(m => m.setMap(null));
this.markers.clear();
}
}
Ключевая особенность wrapper-подхода — идентификация маркеров через
id, а не через ссылки на объекты API.
Google Maps JavaScript API использует собственную систему событий
через google.maps.event.addListener, что усложняет
интеграцию с архитектурой приложения.
Wrapper вводит единый EventBus:
class EventBus {
constructor() {
this.events = {};
}
on(event, handler) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(handler);
}
emit(event, payload) {
(this.events[event] || []).forEach(h => h(payload));
}
}
Интеграция с картой:
this.map.addListener("click", (e) => {
this.eventBus.emit("map:click", {
lat: e.latLng.lat(),
lng: e.latLng.lng()
});
});
Такой подход разрывает прямую зависимость от API событий Google и позволяет заменить источник событий без изменения логики приложения.
Geocoder в нативном API возвращает результаты через callback, что усложняет композицию логики. Wrapper преобразует его в Promise-based API:
class GeoService {
constructor() {
this.geocoder = new google.maps.Geocoder();
}
geocode(address) {
return new Promise((resolve, reject) => {
this.geocoder.geocode({ address }, (results, status) => {
if (status === "OK") resolve(results);
else reject(status);
});
});
}
reverseGeocode(lat, lng) {
return new Promise((resolve, reject) => {
this.geocoder.geocode({ location: { lat, lng } }, (results, status) => {
if (status === "OK") resolve(results);
else reject(status);
});
});
}
}
Это позволяет использовать async/await и выстраивать
линейную бизнес-логику.
Wrapper часто включает слой состояния, который синхронизирует:
class MapState {
constructor() {
this.state = {
center: null,
zoom: null,
selectedMarker: null
};
}
setCenter(center) {
this.state.center = center;
}
setZoom(zoom) {
this.state.zoom = zoom;
}
selectMarker(id) {
this.state.selectedMarker = id;
}
getState() {
return this.state;
}
}
Wrapper синхронизирует состояние с картой:
this.map.addListener("center_changed", () => {
this.state.setCenter(this.map.getCenter().toJSON());
});
В зрелых wrapper-архитектурах каждый сервис изолирован, но управляется единым фасадом:
class MapsFacade {
constructor(config) {
this.map = new MapWrapper(config);
this.markers = null;
this.geo = null;
this.events = new EventBus();
}
async init() {
await this.map.init();
this.markers = new MarkerManager(this.map.map);
this.geo = new GeoService();
}
}
Фасад скрывает сложность SDK и предоставляет единый интерфейс приложения.
Wrapper над Google Maps JavaScript API часто проектируется как расширяемая система плагинов:
class PluginSystem {
constructor() {
this.plugins = [];
}
use(plugin) {
this.plugins.push(plugin);
plugin.init(this);
}
}
Пример плагина:
const TrafficPlugin = {
init(wrapper) {
wrapper.map.map.setOptions({ trafficLayer: true });
}
};
Такой подход позволяет добавлять функциональность без изменения ядра wrapper-библиотеки.
Wrapper-слой централизует обработку ошибок SDK:
try {
await geo.geocode("New York");
} catch (e) {
eventBus.emit("geo:error", e);
}
Это предотвращает распространение ошибок по бизнес-логике приложения.
Ключевая ценность wrapper заключается в тестируемости. Вместо
мокирования глобального google.maps, тестируется
собственный интерфейс:
class MockGeoService {
geocode() {
return Promise.resolve([{ formatted_address: "Mock Address" }]);
}
}
Инъекция зависимостей позволяет изолировать тесты от внешнего API.
В TypeScript wrapper обычно описывается через строгие интерфейсы:
interface LatLng {
lat: number;
lng: number;
}
interface MapConfig {
containerId: string;
center: LatLng;
zoom: number;
apiKey: string;
}
Это снижает вероятность ошибок при работе с динамическим API JavaScript.
Дополнительный слой абстракции влияет на производительность минимально, однако позволяет оптимизировать:
function debounce(fn, delay) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), delay);
};
}
Wrapper над Google Maps JavaScript API формирует промежуточный слой между внешним SDK и внутренней архитектурой приложения, обеспечивая: