> **本文回答**：组件卸载 / 切换路由时正确的清理顺序，避免 Marker 残留、监听器泄漏、WebGL 上下文爆满。

## 一、标准清理顺序

```js
// 卸载时按以下顺序：
// 1) 卸载 MapCadLayer（map.removeControl 会触发其 onRemove → 取消动画、释放 fx、清实体）
if (mapcadLayer) {
  map.removeControl(mapcadLayer);
  mapcadLayer = null;
}

// 2) 移除手动挂上的 Marker / Popup / 监听
markers.forEach(m => m.remove());
markers = [];
map.off("click", myHandler);

// 3) 销毁 Map（会释放 WebGL 上下文、source、layer、tile cache）
map.remove();
map = null;

// 4) Service 无 destroy 方法、本身无状态，不用管；多个 svc 实例共享底层连接也没问题
```

## 二、Vue 3

```vue
<script setup>
import { onMounted, onUnmounted } from "vue";
let map, mapcadLayer;

onMounted(async () => { /* 初始化见 integrations.md */ });
onUnmounted(() => {
  mapcadLayer && map?.removeControl(mapcadLayer);
  map?.remove();
  map = null;
  mapcadLayer = null;
});
</script>
```

Vue 2：用 `beforeDestroy`（或 Vue 2.7 的 `beforeUnmount`）。

## 三、React（StrictMode 友好）

```jsx
useEffect(() => {
  let map, cad, disposed = false;
  (async () => {
    // ... 初始化 ...
    if (disposed) { map?.remove(); return; }
  })();
  return () => {
    disposed = true;
    if (cad && map) map.removeControl(cad);
    map?.remove();
  };
}, []);
```

React StrictMode 会双触发 useEffect，必须用 `disposed` 标志位防止第二次 map 在第一次还没建完就 remove。

## 四、常见泄漏症状

| 症状 | 泄漏点 | 修法 |
|---|---|---|
| Marker 在页面切换后还显示 | 没 `marker.remove()` 或持有 DOM 引用 | 组件卸载时显式 `remove` 全部 Marker |
| 切换页面多次后页面卡 | `map.on("mousemove", ...)` 不清 | 统一保存 handler 并 `map.off` |
| 切页面十次后抛 `CONTEXT_LOST_WEBGL` | map 没 remove，WebGL 上下文超 16 个 | 卸载时必须 `map.remove()` |
| `MapCadLayer` 的动画还在运行 | 没 `removeControl(cad)` | 先 `removeControl`，再 `map.remove()` |
| `mapdata` watcher 还在触发 | `MapCadLayer.onRemove` 没跑 | 确认 `map.removeControl(cad)` 成功调用 |

## 五、长驻应用的特殊情况

SPA 里长时间运行 / 地图频繁切换：

- 切图（切换另一张 DWG）不需要销毁 map，而是用 `await map.switchMap({ mapid, mapopenway }, …)`（内部 openMap + 换瓦片源 + `updateMapExtent`；只调 `updateMapExtent` 不会换瓦片，旧图照样显示），详见 [open-map/switching.md](../open-map/switching.md)。
- 不断新建 / 销毁 Map 实例会让浏览器 WebGL 上下文池满。每个页面 **最多同时保留一张 Map**。

## 六、配合 ResizeObserver 的清理

```js
const ro = new ResizeObserver(() => map.resize());
ro.observe(container);
// 卸载时：ro.disconnect(); ro = null;
```

## 七、自动化检查清单

卸载时请确认以下都执行了：

- [ ] `map.removeControl(cad)`（仅当用了 `MapCadLayer`）
- [ ] 所有手动 `Marker.remove()`
- [ ] 所有手动 `map.off(...)`
- [ ] `ResizeObserver.disconnect()`
- [ ] `map.remove()`
- [ ] 本地引用置 `null`
- [ ] 组件内保留的 `setInterval / setTimeout` 清除
