В архитектуре Mapbox GL JS пользовательские элементы управления
(controls) интегрируются в карту через единый контракт жизненного цикла.
Этот контракт определяет два ключевых метода: onAdd(map) и
onRemove(map). Они обеспечивают предсказуемую и управляемую
интеграцию DOM-элементов, событий и логики управления состоянием.
Контрол в Mapbox GL JS представляет собой объект, который реализует интерфейс:
onAdd(map) — создание и добавление элемента в DOM
картыonRemove(map) — удаление элемента и очистка
ресурсовДополнительно часто присутствует метод
getDefaultPosition, но именно пара
onAdd/onRemove формирует основу жизненного цикла.
Метод onAdd(map) вызывается автоматически при добавлении
контрола в карту через map.addControl(control).
Основная задача метода — создать DOM-узел, связать его с экземпляром карты и подготовить внутреннюю логику.
Сигнатура:
onAdd(map: Map): HTMLElement
При вызове метода обычно выполняются следующие действия:
class CustomControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl custom-control';
this.button = document.createElement('button');
this.button.textContent = 'Reset';
this.container.appendChild(this.button);
this.onCl ick = this.onClick.bind(this);
this.button.addEventListener('click', this.onClick);
return this.container;
}
onClick() {
this.map.resetNorthPitch();
}
}
Создание DOM
Контрол обязан создавать собственный контейнер. Mapbox GL JS не предоставляет готового DOM-узла, поэтому разработчик полностью контролирует структуру.
Сохранение ссылки на карту
this.map = map;
Это критично для дальнейшего взаимодействия с API карты: изменение центра, масштаба, подписка на события.
Подписка на события
Любая логика взаимодействия должна быть привязана внутри
onAdd, чтобы гарантировать, что контрол полностью готов к
работе сразу после добавления.
this.map.on('move', this.onMapMove);
Именование и изоляция
Контрол должен избегать конфликтов с другими элементами интерфейса, используя уникальные классы или namespace.
Метод onRemove(map) вызывается при удалении контрола
через map.removeControl(control).
Сигнатура:
onRemove(map: Map): void
class CustomControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl custom-control';
this.onM ove = this.onMove.bind(this);
this.map.on('move', this.onMove);
return this.container;
}
onRemove() {
this.map.off('move', this.onMove);
this.container.remove();
this.map = undefined;
}
onMove() {
console.log('map moved');
}
}
В долгоживущих SPA-приложениях контролы могут добавляться и удаляться
многократно. Неправильная реализация onRemove приводит к
типичным проблемам:
onRemove() {
// ошибка: обработчик не удалён
this.container.remove();
}
В этом случае подписка на события остаётся активной:
this.map.on('move', this.handler);
Даже после удаления контрола обработчик продолжит вызываться.
onAdd и onRemove образуют симметричную
пару, которая определяет жизненный цикл компонента:
onAdd — установка всех связейonRemove — полное их разрушениеКонтролы в Mapbox GL JS проектируются как изолированные модули, где вся побочная активность должна быть привязана к этим двум точкам.
class StructuredControl {
onAdd(map) {
this.map = map;
this._createUI();
this._bindEvents();
return this.container;
}
onRemove() {
this._unbindEvents();
this._destroyUI();
this.map = undefined;
}
}
Такое разделение улучшает читаемость и облегчает сопровождение.
Контролы могут включать асинхронные операции: загрузку данных, таймеры, WebSocket-подключения.
onAdd(map) {
this.map = map;
this.interval = setInterval(() => {
console.log('tick');
}, 1000);
return document.createElement('div');
}
onRemove() {
clearInterval(this.interval);
}
onAdd(map) {
this.controller = new AbortController();
fetch('/data', { signal: this.controller.signal })
.then(r => r.json())
.then(data => this.handleData(data));
return document.createElement('div');
}
onRemove() {
this.controller.abort();
}
В onAdd часто возникает необходимость привязки методов к
экземпляру контролла.
this.handler = this.handler.bind(this);
Альтернативный подход — использование стрелочных функций:
this.handler = () => {
console.log(this.map);
};
Корректная привязка важна, поскольку Mapbox GL JS вызывает методы
контрола в собственном контексте только для onAdd и
onRemove, остальные методы выполняются в контексте
объекта.
Контролы часто взаимодействуют с состоянием карты:
onAdd(map) {
this.map = map;
this.button = document.createElement('button');
this.button.oncl ick = () => {
const visibility = this.map.getLayoutProperty('roads', 'visibility');
this.map.setLayoutProperty(
'roads',
'visibility',
visibility === 'visible' ? 'none' : 'visible'
);
};
return this.button;
}
В этом случае onRemove должен учитывать возможные
изменения состояния карты, если контрол влияет на глобальную
конфигурацию.
Экземпляр контрола может быть добавлен и удалён несколько раз в течение жизненного цикла карты или приложения.
Корректная реализация:
onAddmap.addControl(control);
map.removeControl(control);
map.addControl(control);
Повторное добавление должно работать без побочных эффектов.
| Этап | onAdd | onRemove |
|---|---|---|
| DOM | создание | удаление |
| События | подписка | отписка |
| Состояние | инициализация | очистка |
| Карта | сохранение ссылки | освобождение ссылки |
| Асинхронность | запуск | остановка |
Чёткое соблюдение этого разделения является базовым требованием корректной интеграции с Mapbox GL JS.