В экосистеме интерактивных карт Mapbox GL JS ключевую роль в архитектуре приложений играет разделение состояния (state) и реактивного обновления визуализации. Понятия stores и reactivity формируют слой абстракции между данными приложения и рендерингом карты, позволяя строить масштабируемые интерфейсы поверх WebGL-движка.
Store в контексте Mapbox GL JS не является встроенной сущностью библиотеки, но представляет собой архитектурный паттерн, применяемый для управления состоянием карты и связанных данных. Store выступает единым источником истины для:
Типовая структура store может быть описана как объект состояния:
const mapStore = {
center: [37.6173, 55.7558],
zoom: 10,
bearing: 0,
pitch: 0,
style: 'mapbox://styles/mapbox/streets-v12'
};
Такой подход позволяет отделить управление состоянием от непосредственных вызовов API карты.
Реактивность в Mapbox GL JS выражается через событийную модель и императивные методы обновления состояния визуализации. Изменение данных store должно приводить к синхронному или асинхронному обновлению карты без ручного пересоздания компонентов.
Ключевые механизмы реактивности:
move, zoom,
rotate, pitch)setData)setPaintProperty,
setLayoutProperty)setStyle)Каркас реактивного связывания:
map.on('move', () => {
mapStore.center = map.getCenter().toArray();
mapStore.zoom = map.getZoom();
});
В обратную сторону изменения store должны отражаться на карте:
function updateMapFromStore(map, store) {
map.setCenter(store.center);
map.setZoom(store.zoom);
map.setBearing(store.bearing);
map.setPitch(store.pitch);
}
Одним из наиболее важных элементов реактивной архитектуры являются источники данных (sources). GeoJSON-источники в Mapbox GL JS представляют собой mutable-структуры, обновляемые без пересоздания слоя.
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
});
Реактивное обновление данных осуществляется через
setData, что позволяет реализовать store-подобное
поведение:
const pointsStore = {
data: { type: 'FeatureCollection', features: [] },
setFeatures(features) {
this.data.features = features;
map.getSource('points').setData(this.data);
}
};
Таким образом источник данных становится реактивным адаптером между состоянием приложения и рендерингом WebGL-слоя.
Стилевая система Mapbox GL JS построена на декларативной модели слоёв, где изменения параметров могут быть реактивно применены без пересборки всего стиля.
map.setPaintProperty('cities-layer', 'circle-radius', [
'interpolate',
['linear'],
['get', 'population'],
1000, 4,
1000000, 12
]);
Store может инкапсулировать параметры визуализации:
const styleStore = {
radius: 6,
color: '#ff0000'
};
function applyStyle(map, store) {
map.setPaintProperty('cities-layer', 'circle-radius', store.radius);
map.setPaintProperty('cities-layer', 'circle-color', store.color);
}
При изменении значений store происходит точечное обновление слоя без пересоздания карты.
Реактивность в Mapbox GL JS опирается на событийную систему WebGL-карты. Основные события, используемые для синхронизации store:
move — изменение центра, масштаба, вращенияrender — цикл отрисовки кадраidle — завершение загрузки и отрисовкиdata — обновление источниковПример синхронизации состояния:
map.on('move', syncStore);
function syncStore() {
mapStore.center = map.getCenter().toArray();
mapStore.zoom = map.getZoom();
mapStore.bearing = map.getBearing();
mapStore.pitch = map.getPitch();
}
Для оптимизации часто применяется троттлинг, поскольку события
move могут вызываться десятки раз в секунду.
Поверх базового store строятся производные состояния (derived state), которые вычисляются на основе первичных параметров.
Пример:
function getVisibleRadius(store) {
return store.zoom > 12 ? 10 : 5;
}
Или более сложная модель:
Mapbox GL JS поддерживает выражения (expressions), которые частично заменяют необходимость внешних derived state:
['step',
['get', 'mag'],
'#00ff00',
5, '#ffff00',
7, '#ff0000'
]
При построении store-подхода часто требуется синхронизация в обе стороны:
Пример реализации:
let isSyncing = false;
map.on('move', () => {
if (isSyncing) return;
isSyncing = true;
mapStore.center = map.getCenter().toArray();
mapStore.zoom = map.getZoom();
isSyncing = false;
});
function setCenter(center) {
mapStore.center = center;
map.setCenter(center);
}
Без защиты от рекурсии возникает зацикливание обновлений.
Store-подход часто реализуется через внешние библиотеки реактивности:
Пример RxJS-подхода:
const center$ = new BehaviorSubject([0, 0]);
center$.subscribe(center => {
map.setCenter(center);
});
map.on('move', () => {
center$.next(map.getCenter().toArray());
});
Такая модель превращает карту в реактивный поток данных.
Реактивные обновления карты требуют аккуратного управления частотой изменений. Основные проблемы:
setDatamove событииОптимизационные стратегии:
requestAnimationFrameПример батчинга:
let pending = null;
function scheduleUpdate(state) {
pending = state;
requestAnimationFrame(() => {
applyMapState(pending);
pending = null;
});
}
GeoJSON source в Mapbox GL JS может рассматриваться как потоковое состояние. При обновлении данных:
Это делает источники основным кандидатом для store-абстракции:
class GeoJsonStore {
constructor(map, sourceId) {
this.map = map;
this.sourceId = sourceId;
this.state = { type: 'FeatureCollection', features: [] };
}
set(features) {
this.state.features = features;
this.map.getSource(this.sourceId).setData(this.state);
}
}
Такая модель позволяет изолировать работу с данными от визуального слоя карты.
Стиль карты в Mapbox GL JS сам по себе является декларативной системой, которая частично заменяет необходимость внешних store. Однако в сложных приложениях стиль становится лишь отражением более высокого уровня состояния.
Пример соответствия store → style:
function applyTheme(map, theme) {
map.setStyle(theme === 'dark'
? 'mapbox://styles/mapbox/dark-v11'
: 'mapbox://styles/mapbox/light-v11'
);
}
Каждая смена темы становится реактивным событием верхнего уровня.
В сложных приложениях store часто разбивается на независимые части:
Композиция позволяет локализовать реактивные обновления:
const appStore = {
map: mapStore,
data: dataStore,
ui: uiStore
};
Каждый store отвечает за свою область реактивности, минимизируя каскадные обновления.
Реактивная система вокруг Mapbox GL JS формируется как цикл: