Организация кода в приложениях на Mapbox GL JS напрямую влияет на масштабируемость, производительность и поддерживаемость. Даже при небольшом объёме логики карта быстро превращается в центральный компонент приложения, вокруг которого выстраиваются источники данных, слои, взаимодействия и бизнес-логика.
Типовая ошибка — размещение всей логики инициализации карты в одном файле. Такой подход допустим только для демонстрационных примеров. В реальных проектах карта становится частью архитектуры, где ответственность распределяется по слоям: инициализация, конфигурация, работа с данными, UI-взаимодействия, управление состоянием.
Код приложения на Mapbox GL JS обычно делится на несколько логических зон:
Каждая зона должна быть изолирована, чтобы изменение одного слоя логики не приводило к каскадным правкам в других частях системы.
Создание экземпляра карты не должно смешиваться с добавлением слоёв и источников. Базовая инициализация выносится в отдельный модуль.
import mapboxgl from "mapbox-gl";
export function createMap(containerId) {
return new mapboxgl.Map({
container: containerId,
style: "mapbox://styles/mapbox/streets-v12",
center: [37.6173, 55.7558],
zoom: 10
});
}
Такой подход позволяет:
Жёстко зашитые параметры ухудшают сопровождаемость. Все параметры карты, стили и ключи доступа выносятся в конфигурационные модули.
export const MAP_CONFIG = {
style: "mapbox://styles/mapbox/light-v11",
center: [37.6173, 55.7558],
zoom: 9
};
export const API_CONFIG = {
token: "YOUR_MAPBOX_TOKEN"
};
Дальнейшее расширение системы становится проще, поскольку логика не зависит от конкретных значений.
Источники данных в Mapbox GL JS являются фундаментом всей визуализации. Их добавление должно быть централизованным.
export function addCitySource(map) {
map.addSource("cities", {
type: "geojson",
data: "/data/cities.geojson"
});
}
В более сложных приложениях источники группируются:
/sources
geojsonSources.js
vectorSources.js
rasterSources.js
Такое разделение снижает когнитивную нагрузку и упрощает поиск нужного источника.
Слои представляют визуальную интерпретацию данных и должны быть отделены от источников.
export function addCityLayer(map) {
map.addLayer({
id: "city-points",
type: "circle",
source: "cities",
paint: {
"circle-radius": 6,
"circle-color": "#3b82f6"
}
});
}
Практика разделения слоёв по файлам:
/layers
cityLayers.js
heatmapLayers.js
routeLayers.js
Такой подход позволяет:
Инициализация карты должна быть точкой сборки, а не местом хранения логики.
import { createMap } from "./map/createMap";
import { addCitySource } from "./sources/cities";
import { addCityLayer } from "./layers/cityLayers";
export function initApp() {
const map = createMap("map");
map.on("load", () => {
addCitySource(map);
addCityLayer(map);
});
return map;
}
Такой подход обеспечивает:
Критическая ошибка — смешивание вычислений и визуализации. Бизнес-логика должна быть независимой от Mapbox GL JS.
Пример неправильного подхода:
map.setFilter("cities", ["==", ["get", "population"], 1000]);
Вместо этого фильтры должны формироваться в отдельном модуле:
export function getPopulationFilter(min) {
return ["all", [">=", ["get", "population"], min]];
}
И применяться уже в слое взаимодействия:
import { getPopulationFilter } from "./filters/population";
map.setFilter("cities", getPopulationFilter(1000));
При росте приложения появляется необходимость централизованного состояния: выбранные объекты, активные фильтры, текущие режимы отображения.
Простейшая модель:
export const state = {
selectedCity: null,
filters: {
populationMin: 0
}
};
Обновление состояния отделяется от Mapbox-операций:
export function setSelectedCity(city) {
state.selectedCity = city;
}
Реакция Mapbox-кода на состояние:
import { state } from "./state";
export function updateCityFilter(map) {
map.setFilter(
"cities",
[">=", ["get", "population"], state.filters.populationMin]
);
}
События Mapbox GL JS должны обрабатываться в отдельном модуле, а не в инициализации карты.
export function bindMapEvents(map) {
map.on("click", "city-points", (e) => {
const feature = e.features[0];
console.log(feature.properties.name);
});
map.on("mouseenter", "city-points", () => {
map.getCanvas().style.cursor = "pointer";
});
map.on("mouseleave", "city-points", () => {
map.getCanvas().style.cursor = "";
});
}
Такой подход позволяет:
Типовая структура проекта:
/src
/map
createMap.js
config.js
/sources
cities.js
routes.js
/layers
cityLayers.js
routeLayers.js
/events
mapEvents.js
uiEvents.js
/state
index.js
/filters
population.js
/utils
geo.js
initApp.js
Такая структура отражает архитектурное разделение ответственности, а не техническую группировку файлов.
Источники данных часто загружаются динамически. Логика загрузки не должна быть связана с визуализацией.
export async function loadCities() {
const response = await fetch("/api/cities");
return response.json();
}
Подключение к карте отделяется:
import { loadCities } from "./data/loadCities";
export async function addDynamicCities(map) {
const data = await loadCities();
map.addSource("cities", {
type: "geojson",
data
});
}
С ростом приложения появляются повторяющиеся операции:
Эти операции выносятся в утилиты:
export function addGeoJsonSource(map, id, data) {
map.addSource(id, {
type: "geojson",
data
});
}
export function addCircleLayer(map, id, source, paint) {
map.addLayer({
id,
type: "circle",
source,
paint
});
}
При увеличении сложности приложения Mapbox GL JS часто становится только визуальным рендерером, а вся логика уходит в отдельные слои:
Такое разделение снижает связанность компонентов и упрощает тестирование.
При использовании TypeScript структура кода становится более предсказуемой.
export interface City {
id: string;
name: string;
population: number;
coordinates: [number, number];
}
Типизация источников:
import { City } from "../types";
export function createCitySource(data: City[]) {
return {
type: "geojson",
data
};
}
Код, связанный с Mapbox GL JS, должен быть максимально локализован. Остальная часть системы не должна знать о:
Это достигается через адаптеры:
export function setCitiesVisibility(map, visible) {
map.setLayoutProperty(
"city-points",
"visibility",
visible ? "visible" : "none"
);
}
Такой слой абстракции защищает архитектуру от привязки к конкретной библиотеке Mapbox GL JS и упрощает потенциальную миграцию.
Экосистема тесно связана с платформой Mapbox, предоставляющей стили, тайлы и API. Это накладывает дополнительные требования к структуре кода:
Такое разделение снижает зависимость приложения от внешней инфраструктуры и упрощает поддержку в долгосрочной перспективе