第 13 章 性能与最佳实践
地图应用的性能坑往往在使用量上来后才暴露。本章把前文散落的性能要点收敛成一份 可操作的清单,帮你在「功能」与「流畅」之间拿稳平衡。
13.1 三种渲染方式的取舍
| 方式 | 渲染机制 | 适用规模 | 交互粒度 |
|---|---|---|---|
MapMarker(DOM) | 真实 DOM 节点 | 数百 | 每点独立、富样式 |
MapGeoJSON 点图层 | WebGL | 数千~数万 | 统一样式、批量 |
MapClusterLayer | WebGL + 聚合 | 数千~数十万 | 聚合/单点 |
经验法则:点数过千,优先 MapClusterLayer 或 MapGeoJSON 点图层;只有需要 「每个标记都是独特 DOM 组件」时才用 MapMarker。
13.2 受控模式的代价
受控模式(viewport + onViewportChange)会在每一次平移、缩放、旋转时触发 回调,频繁 onViewportChange 可能带来重渲染压力。建议:
- 仅在确实需要「地图状态」与「应用状态」同步时启用;
- 若只想「监听」变化,单独用
onViewportChange而不传viewport,地图仍是非 受控的,回调不会驱动重渲染; - 对高频回调做节流(throttle),或只记录最终值。
13.3 事件监听必须清理
自定义 map.on() 监听要在组件卸载时 map.off()(见第 11 章)。未清理的 监听会随组件反复挂载而累积,导致内存泄漏与诡异行为。
13.4 图层增删先判定
直接用 map 增删图层时,先 getSource / getLayer 判断存在性再添加,避免 重复添加报错;卸载时移除自己创建的图层与 source。mapcn 的内置图层(Route/Arc/ GeoJSON/Cluster)已自动管理生命周期,无需你清理。
13.5 大数据量的取数策略
- 静态大数据:优先用 URL 让组件
fetch,或内联一次性数据,避免每次渲染重建 大型对象; - 动态数据:用
useMemo缓存 GeoJSON 结构,仅在数据真正变化时更新,减少fetch调用; - 表达式优于循环:用 MapLibre 表达式(如
interpolate/match)按字段着色, 而不是为每个要素单独建图层。
13.6 容器与尺寸
- 地图容器必须有确定高度,否则塌陷为 0;
- 频繁改变容器尺寸时,MapLibre 会自动重排,但应避免在动画帧里反复改尺寸;
- 移动端注意
100vh在地址栏收起时的抖动,必要时用100dvh或固定高度。
13.7 可访问性与降级
- 给交互控件加
aria-label(mapcn 已为内置按钮加好); - 地图本身对屏幕阅读器不友好,关键数据应同时在列表/表格中以文本形式提供;
- 地理定位等依赖用户授权的能力,要在被拒绝时优雅降级(见第 4 章
showLocate)。
13.8 一份检查清单
上线前对照:
- [ ] 点数规模是否选对了渲染方式?
- [ ] 受控模式是否必要?回调是否节流?
- [ ] 所有
map.on()都有对应map.off()? - [ ] 自定义图层是否在卸载时移除?
- [ ] 大数据是否用
useMemo/ URL / 表达式优化? - [ ] 容器高度是否确定、移动端是否稳定?
本章小结
- 渲染方式按规模取舍:数百用 DOM 标记,数千以上用图层/聚合;
- 受控模式按需启用,监听变化可用单向
onViewportChange; - 事件监听与自定义图层务必清理;
- 用
useMemo、URL 取数、表达式着色优化大数据; - 容器高度确定、关键数据文本化,是可访问与稳定的底线。
下一章,我们用一个完整案例把所学串起来。