> **本文回答**：`map.on` 生命周期事件 —— load / idle / render / sourcedata / styledata / error。

## 一、事件列表

| 事件 | 触发时机 |
|---|---|
| `load` | 地图首次加载完成（style + initial render） |
| `idle` | 当前帧渲染完毕且没有未完成的操作 |
| `render` | 每次地图重绘后触发（动画 / 交互时高频；静止时不触发） |
| `sourcedata` | source 有新数据（瓦片加载、setData） |
| `styledata` | 地图样式对象有变化（setStyle / addLayer / setPaintProperty…，会多次触发）；vjmap `map.updateStyle` 栅格模式不触发 |
| `styledataloading` | 样式开始加载 |
| `sourcedataloading` | source 开始加载 |
| `error` | 出错（图层 / 源 / 瓦片） |
| `remove` | 地图被销毁 |

## 二、load（关键）

**所有 addLayer / addSource 调用必须在 load 之后**：

```js
await map.onLoad();
map.addLayer({ id: 'my', source: 'src', type: 'line' });
```

或用事件：

```js
map.on('load', () => {
  map.addLayer(...);
});
```

`map.onLoad()` 是 Promise 版本，推荐。

## 三、idle

"空闲"状态 = 所有瓦片请求完成 + 无动画。用于**截图**：

```js
map.once('idle', () => {
  const dataUrl = map.getCanvas().toDataURL();
  download(dataUrl);
});
```

## 四、render

地图每次重绘都 fire（按需渲染：静止不触发，动画 / 拖动时每帧触发），对高频操作**慎用**：

```js
map.on('render', () => {
  // 同步 HUD，最好判断变化
});
```

## 五、sourcedata

source 状态变化：

```js
map.on('sourcedata', e => {
  if (e.sourceId === 'my-src' && map.isSourceLoaded('my-src')) {
    console.log('my-src 数据已加载');
  }
});
```

⚠️ `e.isSourceLoaded` 实为**整个样式所有源**是否都加载完（取的是 `style.loaded()`），不是 `e.sourceId` 这一个源；判断单源用 `map.isSourceLoaded(id)`。

## 六、styledata

样式变化：

```js
map.on('styledata', () => {
  // 样式对象变了（会多次触发，handler 须幂等）
});
```

styledata 是地图样式对象变化（`setStyle` / `addLayer` / `setPaintProperty`…）时触发。vjmap `map.updateStyle(svc, …)` 在栅格模式只换瓦片 URL（只有 `sourcedata`），不触发 styledata，自定义图层也不会丢；仅矢量瓦片且 darkMode 变化时内部才 `setStyle`。

## 七、error

```js
map.on('error', e => {
  console.error('地图错误：', e.error);
});
```

常见：

- 图层 id 重复
- source URL 404 / CORS
- 瓦片下载失败（单个瓦片错不会让地图坏）

## 八、一次性 once

```js
map.once('load', handler);
```

或 Promise：

```js
await map.onLoad();
```

## 九、组合场景

### 1 加载进度条

`dataloading` / `sourcedataloading` 按瓦片逐个触发，与 `sourcedata` 不一一对应，**勿逐事件计数**：

```js
map.on('dataloading', () => showLoading());
map.on('idle', () => hideLoading());
```

### 2 地图准备就绪

```js
await map.onLoad();
await map.once('idle');   // once 不传 listener 时返回 Promise
```

⚠️ `map.once(type, layerId, fn)` 的图层级 once 在**第一次**该类事件时就被消耗——即使那次没命中图层（fn 不会被调用）。

### 3 所有瓦片加载完打截图

```js
map.once('idle', async () => {
  const blob = await new Promise(resolve => {
    map.getCanvas().toBlob(resolve);
  });
  saveBlob(blob);
});
```

### 4 setStyle 后重建自定义图层

控件是 DOM，不随 setStyle 丢失，无需重建；需要重建的是自定义 source / layer：

```js
map.on('style.load', () => {
  if (!map.getSource('my-src')) {
    // 重新 addSource / addLayer
  }
});
```

## 十、生命周期坑

1. **load 之前 addLayer**：报错 `Style is not done loading`。
2. **render 里做昂贵计算**：重绘的每帧都跑；必须节流 or 用 `idle`。
3. **setStyle 后 addLayer 丢失**：setStyle 会清空 layer；在 `style.load` 里判断后重新加（styledata 会多次触发）。
4. **error 事件吃掉错误**：如果没监听，库会打 `console.error`；监听后记得自己打印。
5. **remove 之后继续 on**：报错或静默；代码里判断 `if (!map._removed)`。

## 相关

- 交互事件 → [map-interaction](map-interaction.md)
- 视图事件 → [map-view](map-view.md)
- 样式切换 → [map-style/theme-switch](../map-style/theme-switch.md)
