Работа с картами в Vue 3 через Composition API опирается на создание изолированного жизненного цикла экземпляра карты и привязку его к DOM-узлу, управляемому компонентом. Библиотека Mapbox GL JS требует императивного подхода, поэтому ключевая задача — корректно синхронизировать реактивность Vue и внутреннее состояние карты.
Подключение библиотеки:
npm install mapbox-gl
Импорт стилей обязателен, иначе карта не отобразится корректно:
import 'mapbox-gl/dist/mapbox-gl.css'
import mapboxgl from 'mapbox-gl'
Composition API позволяет строго контролировать инициализацию и
уничтожение ресурсов через onMounted и
onUnmounted.
<template>
<div ref="mapContainer" class="map-container"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import mapboxgl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css'
mapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN'
const mapContainer = ref(null)
let map = null
onMounted(() => {
map = new mapboxgl.Map({
container: mapContainer.value,
style: 'mapbox://styles/mapbox/streets-v12',
center: [69.2401, 53.2145],
zoom: 10
})
})
onUnmounted(() => {
if (map) {
map.remove()
map = null
}
})
</script>
<style>
.map-container {
width: 100%;
height: 500px;
}
</style>
Vue 3 позволяет связывать состояние интерфейса с параметрами карты
через watch. Это важно при изменении центра, масштаба или
стиля.
import { ref, watch } from 'vue'
const center = ref([69.2401, 53.2145])
const zoom = ref(10)
watch(center, (newCenter) => {
if (map) {
map.setCenter(newCenter)
}
})
watch(zoom, (newZoom) => {
if (map) {
map.setZoom(newZoom)
}
})
Такой подход обеспечивает однонаправленную синхронизацию состояния приложения и карты.
Для масштабируемых приложений логика Mapbox выносится в отдельный composable. Это уменьшает связанность компонентов и упрощает повторное использование.
import { ref, onMounted, onUnmounted } from 'vue'
import mapboxgl from 'mapbox-gl'
export function useMapbox(containerRef, options) {
const map = ref(null)
onMounted(() => {
map.value = new mapboxgl.Map({
container: containerRef.value,
style: options.style || 'mapbox://styles/mapbox/streets-v12',
center: options.center || [0, 0],
zoom: options.zoom || 2
})
})
onUnmounted(() => {
map.value?.remove()
map.value = null
})
return {
map
}
}
Использование composable:
<script setup>
import { ref } from 'vue'
import { useMapbox } from './useMapbox'
const container = ref(null)
const { map } = useMapbox(container, {
center: [69.2401, 53.2145],
zoom: 10
})
</script>
Mapbox GL JS использует императивное добавление объектов. В Vue 3 это
удобно комбинировать с watch.
const markers = ref([
{ id: 1, coords: [69.24, 53.21] },
{ id: 2, coords: [69.25, 53.22] }
])
watch(markers, (newMarkers) => {
if (!map) return
newMarkers.forEach(m => {
new mapboxgl.Marker()
.setLngLat(m.coords)
.addTo(map)
})
}, { deep: true })
Важно учитывать, что Mapbox не управляет жизненным циклом маркеров автоматически, поэтому требуется хранить ссылки для удаления:
const markerInstances = []
function addMarker(coords) {
const marker = new mapboxgl.Marker()
.setLngLat(coords)
.addTo(map)
markerInstances.push(marker)
}
function clearMarkers() {
markerInstances.forEach(m => m.remove())
markerInstances.length = 0
}
Mapbox GL JS опирается на концепцию источников (sources) и слоёв (layers). В Vue 3 их добавление обычно выполняется после события загрузки карты.
map.on('load', () => {
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
})
map.addLayer({
id: 'points-layer',
type: 'circle',
source: 'points',
paint: {
'circle-radius': 6,
'circle-color': '#ff0000'
}
})
})
Реактивное обновление данных:
function updateSource(data) {
const source = map.getSource('points')
if (source) {
source.setData(data)
}
}
Mapbox генерирует множество событий: движение карты, клики, загрузка стиля. Vue 3 позволяет обрабатывать их в рамках реактивной архитектуры.
const mapState = ref({
lng: 0,
lat: 0,
zoom: 0
})
map.on('move', () => {
const center = map.getCenter()
mapState.value = {
lng: center.lng,
lat: center.lat,
zoom: map.getZoom()
}
})
Mapbox поддерживает переключение стилей без пересоздания карты.
const style = ref('mapbox://styles/mapbox/streets-v12')
watch(style, (newStyle) => {
if (map) {
map.setStyle(newStyle)
}
})
После смены стиля требуется повторная инициализация слоёв:
map.on('style.load', () => {
map.addSource('points', {
type: 'geojson',
data: initialData
})
})
Composition API требует явного контроля уничтожения объектов, созданных вне Vue.
onUnmounted(() => {
markerInstances.forEach(m => m.remove())
markerInstances.length = 0
map?.remove()
map = null
})
Игнорирование очистки приводит к утечкам памяти, особенно при частой навигации между страницами.
Mapbox позволяет добавлять кастомные элементы управления, которые легко интегрируются во Vue.
class CustomControl {
onAdd(map) {
this.map = map
this.container = document.createElement('button')
this.container.textContent = 'Reset'
this.container.oncl ick = () => {
map.setCenter([69.24, 53.21])
map.setZoom(10)
}
return this.container
}
onRemove() {
this.container.remove()
this.map = null
}
}
map.addControl(new CustomControl())
GeoJSON остаётся основным форматом передачи данных. В Vue 3 он удобно
хранится в ref.
const geojson = ref({
type: 'FeatureCollection',
features: []
})
function addFeature(feature) {
geojson.value.features.push(feature)
updateSource(geojson.value)
}
Частые вызовы setData или добавление маркеров напрямую
могут снижать производительность. Используются техники батчинга:
let queue = []
function queueUpdate(feature) {
queue.push(feature)
requestAnimationFrame(() => {
if (queue.length === 0) return
geojson.value.features.push(...queue)
updateSource(geojson.value)
queue = []
})
}
Часто данные для карты приходят из API, что требует аккуратной синхронизации с состоянием карты.
async function fetchPoints() {
const res = await fetch('/api/points')
const data = await res.json()
geojson.value = data
if (map?.getSource('points')) {
map.getSource('points').setData(data)
}
}
При переходах между маршрутами карта должна корректно пересоздаваться
или обновляться, особенно при использовании
<keep-alive>.
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
map?.resize()
})
onDeactivated(() => {
// временная пауза взаимодействия
})
При росте проекта логика карты разделяется на:
Такое разделение позволяет поддерживать предсказуемую структуру при работе с Mapbox GL JS в рамках Vue 3 Composition API.