marker-duplicate
本文回答:Vue / React 组件切换后 Marker 残留 / 重影 —— 卸载时没
marker.remove()。
一、症状
- 切换路由回来,Marker 数量变成原来的 2 倍、3 倍
- Popup 重影、残留
- 控件重复出现
二、根因
Marker / Popup / 控件不在 map.remove 时自动清理。
map.remove()只销毁 map 本身- 外部
new vjmap.Marker().addTo(map)创建的 Marker 保留在 DOM
三、正确卸载
const markers = [];
function addMarker(lng, lat) {
const m = new vjmap.Marker().setLngLat([lng, lat]);
m.addTo(map); // addTo 返回 undefined,勿接链尾
markers.push(m);
}
// 卸载
for (const m of markers) m.remove();
markers.length = 0;
四、Vue 3 完整模板
<template>
<div ref="mapEl" class="map"></div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, shallowRef } from 'vue';
const mapEl = ref(null);
const map = shallowRef(null); // 避免响应式代理 map
const markers = [];
const handlers = [];
function on(target, evt, h) {
target.on(evt, h);
handlers.push(() => target.off(evt, h));
}
onMounted(async () => {
map.value = new vjmap.Map({ container: mapEl.value });
await map.value.onLoad();
const m = new vjmap.Marker().setLngLat([0, 0]);
m.addTo(map.value);
markers.push(m);
on(map.value, 'click', e => console.log(e.lngLat));
});
onBeforeUnmount(() => {
handlers.forEach(fn => fn());
markers.forEach(m => m.remove());
markers.length = 0;
map.value?.remove();
map.value = null;
});
</script>
五、React 模板
import { useEffect, useRef } from 'react';
function MapView() {
const mapRef = useRef(null);
const mapEl = useRef(null);
const markers = useRef([]);
useEffect(() => {
const map = new vjmap.Map({ container: mapEl.current });
mapRef.current = map;
const m = new vjmap.Marker().setLngLat([0, 0]);
m.addTo(map);
markers.current.push(m);
const onClick = e => console.log(e.lngLat);
map.on('click', onClick);
return () => {
map.off('click', onClick);
markers.current.forEach(m => m.remove());
markers.current = [];
map.remove();
};
}, []);
return <div ref={mapEl} style={{ width: '100%', height: '600px' }} />;
}
六、热更新(HMR)下的重影
Vite / webpack HMR 重新加载组件 → 旧 map 没销毁 + 新 map 建立 → canvas 重叠:
if (import.meta.hot) {
import.meta.hot.dispose(() => {
markers.forEach(m => m.remove());
map.remove();
});
}
七、路由切换
Vue Router 切换路由:
keepAlive:组件 deactivate 不 unmount;Marker 不会被 remove → 下次 activate 又加 → 重影- 不
keepAlive:正常走 onBeforeUnmount
keepAlive 下用 onDeactivated:
import { onDeactivated, onActivated } from 'vue';
onDeactivated(() => {
markers.forEach(m => m.remove());
markers.length = 0;
});
onActivated(() => {
// 重新创建 Marker
});
八、检测 Marker 重影
console.log('Marker count:', document.querySelectorAll('.vjmapgis-marker').length);
如果比预期多 → 有泄漏。
九、用一次性清理 helper
class ResourceBag {
constructor() { this.items = []; }
add(cleanup) { this.items.push(cleanup); }
clear() {
for (const f of this.items) {
try { f(); } catch (e) { console.warn(e); }
}
this.items = [];
}
}
const bag = new ResourceBag();
const m = new vjmap.Marker().setLngLat([0, 0]);
m.addTo(map);
bag.add(() => m.remove());
const h = e => {};
map.on('click', h);
bag.add(() => map.off('click', h));
// 卸载
bag.clear();
十、Popup 残留
Popup 和 Marker 类似,需要 popup.remove():
const popup = new vjmap.Popup().setLngLat([0, 0]).setHTML('...').addTo(map);
// 卸载
popup.remove();
或绑在 Marker 上自动管理:
const m = new vjmap.Marker()
.setLngLat([0, 0])
.setPopup(new vjmap.Popup().setHTML('...'));
m.addTo(map);
// marker.remove() 时 popup 一起移除
十一、控件重复
const ctrl = new vjmap.NavigationControl();
map.addControl(ctrl);
// 卸载时
map.removeControl(ctrl);
十二、常见坑
- onBeforeUnmount 里
markers = []但不 remove:数组清空,DOM 还在。 - Marker 加到 map 之外的 DOM:自定义 element parent 不是 map.container → map.remove 不清;要自己 remove。
- 响应式变量 wrap map:Vue
reactive(map)会 proxy,map.remove调用在 proxy 上可能异常;用shallowRef。 - forgetn 的 eventBus 监听:mapcadLayer 监听 entity:added → 仍持有旧组件引用;记得 off。
- keepAlive 下重复 mount:只做"创建",不做"销毁" → 第二次 activate 又创建。
相关
- 卸载 → setup/dispose
- Marker 生命周期 → marker/create
- Vue / React → vue-react-dispose