> **本文回答**：Vue / React 组件切换后 Marker 残留 / 重影 —— 卸载时没 `marker.remove()`。

## 一、症状

- 切换路由回来，Marker 数量变成原来的 2 倍、3 倍
- Popup 重影、残留
- 控件重复出现

## 二、根因

Marker / Popup / 控件**不在 map.remove 时自动清理**。

- `map.remove()` 只销毁 map 本身
- 外部 `new vjmap.Marker().addTo(map)` 创建的 Marker 保留在 DOM

## 三、正确卸载

```js
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 完整模板

```vue
<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 模板

```jsx
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 重叠：

```js
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`：

```js
import { onDeactivated, onActivated } from 'vue';

onDeactivated(() => {
  markers.forEach(m => m.remove());
  markers.length = 0;
});

onActivated(() => {
  // 重新创建 Marker
});
```

## 八、检测 Marker 重影

```js
console.log('Marker count:', document.querySelectorAll('.vjmapgis-marker').length);
```

如果比预期多 → 有泄漏。

## 九、用一次性清理 helper

```js
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()`：

```js
const popup = new vjmap.Popup().setLngLat([0, 0]).setHTML('...').addTo(map);
// 卸载
popup.remove();
```

或绑在 Marker 上自动管理：

```js
const m = new vjmap.Marker()
  .setLngLat([0, 0])
  .setPopup(new vjmap.Popup().setHTML('...'));
m.addTo(map);
// marker.remove() 时 popup 一起移除
```

## 十一、控件重复

```js
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 又创建。

## 相关

- 卸载 → [setup/dispose](../setup/dispose.md)
- Marker 生命周期 → [marker/create](../marker/create.md)
- Vue / React → [vue-react-dispose](vue-react-dispose.md)
