> **本文回答**：Vue / React 组件卸载时地图没释放 —— 正确的销毁顺序 + 响应式陷阱。

## 一、症状

- 组件切走后 canvas 继续在 DOM 里（或不在 DOM 但内存不降）
- 多次进出页面后页面越来越慢
- 热更新（HMR）后地图变"重影"
- keepAlive 下 Marker 积累

## 二、销毁顺序（不可颠倒）

```
1. 取消业务事件监听 (off)
2. 清 Marker / Popup
3. 清 mapcadLayer eventBus
4. map.removeControl(mapcadLayer)
5. 清定时器 / RAF / Worker
6. map.remove()
7. 清 DOM 容器（通常 Vue/React 自动）
```

## 三、Vue 3 Options / setup 模板

```vue
<template>
  <div ref="mapEl" class="map"></div>
</template>

<script setup>
import { ref, shallowRef, onMounted, onBeforeUnmount } from 'vue';

const mapEl = ref(null);
const map = shallowRef(null);        // ★ shallowRef
const mapcadLayer = shallowRef(null);
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();

  mapcadLayer.value = new vjmapext.MapCadLayer();
  map.value.addControl(mapcadLayer.value);   // onAdd 同步完成，无 ready 钩子

  const m = new vjmap.Marker().setLngLat([0, 0]);
  m.addTo(map.value);
  markers.push(m);

  on(map.value, 'click', e => console.log(e.lngLat));
  on(mapcadLayer.value.eventBus, 'entity:added', ({ entity }) => console.log(entity.id));
});

onBeforeUnmount(() => {
  handlers.forEach(fn => fn());
  handlers.length = 0;
  markers.forEach(m => m.remove());
  markers.length = 0;
  if (mapcadLayer.value) {
    mapcadLayer.value.eventBus.clear();
    map.value?.removeControl(mapcadLayer.value);
    mapcadLayer.value = null;
  }
  map.value?.remove();
  map.value = null;
});
</script>
```

## 四、Vue Options API

```js
export default {
  data() {
    return { markers: [], handlers: [] };
  },
  mounted() {
    this.map = markRaw(new vjmap.Map(...));      // markRaw 防响应式代理
    // ...
  },
  beforeUnmount() {
    this.handlers.forEach(fn => fn());
    this.markers.forEach(m => m.remove());
    this.map?.remove();
  }
};
```

## 五、Vue keepAlive 场景

```vue
<keep-alive>
  <router-view />
</keep-alive>
```

组件切走用 `onDeactivated`，不是 `onBeforeUnmount`：

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

onDeactivated(() => {
  // 组件被 cache，按需释放资源
  markers.forEach(m => m.remove());
  markers.length = 0;
});
onActivated(() => {
  // 重新创建
});
```

或不 keepAlive 地图页。

## 六、React 完整模板

```jsx
import { useEffect, useRef } from 'react';

export default function MapView() {
  const mapEl = useRef(null);
  const mapRef = useRef(null);
  const mapcadLayerRef = useRef(null);
  const markersRef = useRef([]);

  useEffect(() => {
    let disposed = false;
    let cleanup = [];

    (async () => {
      const map = new vjmap.Map({ container: mapEl.current });
      await map.onLoad();
      if (disposed) { map.remove(); return; }

      mapRef.current = map;

      const mapcadLayer = new vjmapext.MapCadLayer();
      map.addControl(mapcadLayer);
      if (disposed) { map.removeControl(mapcadLayer); map.remove(); return; }

      mapcadLayerRef.current = mapcadLayer;

      const m = new vjmap.Marker().setLngLat([0, 0]);
      m.addTo(map);
      markersRef.current.push(m);

      const onClick = e => console.log(e.lngLat);
      map.on('click', onClick);
      cleanup.push(() => map.off('click', onClick));
    })();

    return () => {
      disposed = true;
      cleanup.forEach(fn => fn());
      markersRef.current.forEach(m => m.remove());
      markersRef.current = [];
      if (mapcadLayerRef.current) {
        mapcadLayerRef.current.eventBus.clear();
        mapRef.current?.removeControl(mapcadLayerRef.current);
        mapcadLayerRef.current = null;
      }
      mapRef.current?.remove();
      mapRef.current = null;
    };
  }, []);

  return <div ref={mapEl} className="map" />;
}
```

注意异步 init：卸载时 map 可能还没 onLoad 完成，用 `disposed` 标志防竞态。

## 七、响应式陷阱

### Vue `reactive` 代理 map 对象

```js
const state = reactive({ map: null });
state.map = new vjmap.Map(...);
// ❌ map 被 Proxy 包裹；很多内部 this 指向不对
```

**必用** `shallowRef` 或 `markRaw`：

```js
const map = shallowRef(null);
// 或
state.map = markRaw(new vjmap.Map(...));
```

### React useState 重渲染

```js
const [map, setMap] = useState(null);
```

`setMap(new vjmap.Map(...))` 会导致组件 re-render；通常不是问题，但 map 对象当依赖放 useEffect 的 `[]` 会反复运行。用 `useRef`：

```js
const mapRef = useRef(null);
```

## 八、HMR 支持

```js
if (import.meta.hot) {
  import.meta.hot.dispose(() => {
    markers.forEach(m => m.remove());
    map?.remove();
  });
}
```

## 九、异步初始化 + 快速卸载

```js
let disposed = false;
onBeforeUnmount(() => { disposed = true; });

(async () => {
  const map = new vjmap.Map(...);
  await map.onLoad();
  if (disposed) {
    map.remove();
    return;
  }
  // continue init
})();
```

避免 "用户进页面 → 立刻切走 → 异步 init 完成 → map 无法释放"。

## 十、SSR / Nuxt

SSR 下没 window / document：

```vue
// Nuxt 3
<script setup>
import { onMounted } from 'vue';

onMounted(async () => {
  const vjmap = await import('vjmap');
  const map = new vjmap.Map(...);
});
</script>
```

用动态 import 避免 SSR 阶段加载 vjmap。

## 十一、常见坑

1. **map 被 Vue reactive 代理**：异常 / 性能差；必 shallowRef / markRaw。
2. **onBeforeUnmount 时 map 已被路由切走但 DOM 还没销毁**：`map.remove` 顺序错；按本文顺序。
3. **keepAlive 下用 onBeforeUnmount**：不触发；用 onDeactivated。
4. **异步 init 卸载冲突**：加 `disposed` 标志。
5. **React StrictMode 下 useEffect 跑两次**：开发环境；生产不会。init/dispose 的幂等性要保证。

## 相关

- Marker 重影 → [marker-duplicate](marker-duplicate.md)
- 监听泄漏 → [listener-leak](listener-leak.md)
- 卸载 → [setup/dispose](../setup/dispose.md)
- Vue/React 集成 → [setup/integrations](../setup/integrations.md)
