В приложениях на основе Mapbox GL JS ключевая проблема масштабирования заключается в том, чтобы избежать «монолитного» файла конфигурации карты. Переиспользуемые компоненты позволяют разносить логику по независимым модулям: источники данных, слои, стили, контролы, поведение и утилиты.
Базовый принцип заключается в том, что каждый элемент карты
описывается как функция или класс, принимающий map и
возвращающий объект с методами управления жизненным циклом:
export function createTrafficLayer(map) {
const id = 'traffic-layer';
function add() {
if (map.getLayer(id)) return;
map.addLayer({
id,
type: 'line',
source: 'traffic-source',
paint: {
'line-color': '#ff0000',
'line-width': 2
}
});
}
function remove() {
if (map.getLayer(id)) {
map.removeLayer(id);
}
}
return { add, remove, id };
}
Такой подход позволяет использовать один и тот же компонент на разных картах без дублирования логики.
Источники данных (sources) в Mapbox GL JS часто становятся точкой повторного использования, особенно при работе с GeoJSON или векторными тайлами.
Инкапсуляция источника позволяет стандартизировать структуру данных и обновления:
export function createGeoJsonSource(map, id, data) {
function add() {
if (map.getSource(id)) return;
map.addSource(id, {
type: 'geojson',
data
});
}
function update(newData) {
const source = map.getSource(id);
if (source) {
source.setData(newData);
}
}
return { add, update, id };
}
Такой компонент скрывает детали работы с map.getSource и
делает API предсказуемым.
Слои в Mapbox GL JS обладают высокой степенью конфигурируемости, что делает их идеальными кандидатами для композиции.
Принцип построения повторно используемого слоя:
line,
circle, fill)export function createLineLayer({
id,
source,
color = '#000',
width = 2
}) {
return function attach(map) {
if (map.getLayer(id)) return;
map.addLayer({
id,
type: 'line',
source,
paint: {
'line-color': color,
'line-width': width
}
});
return {
remove() {
if (map.getLayer(id)) {
map.removeLayer(id);
}
}
};
};
}
Такой слой становится фабрикой, которую можно конфигурировать под разные сценарии отображения.
Контролы в Mapbox GL JS реализуются через интерфейс
IControl, что делает их естественно переиспользуемыми
компонентами.
Паттерн заключается в создании класса с методами onAdd и
onRemove:
export class CompassControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl compass-control';
this.button = document.createElement('button');
this.button.textContent = 'N';
this.button.oncl ick = () => {
map.resetNorth();
};
this.container.appendChild(this.button);
return this.container;
}
onRemove() {
this.container.parentNode.removeChild(this.container);
this.map = undefined;
}
}
Контрол становится независимым компонентом UI, который можно подключать к любой карте.
Фабрики позволяют объединять несколько слоёв и источников в один логический модуль. Это особенно полезно для тематических карт (например, транспорт, аналитика, тепловые карты).
export function createUrbanLayerBundle(map) {
const roads = createLineLayer({
id: 'roads',
source: 'roads-source',
color: '#888',
width: 1
});
const highways = createLineLayer({
id: 'highways',
source: 'roads-source',
color: '#ff9900',
width: 3
});
function add() {
roads(map).remove?.();
highways(map).remove?.();
}
return { add };
}
Такой подход превращает набор слоёв в единый переиспользуемый модуль.
Поведение карты (например, зум к объекту, фильтрация, подсветка) лучше выносить в отдельные функции, не зависящие от UI.
export function fitToFeature(map, feature) {
const coordinates = feature.geometry.coordinates;
const bounds = coordinates.reduce(
(b, coord) => b.extend(coord),
new mapboxgl.LngLatBounds(coordinates[0], coordinates[0])
);
map.fitBounds(bounds, {
padding: 40
});
}
Такие функции легко переиспользуются между разными компонентами интерфейса.
Переиспользуемые компоненты особенно эффективны при декларативной сборке карты, где конфигурация описывается как набор независимых модулей:
export function initMap(map) {
const sources = [
createGeoJsonSource(map, 'cities', citiesData),
createGeoJsonSource(map, 'roads', roadsData)
];
const layers = [
createLineLayer({
id: 'roads-layer',
source: 'roads',
color: '#555'
})
];
sources.forEach(s => s.add());
layers.forEach(l => l(map).remove?.());
return { sources, layers };
}
Такой стиль упрощает масштабирование проекта и повторное использование конфигураций.
Визуальные паттерны (heatmap, clustering, choropleth) удобно оформлять как готовые компоненты, где меняются только данные и минимальные параметры.
Пример кластеризации:
export function createClusterLayer(map, sourceId) {
if (!map.getSource(sourceId)) return;
map.addLayer({
id: 'clusters',
type: 'circle',
source: sourceId,
filter: ['has', 'point_count'],
paint: {
'circle-color': '#51bbd6',
'circle-radius': 18
}
});
}
Такой компонент можно использовать в любом проекте без адаптации логики.
Ключевой аспект переиспользования — управление жизненным циклом. Компоненты должны поддерживать явные стадии:
Пример универсального шаблона:
export function createComponent(factory) {
return function (map, options) {
const instance = factory(map, options);
return {
mount: instance.add,
update: instance.update,
unmount: instance.remove
};
};
}
Такой уровень абстракции позволяет строить библиотеку компонентов поверх Mapbox GL JS как полноценную UI-систему.