Реактивность и watchers

Реактивная модель в OpenLayers строится вокруг событийной системы и наблюдаемых свойств, которые позволяют связывать изменения состояния карты с автоматическим обновлением визуального представления. В отличие от фреймворков с полноценным виртуальным DOM, здесь реактивность реализована на уровне отдельных объектов: карты, представления, слоёв и источников данных.

Базовым строительным блоком является система Observable и BaseObject. Почти каждый значимый объект в библиотеке наследует возможность:

  • хранить именованные свойства;
  • уведомлять подписчиков об изменениях;
  • предоставлять доступ к старым и новым значениям.

Типичный пример — изменение центра карты:

map.getView().setCenter([0, 0]);

В этот момент срабатывает цепочка событий:

  1. свойство center изменяется внутри View;
  2. генерируется событие change:center;
  3. подписчики получают уведомление;
  4. пересчитывается матрица отображения тайлов и векторных слоёв.

Ключевая особенность — отсутствие глобального реактивного графа. Каждый объект отвечает за собственные изменения и сам уведомляет слушателей.

Подписка на изменения через события

Основной способ наблюдения — метод on. Он позволяет подписываться на конкретные события, включая изменения свойств.

const view = map.getView();

view.on('change:zoom', () => {
  console.log('Zoom изменился:', view.getZoom());
});

Подобный подход используется практически для всех сущностей:

  • Map — изменения размера, движений и взаимодействий;
  • View — центр, масштаб, вращение;
  • Layer — видимость, источник данных, стиль;
  • Source — добавление и удаление объектов.

Важно, что события не агрегируются автоматически. При частых изменениях, например при анимации, слушатель будет вызываться многократно.

Property Watchers как реактивный слой

Внутри OpenLayers реализован механизм set/get свойств с автоматической генерацией событий изменения. Это позволяет использовать «watchers» на уровне именованных ключей.

layer.on('change:opacity', () => {
  console.log('Новая прозрачность:', layer.getOpacity());
});

Схема работы:

  • set('opacity', value) изменяет внутреннее состояние;
  • вызывается dispatchEvent('propertychange');
  • дополнительно вызывается change:opacity;
  • обновляются рендереры слоя.

Такой механизм можно считать низкоуровневым аналогом watchers, где каждый ключ — отдельная реактивная точка.

Реактивность View: центр, масштаб и вращение

Объект View является одним из самых «реактивных» элементов системы. Он отслеживает три ключевых параметра:

  • центр карты;
  • масштаб (zoom);
  • вращение.

Каждое изменение этих параметров вызывает перерасчёт матрицы отображения.

view.on('change:rotation', () => {
  const rotation = view.getRotation();
  console.log('Поворот карты:', rotation);
});

Реактивная цепочка включает:

  • пересчёт координат пикселей;
  • обновление матрицы трансформации;
  • перерисовку canvas или WebGL слоя.

Важно учитывать, что несколько изменений подряд могут привести к серии рендеров, если не использовать оптимизацию через requestAnimationFrame.

Реактивность слоёв

Слои в OpenLayers обладают собственной системой наблюдения. Основные реактивные свойства:

  • visible;
  • opacity;
  • zIndex;
  • source;
  • style.

Пример наблюдения за источником данных:

layer.on('change:source', () => {
  console.log('Источник данных слоя изменился');
});

При смене источника происходит:

  1. отключение старых событий;
  2. подключение нового Source;
  3. сброс кэша рендеринга;
  4. принудительная перерисовка.

Особенно важно поведение при работе с TileSource, где изменение параметров может привести к полной перегрузке тайлов.

Реактивность источников данных (Source)

Источники данных — одна из наиболее динамичных частей системы. Векторные источники (VectorSource) генерируют события при изменении коллекции объектов:

vectorSource.on('addfeature', (event) => {
  console.log('Добавлен объект:', event.feature);
});

vectorSource.on('removefeature', (event) => {
  console.log('Удалён объект:', event.feature);
});

Каждое изменение автоматически отражается в:

  • геометрическом представлении;
  • кластерах (если используются);
  • стилях;
  • фильтрах отображения.

При массовых изменениях используется событие change, которое сигнализирует о необходимости полной переработки состояния.

Watchers на уровне Feature

Геометрические объекты (Feature) поддерживают собственные наблюдаемые свойства. Это позволяет реализовывать реактивные визуализации данных.

feature.on('change', () => {
  console.log('Изменилось одно из свойств feature');
});

Для более точного контроля можно использовать:

feature.on('change:geometry', () => {
  console.log('Геометрия изменена');
});

Такая модель особенно важна при:

  • интерактивном редактировании объектов;
  • динамической анимации координат;
  • привязке данных к внешним API.

Паттерн отписки и управление жизненным циклом

Любая реактивная подписка должна быть управляемой. Метод un или unByKey используется для удаления слушателей.

const key = view.on('change:center', handler);

// позже
unByKey(key);

Без явной отписки возможны:

  • утечки памяти;
  • накопление слушателей;
  • деградация производительности при длительной работе карты.

Особенно критично это при создании временных слоёв и динамических интерфейсов.

Оптимизация реактивных обновлений

При высокой частоте изменений реактивная система может становиться узким местом. Основные подходы оптимизации:

Дебаунс событий

let timeout;

view.on('change:center', () => {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    console.log('Стабилизированный центр:', view.getCenter());
  }, 100);
});

Группировка изменений

view.setCenter([0, 0]);
view.setZoom(5);
view.setRotation(0);

При таком подходе движок старается объединить изменения в один цикл рендеринга.

Минимизация подписок

Чем меньше активных watchers, тем ниже нагрузка на event loop. Практика — подписываться только на реально используемые свойства, избегая универсальных change.

Реактивные стили

Функция style может выступать как реактивный вычисляемый слой:

layer.setStyle((feature) => {
  const value = feature.get('value');
  return value > 10 ? styleA : styleB;
});

При изменении свойств feature автоматически происходит перерасчёт стиля, что делает систему похожей на вычисляемые зависимости.

Особенности архитектуры реактивности

Реактивная модель OpenLayers не является централизованной. Она строится по принципу распределённого реагирования:

  • каждый объект — независимый источник событий;
  • нет глобального state manager;
  • обновления локализованы;
  • рендеринг инициируется снизу вверх.

Такая архитектура обеспечивает:

  • предсказуемость поведения;
  • высокую производительность при частичных изменениях;
  • гибкость при интеграции с внешними системами состояния (Redux, MobX, RxJS).

При этом сложные цепочки watchers требуют аккуратного проектирования, чтобы избежать каскадных перерисовок и избыточных вычислений.