You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3响应式对象的modelValue更新问题及Mapbox组件调试

问题:Vue3组合式API改造Mapbox示例时v-model无法更新

我是Vue新手,对v-model理解尚浅,尝试将Mapbox官方Vue示例改造为Vue3组合式API版本时,遇到props.modelValue无法更新的问题。父组件App.vue和子组件Map.vue代码如下,请求排查问题并给出改进建议。

父组件App.vue

<script setup>
  import Map from './components/Map.vue';
  import '../node_modules/mapbox-gl/dist/mapbox-gl.css';
  import { ref, reactive, defineModel } from 'vue';

  const model = defineModel()
  const location = reactive(
    { lng: -71.224518, 
      lat: 42.213995, 
      bearing: 0, 
      pitch: 0, 
      zoom: 9 },
  )

</script>

<template>
  <div id="layout">
    <div id="sidebar">
      Longitude: {{ location.lng.toFixed(4) }} | Latitude: {{ 
      location.lat.toFixed(4) }} | Zoom: {{ location.zoom.toFixed(2) }} |
      <template v-if="location.bearing">
        Bearing: {{ location.bearing.toFixed(2) }} |
      </template>
      <template v-if="location.pitch">
        Pitch: {{ location.pitch.toFixed(2) }} |
      </template>
      <button
        @click="location = { lng: -71.224518, lat: 42.213995, zoom: 9, pitch: 0, bearing: 0 }"
      >
        Reset
      </button>
    </div>
    <Map v-model="location" />
  </div>
</template>

<style>
  #layout {
    flex: 1;
    display: flex;
  }

  #sidebar {
    background-color: rgb(255 255 0 / 50%);
    color: #fff;
    padding: 6px 12px;
    font-family: monospace;
    font-weight: bold;
    z-index: 1;
    position: absolute;
    top: 0;
    left: 0;
    margin: 12px;
    border-radius: 4px;
  }
</style>

子组件Map.vue

<script setup>
import mapboxgl from 'mapbox-gl';
import { ref, watch, onMounted, onUnmounted } from 'vue'
mapboxgl.accessToken =  <UserAccessToken />; // 此处需替换为你的Mapbox访问令牌

const props = defineProps({
    modelValue:
    {
        type: Object,
        required: true
    }
});

const emit = defineEmits(['update:modelValue']);

const mapContainer = ref(null);
const thismap = ref(null);

onMounted(() => {
    const { lng, lat, zoom, bearing, pitch } = props.modelValue;
    const map = new mapboxgl.Map({
        container: mapContainer.value,
        style: "mapbox://styles/mapbox/dark-v11", //'mapbox://styles/mapbox/streets-v12',
        center: [lng, lat],
        bearing,
        pitch,
        zoom
    });

    const updateLocation = () => {
        emit('update:modelValue', getLocation()); // 怀疑此处存在问题
    }

    map.on('move', updateLocation);
    map.on('zoom', updateLocation);
    map.on('rotate', updateLocation);
    map.on('pitch', updateLocation);
    
    thismap.value = map;
    
    
});

onUnmounted(() => {
    thismap.value.remove();
    thismap = null;
    });


watch (() => props.modelValue, (next) => {
    const curr = getLocation();
    const map = thismap.value;
    if (curr.lng != next.lng || curr.lat != next.lat)
    thismap.setCenter({ lng: next.lng, lat: next.lat });
    if (curr.pitch != next.pitch) thismap.setPitch(next.pitch);
    if (curr.bearing != next.bearing) thismap.setBearing(next.bearing);
    if (curr.zoom != next.zoom) thismap.setZoom(next.zoom);
});

function getLocation() {
    console.log("is this ran?")
    //console.log(props.modelValue)
    // console.log(thismap.value.getZoom())
    return {
        ...thismap.value.getCenter(),
        bearing: thismap.value.getBearing(),
        pitch: thismap.value.getPitch(),
        zoom: thismap.value.getZoom()
    }
}

</script>

<template>
    <div ref="mapContainer" class="map-container"></div>
</template>  

<style>
.map-container {
    flex: 1;
}
</style>

问题现象

emit后对象未更新,props.modelValue数值无变化。


问题排查与改进建议

  • 修复getLocation返回对象的响应性问题:
    Mapbox的getCenter()返回的是LngLat实例,其lng/lat是getter属性,直接展开无法被Vue追踪响应性。需手动提取数值:

    function getLocation() {
        const center = thismap.value.getCenter();
        return {
            lng: center.lng,
            lat: center.lat,
            bearing: thismap.value.getBearing(),
            pitch: thismap.value.getPitch(),
            zoom: thismap.value.getZoom()
        }
    }
    
  • 修复watch的深度监听与调用错误:
    原watch是浅监听,无法捕获对象内部属性变化;同时存在thismap.setCenter的调用错误(需访问.value或使用已声明的map变量):

    watch(() => props.modelValue, (next) => {
        const map = thismap.value;
        if (!map) return; // 避免地图未初始化时报错
        const curr = getLocation();
        if (curr.lng !== next.lng || curr.lat !== next.lat) {
            map.setCenter([next.lng, next.lat]);
        }
        if (curr.pitch !== next.pitch) map.setPitch(next.pitch);
        if (curr.bearing !== next.bearing) map.setBearing(next.bearing);
        if (curr.zoom !== next.zoom) map.setZoom(next.zoom);
    }, { deep: true }); // 添加深度监听
    
  • 修复Reset按钮的响应性破坏问题:
    直接赋值location = {...}会替换reactive对象,破坏响应性关联,改用Object.assign更新属性:

    <button
      @click="Object.assign(location, { lng: -71.224518, lat: 42.213995, zoom: 9, pitch: 0, bearing: 0 })"
    >
      Reset
    </button>
    
  • 添加事件防抖优化性能:
    Mapbox的move/zoom等事件触发频率极高,频繁emit会导致父组件频繁更新,建议添加防抖:

    // 自己实现简易防抖函数
    function debounce(fn, delay) {
        let timer;
        return (...args) => {
            clearTimeout(timer);
            timer = setTimeout(() => fn(...args), delay);
        }
    }
    
    const updateLocation = debounce(() => {
        emit('update:modelValue', getLocation());
    }, 100); // 100ms防抖间隔
    
  • 清理无用代码:
    父组件中const model = defineModel()未使用,可直接删除;子组件onUnmounted中thismap = null改为thismap.value = null,避免破坏ref引用。


内容的提问来源于stack exchange,提问作者giosans

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 04:57:24