v-for循环中动态传Props至组件时地图定位异常问题排查
问题根源及解决方案
核心问题分析
- 多弹窗复用同一控制变量:在
v-for循环内为每个卡片都创建了q-dialog,且所有弹窗共用同一个mapDialog状态变量。当触发弹窗时,所有弹窗都会尝试激活,视觉上最后一个弹窗会覆盖前面的,导致始终显示最后一个会议的位置。 - 地图组件未响应Props更新:
MeetMapComponent的mapRender()仅在组件初始化时执行一次,后续Props变化不会触发地图重新渲染,导致显示的是第一次初始化时的位置数据。 - 组件Key未关联会议实例:
MeetMapComponent的key使用了全局的componentKey,没有绑定到当前会议,Vue会复用组件实例,无法更新为当前会议的位置数据。
解决方案步骤
1. 重构弹窗结构,使用单一弹窗+当前选中会议
将弹窗从v-for循环内移出,通过变量存储当前选中的会议,确保弹窗只渲染对应会议的地图数据:
父组件模板修改:
<!-- 单一弹窗放在循环外 --> <q-dialog class="mapDialog flex column" v-model="mapDialog"> <MeetMapComponent :mapData="currentMeeting?.address" :buyerName="currentMeeting?.name" /> </q-dialog> <!-- 会议卡片循环 --> <div v-for="meeting in meetings" :key="meeting.did" class="col-12 col-md-6 col-lg-3 q-pa-md q-mx-xl" > <q-card class="my-card homeCard q-pa-md"> <q-card-section class="tipCardImage flex row justify-end" :style="`background-image: url(${meeting.car.carImg})`" > <router-link :to="`/user/meet/edit/${meeting.did}`" style="text-decoration: none" > <q-icon @click="fetchMeeting(meeting.did)" name="fa-solid fa-pencil editNameIcon q-mb-sm q-ml-sm" ></q-icon> </router-link> <q-icon name="fa-solid fa-trash editNameIcon q-mb-sm q-ml-sm" @click="triggerDelete(meeting.did)" ></q-icon> </q-card-section> <q-card-section> <div class="cardTitle"> <span>Encuentro Con</span> {{ truncateString(meeting.name, 30) }} </div> <div class="tipCardText"> <span>Agendado para el </span> <p>{{ truncateString(meeting.date, 120) }}</p> </div> <div class="flex row justify-end"> <q-btn @click="openMapDialog(meeting)" class="text-white cardButton" :class="{ cardButtonMobile: $q.screen.lt.md }" >Ver Ubicación</q-btn > </div> </q-card-section> </q-card> </div>
父组件逻辑修改:
import { ref } from 'vue'; // 弹窗控制变量 const mapDialog = ref(false); // 当前选中的会议 const currentMeeting = ref(null); // 打开弹窗时赋值当前会议 function openMapDialog(meeting) { currentMeeting.value = meeting; mapDialog.value = true; }
2. 修改地图组件,响应Props变化并清理实例
让MeetMapComponent监听Props变化,重新渲染地图,同时添加实例清理逻辑避免内存泄漏:
<template> <div class="meetMapContainer"> <div ref="mapDiv" style="width: 100%; height: 500px" /> <h5 class="text-center text-white">{{ props.mapData?.address }}</h5> </div> </template> <script setup> import { ref, watch, onUnmounted } from "vue"; import { Loader } from "@googlemaps/js-api-loader"; const props = defineProps({ mapData: Object, buyerName: String, }); const GOOGLE_MAPS_API_KEY = "..."; const loader = new Loader({ apiKey: GOOGLE_MAPS_API_KEY }); const mapDiv = ref(null); let mapInstance = null; // 存储地图实例用于销毁 async function mapRender() { // 无有效坐标时直接返回 if (!props.mapData?.coordinates) return; // 清理旧地图实例 if (mapInstance) { google.maps.event.clearInstanceListeners(mapInstance); mapInstance = null; } await loader.load(); // 创建新地图实例 mapInstance = new google.maps.Map(mapDiv.value, { mapTypeId: "roadmap", center: props.mapData.coordinates, zoom: 13, }); // 添加标记 new google.maps.Marker({ position: props.mapData.coordinates, map: mapInstance, title: `Encuentro con ${props.buyerName}`, }); } // 监听Props变化,触发地图重新渲染(immediate: true 确保初始化时执行) watch([() => props.mapData, () => props.buyerName], mapRender, { immediate: true }); // 组件卸载时清理地图实例 onUnmounted(() => { if (mapInstance) { google.maps.event.clearInstanceListeners(mapInstance); mapInstance = null; } }); </script>
内容的提问来源于stack exchange,提问作者Dirk Gaston
相关产品推荐
相关产品推荐

