@zero-bits/amap
v1.6.0
Published
amap hooks map utils for React
Readme
@zero-bits/amap
企业级高精度、高拓展性的 React 高德地图 (AMap) 组件库。
基于 @amap/amap-jsapi-loader 与 React Hooks 构建,专注于解决复杂大屏业务、千万级轨迹动画渲染、以及严苛的 React 闭包性能难题。
核心特性
- ⚡️ 极致性能:底层彻底摒弃无脑 Re-render,引入高级引用代理,规避了
instance.on/off重复绑定的性能深渊。 - 🛡️ 内存安全:杜绝了 React
useEffect闭包卸载陷阱,保证一切地图资源按需销毁,实现真正的零内存泄露。 - 🧩 React Portal 挂载:
Marker完美支持复杂的 React JSX 节点渲染,状态流转浑然一体。 - 🚀 海量轨迹渲染:深度封装
PathSimplifier引擎,轻松应对大屏设备上的百万级流光轨迹渲染与巡航动画。 - 📦 异步稳态:所有 Utils 与 SDK Loader 使用稳健的异步状态机,彻底消除异步时序导致的白屏、空指针异常。
安装与引入
安装使用
npm install @zero-bits/amap 或 pnpm add @zero-bits/amap 基础使用指南
1. 基础加载与地图渲染 (Loader & AmapMap)
任何地图组件必须包裹在 Loader 与 AmapMap 内。AmapMap 采用了原生 Context 下发实例,支持随处使用。
import React from 'react';
import { Loader, AmapMap } from '@zero-bits/amap';
export default function App() {
return (
<Loader amapKey="你的_AMAP_KEY" securityCode="你的安全密钥">
<AmapMap
style={{ width: '100vw', height: '100vh' }}
zoom={11}
center={[116.397428, 39.90923]}
viewMode="3D"
mapStyle="amap://styles/dark" // 支持自定义地图样式
>
{/* 其他地图组件放置于此 */}
</AmapMap>
</Loader>
);
}2. 标记点 (Marker)
利用 React Portal 技术,你可以直接将任何 React 组件、甚至是包含状态的组件写在 <Marker> 里,无需拼写 HTML 字符串。
import { Marker } from '@zero-bits/amap';
import { Button } from 'antd'; // 可自由使用 UI 库组件
export default function MyMap() {
return (
<Marker
position={[116.397428, 39.90923]}
offset={[-20, -20]}
onClick={(e) => console.log('Marker被点击', e)}
>
<div className="custom-marker-node">
<div className="pulse-dot" />
<span>北京总部</span>
<Button size="small">查看详情</Button>
</div>
</Marker>
)
}3. 高性能轨迹流光动画 (PathSimplifier)
用于展示交通路网流光、车辆历史轨迹等海量点位巡航功能。
import { useRef, useEffect } from 'react';
import { PathSimplifier } from '@zero-bits/amap';
const trajectoryData = [
{
name: "车辆 A 历史轨迹",
path: [ [116.4, 39.9], [116.45, 39.95], [116.5, 39.9] ]
}
];
export default function Demo() {
const pathRef = useRef(null);
useEffect(() => {
// 你可以直接操作底层导航器实例,控制巡航
pathRef.current?.start('car_1');
}, []);
return (
<PathSimplifier
ref={pathRef}
data={trajectoryData}
getPath={(d) => d.path}
getHoverTitle={(d) => d.name}
autoSetFitView={true}
navigators={[
{ id: 'car_1', pathIndex: 0, loop: true, speed: 5000 }
]}
renderOptions={{
pathLineStyle: { lineWidth: 6, strokeStyle: '#1890ff' }
}}
/>
);
}4. 静态图层 (TileLayer)
轻松引入高德官方内置的路况图、卫星图、路网图,支持响应式切换。
import { TileLayer } from '@zero-bits/amap';
export default function Layers() {
return (
<>
{/* 卫星图 */}
<TileLayer.Satellite zIndex={10} />
{/* 实时路况图 */}
<TileLayer.Traffic autoRefresh={true} interval={180} zIndex={11} />
{/* 路网图层 */}
<TileLayer.RoadNet zIndex={12} />
</>
)
}5. 地图控件 (Control)
内置官方类型控件与完全自定义的 DOM 控件。
import { Control } from '@zero-bits/amap';
export default function Controls() {
return (
<>
{/* 官方自带:地图类型切换 */}
<Control.MapType position="RT" defaultType={1} showRoad={true} />
{/* 自定义控件:悬浮在左下角的图例 */}
<Control.Custom position="LB">
<div style={{ background: '#fff', padding: 12, borderRadius: 4 }}>
<h4>业务图例</h4>
<ul>
<li>🔴 故障设备</li>
<li>🟢 正常设备</li>
</ul>
</div>
</Control.Custom>
</>
)
}6. 基础折线 (Trajectory)
如果你仅需要画一条静态的线而不需要强大的渲染引擎,使用基础的 Trajectory 即可。
import { Trajectory } from '@zero-bits/amap';
<Trajectory
path={[ [116.4, 39.9], [116.45, 39.95] ]}
options={{
strokeColor: '#ff0000',
strokeWeight: 8,
strokeOpacity: 0.8
}}
fitView={true}
/>7. 搜索输入框聚合 (AutoComplete)
高德自动提示搜索服务的 React 封装,传入你的原生 input 元素 ID 即可实现地点搜索。
import { AutoComplete } from '@zero-bits/amap';
export default function SearchBar() {
return (
<div>
<input id="my-search-box" placeholder="请输入地点..." />
<AutoComplete
input="my-search-box"
city="全国"
onSelect={(e) => {
console.log('用户选择了:', e.poi.name, e.poi.location);
}}
/>
</div>
)
}高阶工具函数 Utils
本库不仅提供了组件,还提供了健壮的异步 API 封装。无需操心高德插件是否加载完成,所有方法返回安全的 Promise:
import { search, getAddressByLngLat, districtSearch } from '@zero-bits/amap/src/utils';
async function mapToolsDemo() {
// 1. 根据关键字进行服务搜索
const { status, result } = await search('天安门')
// 2. 逆地理编码(经纬度 -> 真实地址)
const address = await getAddressByLngLat(116.39, 39.9)
// 3. 行政区划查询 (获取省份下辖的城市、边界等)
const boundaries = await districtSearch('北京市')
}8. 点聚合 (MarkerCluster)
对海量点位进行自动聚合,按网格合并邻近 Marker,并支持完全自定义渲染与事件交互。
基础用法
import { MarkerCluster } from '@zero-bits/amap'
const points = [
{ lnglat: [116.397428, 39.90923], id: 1, name: '北京总部' },
{ lnglat: [116.410000, 39.920000], id: 2, name: '朝阳分部' },
{ lnglat: [116.380000, 39.900000], id: 3, name: '西城仓储' }
]
export default function Demo() {
return (
<MarkerCluster
data={points}
gridSize={80}
onClusterClick={({ marker, data }) => {
console.log(`聚合点包含 ${data.length} 个点位`)
}}
onMarkerClick={({ data }) => {
console.log('单点信息', data[0])
}}
/>
)
}自定义渲染
通过 renderMarker 自定义单个点的外观,onClusterClick 可实现点击聚合圆后缩放地图:
import { useRef } from 'react'
import { AmapMap, Loader, MarkerCluster } from '@zero-bits/amap'
const points = [/* ... */]
export default function Demo() {
const handleZoomIn = ({ marker, data }) => {
// 点击聚合圈,地图放大到聚合内容的视野范围
const lnglats = data.map(d => d.lnglat)
map.setFitView(lnglats)
}
const customRenderMarker = ({ marker, data }) => {
const item = data[0]
const el = document.createElement('div')
el.className = 'custom-car-marker'
el.innerHTML = `<img src="${item.iconUrl}" /><span>${item.name}</span>`
marker.setContent(el)
marker.setOffset([-20, -40])
}
return (
<Loader amapKey="你的_AMAP_KEY" securityCode="你的安全密钥">
<AmapMap zoom={11} center={[116.397428, 39.90923]}>
<MarkerCluster
data={points}
gridSize={60}
renderMarker={customRenderMarker}
onClusterClick={handleZoomIn}
onMarkerClick={({ data }) => console.log(data[0])}
/>
</AmapMap>
</Loader>
)
}通过 ref 命令式操作实例
import { useRef } from 'react'
import { MarkerCluster, MarkerClusterRef } from '@zero-bits/amap'
export default function Demo() {
const clusterRef = useRef<MarkerClusterRef>(null)
const handleUpdateData = (newPoints) => {
// 直接操作底层实例刷新数据,无需卸载重建组件
clusterRef.current?.cluster?.setData(newPoints)
}
return (
<MarkerCluster
ref={clusterRef}
data={points}
gridSize={80}
/>
)
}Props
| 属性 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| data | 聚合点位数据 | MarkerClusterDataItem[] | [] |
| gridSize | 聚合网格的像素大小 | number | 80 |
| renderClusterMarker | 自定义聚合点图标的渲染回调 | (context: MarkerClusterContext) => void | - |
| renderMarker | 自定义非聚合的单点图标渲染回调 | (context: MarkerClusterContext) => void | - |
| onClusterClick | 聚合点被点击的回调,接收高德原生事件对象与包含的点位数组 | (e: MarkerClusterEvent, data: MarkerClusterDataItem[]) => void | - |
| onMarkerClick | 单点被点击的回调,接收高德原生事件对象与对应的单点数据 | (e: MarkerClusterEvent, data: MarkerClusterDataItem) => void | - |
注意:MarkerCluster 组件主要负责管理“聚合圈”和“散落点”的展现。如果不传入对应的 render 函数,则会自动使用高德的默认样式。
Types
interface MarkerClusterDataItem {
lnglat: [number, number]
[key: string]: unknown // 可携带任意业务字段
}
interface MarkerClusterContext {
marker: unknown // 高德原生 Marker 实例,可强转使用
data: MarkerClusterDataItem[]
}
interface MarkerClusterEvent {
clusterData: MarkerClusterDataItem[]
lnglat: [number, number] | { lng: number; lat: number }
marker: unknown
[key: string]: unknown
}
interface MarkerClusterInstance {
setData(data: MarkerClusterDataItem[]): void
setGridSize(gridSize: number): void
setMap(map: AMap.Map | null): void
}
interface MarkerClusterRef {
cluster?: MarkerClusterInstance
}9. 次世代超级点聚合 (SuperMarkerCluster)
基于 WebGL 和底层 KD-Tree 算法打造的极限性能聚合组件,专为十万级海量数据设计。内置了“三层防御性物理隔离”与“重绘锁”,彻底根除由高德引擎缓存引起的状态污染、1 像素地理偏移和 WebGL 纹理拉伸。
最强黑科技:一键无缝切换!
你可以直接通过 cluster={boolean} 属性瞬间切换“聚合 / 散点”形态。在散点形态下,组件依然会在底层利用 KD-Tree 提供极速的屏幕视野剔除(BBox Culling),性能吊打所有传统的 React map() 循环!
import { SuperMarkerCluster } from '@zero-bits/amap'
export default function Demo() {
return (
<SuperMarkerCluster
points={vehiclePoints} // 遵循 GeoJSON 格式的数据
cluster={mapState.showCluster} // 🔥 核心:一键无缝开关聚合效果!无需写 if-else!
gridSize={80}
zIndex={111}
collision={false}
renderMarker={(marker) => marker.properties}
onMarkerClick={(_, marker) => handleMarkerClick(marker.properties.extData)}
/>
)
}10. 数据驱动海量图层 (LabelsLayer)
对于完全不需要聚合的海量静态站点(如数万个基站、充电桩),千万不要在 JSX 里使用 Array.map(<LabelMarker>) 循环渲染!这会导致灾难级的 React Virtual DOM 内存爆炸。
升级后的 LabelsLayer 提供了原生级的纯数据驱动引擎,只需通过 markers 属性传入数组,组件便会绕过 React 渲染树,在底层内存瞬间将配置抛入高德 WebGL 渲染管线,性能快如闪电。
import { LabelsLayer } from '@zero-bits/amap'
export default function StaticStations() {
return (
<LabelsLayer
zIndex={110}
collision={false}
animation={true}
markers={stationMarkers} // 直接丢入配置数组,告别 JSX 循环
onMarkerClick={(e, marker) => console.log('点击站点', marker.extData)}
/>
)
}11. 实时流光巡航聚合 (LiveMarkerCluster)
专门为解决“大规模车辆平滑移动 + 实时聚合”复杂场景设计的组件。通过 BBox Diffing 引擎复用实例,并实现平滑移动与按需聚合。
关于“缓动/蠕动”动画的关键参数说明:
animationDuration(平滑位移时间) 控制车辆从 A 点移动到 B 点的过程。如果业务数据是 30 秒推一次,默认的10000(10秒)会导致车辆慢动作“蠕动”,且宏观视角下极难察觉。 👉 最佳实践:将其设为1000或1500毫秒。可以让每次数据刷新时车辆轻快、敏捷地“滑”到新位置,实现类似“手机导航”的灵动感。 (注意:仅当数据的properties.isMoving === true时生效,否则车辆将瞬间跳跃)。animation(图斑显隐动画) 透传给底层引擎,控制车辆初次加载、或由于地图缩放产生聚合/解聚时,图标渐隐渐现(Fade-in)的视觉效果。它不参与控制点到点的行驶位移。
import { LiveMarkerCluster } from '@zero-bits/amap'
export default function LiveMap() {
return (
<LiveMarkerCluster
points={vehicleData}
animationDuration={1500} // 设置为 1.5 秒实现轻快滑动,消除 10 秒默认带来的缓慢蠕动感
animation={true} // 控制图标刚出现时的淡入淡出效果
renderMarker={(marker) => marker.properties}
/>
)
}组件总览
| 组件 | 说明 |
|---|---|
| Loader | 高德 SDK 异步加载器,提供全局 Context |
| AmapMap | 地图容器,所有子组件的宿主 |
| Marker | 支持 JSX 的自定义标注点(React Portal 实现) |
| MarkerCluster | 海量点位自动聚合(基于传统 DOM 引擎) |
| SuperMarkerCluster | 🚀 次世代 WebGL 超级点聚合,十万级点位极速渲染,支持一键无缝散点切换 |
| LabelsLayer | ⚡️ 纯数据驱动的海量图层,彻底剥离 React 开销,T0 级极速渲染 |
| LiveMarkerCluster | 🚗 实时流光巡航聚合,专为大规模车辆平滑移动场景设计 |
| Trajectory | 基础静态折线 |
| PathSimplifier | 高性能轨迹流光动画与巡航引擎 |
| TileLayer.Satellite | 高德官方卫星图层 |
| TileLayer.TiandituSatellite | 天地图卫星图层(需传入 tk,解决海外/边境无卫星图问题) |
| TileLayer.Traffic | 实时路况图层 |
| TileLayer.RoadNet | 路网图层 |
| Control.MapType | 地图类型切换官方控件 |
| Control.Custom | 任意 JSX 自定义悬浮控件 |
| AutoComplete | 高德地点搜索自动提示 |
