💬 个人观点(小声说):其实这是公司项目中的需求。说实话,我认为"统一地图组件"在技术层面很难做到完全通用(也可能是我水平不够😅)。不过如果只是满足特定的业务需求场景,其实还是有很大意义的。就像 uni-app 也在尝试做统一 API,思路是一致的。
✨ 背景与诉求
在实际业务开发中,地图功能需求愈发频繁,常常涉及多个地图服务提供商(如高德、华为、Google Maps 等)。由于各家地图 API 在调用方式、数据结构和行为逻辑上存在差异,开发人员往往需要为不同服务重复编写适配代码,造成:
- ⛓ 高维护成本
- 🧱 开发效率低
- 🧩 项目结构混乱、重复代码多
为此,我们希望构建一个 统一地图组件(Map Kit),通过抽象统一的接口,屏蔽地图服务差异,实现一次开发多方运行的目标。
🧨 核心痛点
- 地图功能在业务中的关键地位:如定位、路径规划、可视化展示等
- 组件碎片化严重 :
- 同一功能需在不同技术栈中多次实现
- 功能重复、难以复用
- 缺乏统一的接口规范
🎯 目标与价值
- 提升开发效率与代码复用率
- 降低维护成本与技术债务
- 形成内部地图信息展示组件开发标准化体系
🧭 设计原则
- 统一接口抽象:封装主流地图厂商核心 API,屏蔽底层差异
- 多厂商支持:兼容高德、华为、Google 等地图服务
- 高可配置性:通过配置项控制地图样式、行为、API Key 等
- 轻量、低耦合:核心库保持纯粹,业务侧无侵入
- 良好开发体验:TypeScript 类型提示、错误提示、调试接口完善
- 可扩展、可自定义:支持插件机制,便于二次开发与增强功能
🏗 组件构建思路
📌 核心功能模块
我们不会一开始就囊括所有地图 API,只抽象业务中高频使用的核心能力:
- 地图初始化与销毁
- 点标记管理(添加、更新、删除)
- 路径与线段绘制(如轨迹回放)
- 区域绘制(圆形、多边形、矩形)
- 地理位置搜索(关键字 / 周边搜索)
- 地理编码与逆地理编码
- 坐标系转换(WGS-84、GCJ-02、BD-09)
后续会按需迭代如图层控制、热力图、3D 地图等更复杂功能(但我短期内可能不想做😅)。
📐 组件架构分层图

分层说明
📦 业务应用层
- 使用统一 API 初始化地图
- 通过暴露的接口进行地图操作
🔧 统一服务 API 层
- 统一参数结构,提供一致性接口(如
initMap
,addMarker
) - 统一错误处理与日志监控
- 屏蔽底层实现细节,面向业务
🔌 服务适配层
- 注册机制注入不同服务商的实现
- 功能模块化拆分(标记、线、面、路径、搜索等)
- 适配各地图厂商差异,输出统一格式
🌐 原生 API 层
- 引用地图服务提供商的原生 JavaScript SDK
🧱 组件类图

💻 项目结构说明
📂 文件结构
shell
├── demo/ # 各类地图功能示例
├── src/
│ ├── assets/ # 静态资源(如图标)
│ ├── index.ts # 统一入口文件
│ ├── mapProvider/ # 地图服务适配层
│ │ ├── amap/ # 高德地图实现
│ │ ├── google/ # Google 地图实现
│ │ ├── huawei/ # 华为地图实现
│ │ ├── commonHandler.ts # 通用逻辑封装
│ │ └── serviceParamsType.ts # 参数类型定义
│ ├── types/ # 接口与类型声明
│ └── utils.ts # 工具函数
├── package.json
├── tsconfig.json
├── vite.config.ts
└── README.md
🧭 调用流程图
🧠 技术设计亮点
☝ 单一职责原则(SRP)
各功能模块职责清晰,单一管理,便于测试与维护:
ts
private baseManager: any // 基础服务(初始化、缩放等)
private markerManager: any // 标记服务
private lineManager: any // 路径服务
private polygonManager: any // 区域服务
private geometryManager: any // 几何计算服务
private searchManager: any // 搜索服务
private geocoderManager: any // 地理编码服务
private directionManager: any // 路径规划服务
private coordinateManager: any // 坐标系转换服务
private widgetManager: any // 自定义控件(信息窗等)
🧩 注册模式(Registry Pattern)
动态注册服务,实现按需注入、低耦合、插件化:
ts
class UnifiedProvider implements IMapProvider {
static providerSerivces: Record<
MapProviderEnum,
Record<MapProviderServiceEnum, any>
> = {};
static registerServiceToUnifiedProvider(
mapProvider: MapProviderEnum,
serviceName: MapProviderServiceEnum,
serviceClass: any
) {
if (!this.providerSerivces[mapProvider]) {
this.providerSerivces[mapProvider] = {};
}
this.providerSerivces[mapProvider][serviceName] = serviceClass;
}
}
// 示例:注册高德地图的 baseManager
UnifiedProvider.registerServiceToUnifiedProvider(
"amap",
"baseManager",
BaseManager
);
🧠 策略模式(Strategy Pattern)
在策略模式中,我们可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法 ,在这里,每一个封装算法的类我们都可以称之为一种策略(Strategy),为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做规则的定义,而每种算法则对应于一个具体策略类。
策略模式对应于解决某一问题的一个算法族,允许用户从该算法族中任选一个算法来解决某一问题,同时可以方便地更换算法或者增加新的算法。只要涉及到算法的封装、复用和切换都可以考虑使用策略模式。
以下的策略模式的代码 demo 可能有助于理解。
ts
class WeChatPay {
pay(amount) {
console.log(`使用微信支付:${amount} 元`);
}
}
class Alipay {
pay(amount) {
console.log(`使用支付宝支付:${amount} 元`);
}
}
class PaymentContext {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
pay(amount) {
this.strategy.pay(amount);
}
}
// 使用方式
const context = new PaymentContext(new WeChatPay());
context.pay(100);
context.setStrategy(new Alipay());
context.pay(200);
在地图组件中,我们可以将不同的地图服务(如 AMap、GoogleMap、HuaweiMap)作为策略类,统一通过 UnifiedProvider
管理切换。
🥲自己写的代码用了什么设计模式都不太清楚,已经快忘光了,还是本科学习的一些知识,还好现在有 gpt帮我写代码和分析代码。
📚 参考资源
- 推荐阅读:@LoveLion 的设计模式导学 本科时期的老师写的设计模式教程,对我帮助很大,强烈推荐!🎓