Vue.js компоненты

Работа с картографическими сервисами в Vue.js требует согласованного подхода к жизненному циклу компонентов, реактивности состояния и управлению сторонними библиотеками, которые не являются частью реактивной системы фреймворка. HERE Maps API, построенный вокруг глобального объекта H, инициализируется императивно, поэтому его интеграция в Vue-компоненты опирается на изоляцию побочных эффектов и строгий контроль создания и уничтожения экземпляров карты.

Инициализация карты в рамках жизненного цикла компонента

Основной принцип интеграции заключается в создании карты после монтирования DOM-узла. В Vue 3 это реализуется через onMounted, в Vue 2 — через mounted. Контейнер для карты должен существовать в DOM до инициализации H.Map.

import { onMounted, onBeforeUnmount, ref } from "vue";

export default {
  setup() {
    const mapContainer = ref(null);
    let map = null;

    onMounted(() => {
      const platform = new H.service.Platform({
        apikey: "YOUR_API_KEY"
      });

      const defaultLayers = platform.createDefaultLayers();

      map = new H.Map(
        mapContainer.value,
        defaultLayers.vector.normal.map,
        {
          center: { lat: 50.4501, lng: 30.5234 },
          zoom: 10,
          pixelRatio: window.devicePixelRatio || 1
        }
      );

      const beh * avior = new H.mapevents.Behavior(
        new H.mapevents.MapEvents(map)
      );

      H.ui.UI.createDefault(map, defaultLayers);
    });

    onBeforeUnmount(() => {
      if (map) {
        map.dispose();
        map = null;
      }
    });

    return {
      mapContainer
    };
  }
};

Ключевым моментом является обязательное уничтожение экземпляра карты через dispose(). Игнорирование этого шага приводит к утечкам памяти и сохранению слушателей событий.

Разделение логики карты и UI через компонентную архитектуру

Vue-компоненты для картографических приложений целесообразно делить на:

  • контейнер карты (инициализация HERE Maps)
  • слой маркеров
  • слой маршрутов
  • компоненты управления (фильтры, панели, поиск)

Такое разделение обеспечивает изоляцию бизнес-логики от визуализации и упрощает повторное использование.

Контейнерный компонент выступает единственной точкой инициализации H.Map, а дочерние компоненты получают доступ к экземпляру карты через provide/inject или через пропсы.

Передача экземпляра карты через provide/inject

Механизм dependency injection в Vue 3 позволяет избежать глубокой передачи пропсов.

// MapProvider.vue
import { provide, onMounted, ref } from "vue";

export default {
  setup() {
    const map = ref(null);

    provide("hereMap", map);

    onMounted(() => {
      const platform = new H.service.Platform({ apikey: "KEY" });
      const layers = platform.createDefaultLayers();

      map.value = new H.Map(document.getElementById("map"), layers.vector.normal.map, {
        center: { lat: 40.7128, lng: -74.0060 },
        zoom: 12
      });
    });

    return { map };
  }
};
// MarkerLayer.vue
import { inject, onMounted } from "vue";

export default {
  setup() {
    const map = inject("hereMap");

    onMounted(() => {
      const marker = new H.map.Marker({ lat: 40.7128, lng: -74.0060 });
      map.value.addObject(marker);
    });
  }
};

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

Реактивное обновление объектов карты

Vue-реактивность не распространяется автоматически на объекты HERE Maps. Любые изменения должны синхронизироваться вручную через watchers.

import { watch } from "vue";

watch(
  () => props.points,
  (newPoints) => {
    map.removeObjects(map.getObjects());

    newPoints.forEach(p => {
      const marker = new H.map.Marker(p);
      map.addObject(marker);
    });
  },
  { deep: true }
);

При работе с большими массивами объектов требуется оптимизация: полная очистка слоя может быть заменена дифференциальным обновлением.

Обработка событий карты внутри Vue

HERE Maps использует собственную систему событий, которая интегрируется через addEventListener. В Vue важно привязывать и отписывать события строго в рамках жизненного цикла.

onMounted(() => {
  map.addEventListener("tap", (evt) => {
    const coord = map.screenToGeo(
      evt.currentPointer.viewportX,
      evt.currentPointer.viewportY
    );

    emit("map-click", coord);
  });
});

События следует хранить в переменных, если требуется последующее удаление через removeEventListener, особенно при динамической пересборке компонентов.

Управление маркерами как отдельным слоем

Маркерный слой часто выделяется в отдельную абстракцию для предотвращения загрязнения логики карты.

class MarkerLayer {
  constructor(map) {
    this.map = map;
    this.group = new H.map.Group();
    this.map.addObject(this.group);
  }

  setMarkers(points) {
    this.group.removeAll();

    points.forEach(({ lat, lng }) => {
      const marker = new H.map.Marker({ lat, lng });
      this.group.addObject(marker);
    });
  }

  destroy() {
    this.map.removeObject(this.group);
    this.group = null;
  }
}

Инкапсуляция через классы упрощает управление состоянием вне реактивной системы Vue.

Работа с реактивными пропсами и синхронизация состояния

Компоненты карты часто зависят от внешнего состояния: координат центра, уровня масштабирования, выбранных объектов.

watch(
  () => props.center,
  (center) => {
    map.setCenter(center);
  }
);

watch(
  () => props.zoom,
  (zoom) => {
    map.setZoom(zoom);
  }
);

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

Использование composables для повторного использования логики

Vue 3 composition API позволяет выделить HERE Maps логику в переиспользуемые функции.

import { ref, onMounted, onBeforeUnmount } from "vue";

export function useHereMap(containerId) {
  const map = ref(null);

  onMounted(() => {
    const platform = new H.service.Platform({ apikey: "KEY" });
    const layers = platform.createDefaultLayers();

    map.value = new H.Map(
      document.getElementById(containerId),
      layers.vector.normal.map,
      {
        zoom: 8,
        center: { lat: 48.8566, lng: 2.3522 }
      }
    );
  });

  onBeforeUnmount(() => {
    if (map.value) {
      map.value.dispose();
    }
  });

  return { map };
}

Такой подход позволяет стандартизировать инициализацию карт по всему приложению.

Интеграция маршрутов и сервисов HERE Routing

Работа с маршрутами требует отдельного сервиса H.service.RoutingService.

const routingService = platform.getRoutingService(null, 8);

const routingParameters = {
  routingMode: "fast",
  transportMode: "car",
  origin: "52.5160,13.3779",
  destination: "52.5206,13.3862",
  return: "polyline"
};

routingService.calculateRoute(routingParameters, (result) => {
  const route = result.routes[0];
  const lineString = H.geo.LineString.fromFlexiblePolyline(route.sections[0].polyline);

  const routeLine = new H.map.Polyline(lineString, {
    style: { strokeColor: "blue", lineWidth: 4 }
  });

  map.addObject(routeLine);
});

В Vue это обычно инкапсулируется в отдельный composable или сервисный слой, чтобы исключить прямую зависимость компонентов от API запросов.

SSR и Nuxt: ограничения выполнения

HERE Maps API зависит от window, поэтому в SSR-средах требуется условная инициализация.

if (typeof window !== "undefined") {
  // инициализация HERE Maps
}

В Nuxt-компонентах карта создаётся только на клиенте через onMounted или динамический импорт модуля.

Оптимизация производительности

При работе с большим количеством объектов критично учитывать:

  • использование H.map.Group вместо множества отдельных объектов
  • кэширование геометрии линий
  • минимизацию перерисовок карты
  • отключение лишних UI-слоёв

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

Управление масштабируемостью архитектуры компонентов

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

  • слой визуализации (Vue)
  • слой карты (HERE Maps API)
  • слой данных (store: Pinia/Vuex)
  • слой сервисов (routing, geocoding, search)

Разделение ответственности позволяет избежать смешивания реактивных и императивных моделей, что особенно важно при работе с геоданными в реальном времени.