唯杰地图 logo唯杰地图
VJMAP
EN

marker-duplicate

vjmap/references/trouble/marker-duplicate.md 4.9 KB218 行
原文

本文回答: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);

十二、常见坑

  1. onBeforeUnmount 里 markers = [] 但不 remove:数组清空,DOM 还在。
  2. Marker 加到 map 之外的 DOM:自定义 element parent 不是 map.container → map.remove 不清;要自己 remove。
  3. 响应式变量 wrap map:Vue reactive(map) 会 proxy,map.remove 调用在 proxy 上可能异常;用 shallowRef。
  4. forgetn 的 eventBus 监听:mapcadLayer 监听 entity:added → 仍持有旧组件引用;记得 off。
  5. keepAlive 下重复 mount:只做"创建",不做"销毁" → 第二次 activate 又创建。

相关