1. 项目背景与核心价值
去年接手一个跨平台项目时,我遇到了一个典型痛点:需要在H5、微信小程序和App三端实现统一的定位和地址解析功能。当时用传统方式为每个平台分别写定位代码,不仅重复劳动,还产生了近30%的差异性问题。这个经历促使我沉淀出了今天要分享的这套通用Hook方案。
这套方案的核心价值在于:
- 一次开发三端通用(H5/小程序/App)
- 封装完整的定位→坐标→地址的完整链路
- 内置错误降级和兼容处理机制
- 提供TypeScript智能提示
- 性能开销降低40%(实测数据)
2. 技术架构设计
2.1 整体实现思路
采用分层架构设计,自下而上分为:
- 设备层:抹平各平台定位API差异
- 服务层:统一逆地理编码服务调用
- Hook层:提供响应式数据和方法
- 业务层:最终用户调用的简洁接口
// 架构示意图 interface LocationHook { position: Ref<Position> address: Ref<Address> loading: Ref<boolean> getLocation: () => Promise<void> }2.2 关键技术选型
定位服务:
- H5:浏览器Geolocation API
- 小程序:wx.getLocation
- App:uni.getLocation
逆地理编码:
- 高德Web API(H5)
- 腾讯地图SDK(小程序)
- 原生地图模块(App)
状态管理:
- Vue3的ref/reactive
- 自动清理的副作用管理
3. 核心实现细节
3.1 多端定位统一封装
创建useUniLocation基础Hook处理平台差异:
const getPlatformLocation = async () => { // 环境检测逻辑 if (isH5) { return await getH5Location() } else if (isMP) { return await getMpLocation() } else { return await getAppLocation() } } // 统一错误处理装饰器 const withErrorHandling = (fn: LocationFn) => { return async (...args) => { try { return await fn(...args) } catch (e) { // 统一错误转换 throw convertError(e) } } }3.2 逆地理编码智能路由
根据运行环境自动选择最优服务:
const getGeocoder = () => { if (isH5) { return new AMapGeocoder() } else if (isMP) { return new QqMapGeocoder() } else { return new NativeGeocoder() } } // 坐标转换统一处理 const transformCoordinates = (position) => { // GCJ-02/WGS84转换逻辑 return unifiedCoord }3.3 性能优化要点
缓存策略:
- 定位结果本地缓存(5分钟有效期)
- 地址解析结果持久化缓存
节流控制:
const lastCallTime = ref(0) const getLocation = throttle(async () => { if (Date.now() - lastCallTime.value < 5000) return // 定位逻辑... }, 5000)按需加载: 动态加载地图SDK:
const loadSDK = () => import('./sdk/map')
4. 完整Hook实现
4.1 核心代码结构
export function useLocation() { const position = ref<Position>() const address = ref<Address>() const loading = ref(false) const error = ref<Error>() const getLocation = async () => { loading.value = true try { position.value = await getPlatformLocation() address.value = await reverseGeocode(position.value) } catch (e) { error.value = e } finally { loading.value = false } } return { position, address, loading, error, getLocation } }4.2 业务层使用示例
<script setup> const { position, address, loading, getLocation } = useLocation() </script> <template> <button @click="getLocation"> {{ loading ? '定位中...' : '获取位置' }} </button> <div v-if="address"> {{ address.province }}{{ address.city }}{{ address.district }} </div> </template>5. 踩坑与解决方案
5.1 典型问题排查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| H5定位失败 | 非HTTPS环境 | 启用本地开发白名单 |
| 小程序无响应 | 未申请权限 | 检查app.json配置 |
| 地址解析为空 | 坐标系不匹配 | 统一转GCJ-02 |
| 多次调用报错 | 并发限制 | 增加请求锁机制 |
5.2 特别注意事项
iOS定位精度:
- 需要处理
kCLLocationAccuracyBest配置 - 注意AppStore的隐私政策要求
- 需要处理
Android后台定位:
<!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>微信小程序限制:
- 单个页面不能重复调用getLocation
- 需要绑定安全域名
6. 扩展能力
6.1 自定义服务端点
支持传入自定义的逆地理编码服务:
const { address } = useLocation({ geocoder: async (coord) => { return await yourCustomService(coord) } })6.2 类型扩展
增强地址数据类型支持:
declare module './types' { interface Address { township?: string street?: string streetNumber?: string } }这套方案已在生产环境稳定运行8个月,日均调用量超过20万次。最关键的是将多端定位开发效率提升了60%以上,特别适合需要快速实现定位功能的uni-app项目。