Жизненный цикл в Mapbox GL JS представляет собой последовательность состояний и событий, через которые проходит объект карты с момента создания до полного уничтожения. Грамотное управление жизненным циклом позволяет:
Основным объектом жизненного цикла выступает экземпляр класса
mapboxgl.Map.
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [37.6176, 55.7558],
zoom: 10
});
После выполнения конструктора начинается процесс инициализации внутренних компонентов карты.
Типичный жизненный цикл карты включает несколько стадий:
Схематически процесс выглядит следующим образом:
Создание Map
↓
Загрузка Style
↓
Загрузка Sources
↓
Загрузка Tiles
↓
Первый Render
↓
Интерактивная работа
↓
Изменение данных
↓
Удаление карты
Инициализация начинается после вызова конструктора.
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v11'
});
В этот момент происходит:
Карта еще не готова для полноценной работы.
Например, следующий код может завершиться ошибкой:
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v11'
});
map.addLayer({
id: 'test',
type: 'circle',
source: 'points'
});
Причина заключается в том, что стиль еще не загружен.
Наиболее распространенная точка входа в жизненный цикл — событие
load.
map.on('load', () => {
console.log('Карта полностью загружена');
});
Событие возникает после:
После получения события становится безопасно:
Пример:
map.on('load', () => {
map.addSource('cities', {
type: 'geojson',
data: '/data/cities.geojson'
});
map.addLayer({
id: 'cities-layer',
type: 'circle',
source: 'cities'
});
});
Иногда требуется реагировать не на загрузку карты, а на загрузку стиля.
map.on('style.load', () => {
console.log('Стиль загружен');
});
Это особенно важно при динамической смене стилей.
map.setStyle(
'mapbox://styles/mapbox/satellite-streets-v12'
);
После выполнения setStyle() многие пользовательские
настройки удаляются:
Поэтому часто используется повторное создание ресурсов:
map.on('style.load', () => {
createCustomLayers();
});
Для контроля текущего состояния существуют специальные методы.
Проверяет загрузку стиля.
if (map.isStyleLoaded()) {
console.log('Стиль готов');
}
Проверяет готовность конкретного источника.
if (map.isSourceLoaded('cities')) {
console.log('Источник загружен');
}
Проверяет готовность карты к взаимодействию.
if (map.loaded()) {
console.log('Карта полностью готова');
}
Метод полезен при сложной асинхронной логике.
Mapbox GL JS предоставляет набор событий для отслеживания загрузки ресурсов.
Вызывается при изменении состояния источников.
map.on('sourcedata', (event) => {
console.log(event);
});
Применяется для:
Срабатывает при начале загрузки данных.
map.on('dataloading', () => {
console.log('Загрузка данных');
});
Возникает после получения данных.
map.on('data', () => {
console.log('Данные получены');
});
Сообщает об изменениях стиля.
map.on('styledata', () => {
console.log('Стиль обновлен');
});
Полезно при динамическом конфигурировании оформления.
Одним из наиболее полезных этапов жизненного цикла является состояние простоя.
map.on('idle', () => {
console.log('Все операции завершены');
});
Событие возникает, когда:
Это удобная точка для:
Пример:
map.on('idle', () => {
exportMapImage();
});
Mapbox GL JS использует непрерывный цикл отрисовки.
Каждый кадр включает:
Срабатывает при каждом кадре.
map.on('render', () => {
console.log('Кадр отрисован');
});
Использовать его следует осторожно.
Нежелательный пример:
map.on('render', () => {
expensiveCalculation();
});
Подобный код способен значительно снизить FPS.
Вызывается перед началом очередной отрисовки.
map.on('renderstart', () => {
console.log('Начало рендера');
});
Применяется редко, преимущественно для диагностики.
Пользовательские слои также имеют собственный жизненный цикл.
При использовании Custom Layer Interface доступны методы:
const customLayer = {
id: 'custom',
type: 'custom',
onAdd(map, gl) {
console.log('Слой создан');
},
render(gl, matrix) {
console.log('Отрисовка');
}
};
Вызывается один раз после подключения слоя.
onAdd(map, gl) {
this.program = createProgram(gl);
}
Обычно здесь создаются:
Вызывается на каждом кадре.
render(gl, matrix) {
gl.drawArrays(
gl.TRIANGLES,
0,
6
);
}
Здесь располагается код отрисовки.
При удалении пользовательского слоя желательно очищать созданные объекты.
Например:
gl.deleteBuffer(buffer);
gl.deleteTexture(texture);
gl.deleteProgram(program);
Иначе возможно накопление ресурсов GPU.
Смена стиля фактически запускает новую фазу жизненного цикла.
map.setStyle(
'mapbox://styles/mapbox/dark-v11'
);
В процессе:
Поэтому рекомендуется повторно регистрировать данные:
map.on('style.load', () => {
addGeoJsonSources();
addCustomLayers();
});
Источник проходит несколько этапов:
Создание
↓
Регистрация
↓
Загрузка данных
↓
Обработка
↓
Использование слоями
↓
Обновление
↓
Удаление
Создание:
map.addSource('roads', {
type: 'geojson',
data: roadsData
});
Обновление:
map.getSource('roads')
.setData(newData);
Удаление:
map.removeSource('roads');
Перед удалением необходимо удалить связанные слои.
map.removeLayer('roads-layer');
map.removeSource('roads');
Изображения используются в символических слоях.
Добавление:
map.loadImage('/marker.png', (error, image) => {
if (error) throw error;
map.addImage('marker', image);
});
Проверка:
map.hasImage('marker');
Удаление:
map.removeImage('marker');
При длительной работе приложения накопление ненужных изображений приводит к увеличению потребления памяти.
Во время жизненного цикла часто создается большое количество подписок.
Создание:
function handleMove() {
console.log('move');
}
map.on('move', handleMove);
Удаление:
map.off('move', handleMove);
Особенно важно для:
Накопление обработчиков может привести к многократному выполнению одного и того же кода.
Завершающий этап жизненного цикла — уничтожение экземпляра карты.
Для этого используется метод:
map.remove();
После вызова происходит:
После уничтожения объект становится недействительным.
Неверный вариант:
map.remove();
map.flyTo({
center: [0, 0]
});
Подобные вызовы приводят к ошибкам.
Наиболее распространенный сценарий — создание карты при монтировании компонента и удаление при размонтировании.
useEffect(() => {
const map = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/streets-v12'
});
return () => {
map.remove();
};
}, []);
Такой подход гарантирует:
Пример для Vue:
import { onMounted, onUnmounted } from 'vue';
let map;
onMounted(() => {
map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12'
});
});
onUnmounted(() => {
map.remove();
});
Карта существует только в период жизни компонента.
Неправильно:
const map = new mapboxgl.Map(config);
map.addLayer(layer);
Правильно:
map.on('load', () => {
map.addLayer(layer);
});
Неправильно:
componentWillUnmount() {
}
Правильно:
componentWillUnmount() {
map.remove();
}
Неправильно:
map.on('move', handler);
map.on('move', handler);
map.on('move', handler);
Правильно:
map.off('move', handler);
map.on('move', handler);
Неправильно:
map.removeSource('roads');
Правильно:
map.removeLayer('roads-layer');
map.removeSource('roads');
Для крупных приложений эффективной считается следующая последовательность:
Создание карты
↓
load
↓
Регистрация источников
↓
Регистрация слоев
↓
Подписка на события
↓
Работа пользователя
↓
Обновление данных
↓
Удаление событий
↓
Удаление слоев
↓
Удаление источников
↓
remove()
Такой подход обеспечивает предсказуемое поведение карты, стабильное использование памяти, корректную работу WebGL и надежное управление всеми объектами Mapbox GL JS на протяжении полного жизненного цикла приложения.