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
相关产品推荐
相关产品推荐

