高级用法
访问底层 MapLibre GL 地图实例,使用 MapLibre GL JS API 的任何功能。
你可以使用 ref 或useMap hook 访问底层 MapLibre GL 地图实例。
💡 提示:查看MapLibre GL JS 文档获取完整的方法和事件列表。
使用 Ref
访问地图实例最简单的方式。使用 ref 从事件处理器或 effect 中调用地图方法:
MyMapComponent.tsx
123456789101112131415161718
import { Map, type MapRef } from class="tk-string">"@/components/ui/map";
import { useRef } from class="tk-string">"react";
function MyMapComponent() {
const mapRef = useRef<MapRef>(null);
const handleFlyTo = () => {
class=class="tk-string">"tk-comment">// 通过 ref 访问 MapLibre GL 地图实例
mapRef.current?.flyTo({ center: [-74, 40.7], zoom: 12 });
};
return (
<>
<button onClick={handleFlyTo}>飞往纽约</button>
<Map ref={mapRef} center={[-74, 40.7]} zoom={10} />
</>
);
}使用 Hook
对于在 Map 内渲染的子组件,使用 useMap hook 访问地图实例并监听事件:
MapEventListener.tsx
12345678910111213141516171819202122232425
import { Map, useMap } from class="tk-string">"@/components/ui/map";
import { useEffect } from class="tk-string">"react";
class=class="tk-string">"tk-comment">// 对于 Map 内的子组件,使用 useMap hook
function MapEventListener() {
const { map, isLoaded } = useMap();
useEffect(() => {
if (!map || !isLoaded) return;
const handleClick = (e) => {
console.log(class="tk-string">"点击位置:", e.lngLat);
};
map.on(class="tk-string">"click", handleClick);
return () => map.off(class="tk-string">"click", handleClick);
}, [map, isLoaded]);
return null;
}
class=class="tk-string">"tk-comment">// 用法
<Map center={[-74, 40.7]} zoom={10}>
<MapEventListener />
</Map>自定义控件
创建自定义控件来操作地图的倾斜和方向,并监听地图事件显示实时值:
AdvancedUsageExample.tsx
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
class="tk-string">"use client";
import { useEffect, useState } from class="tk-string">"react";
import { Map, useMap } from class="tk-string">"@/components/ui/map";
import { Button } from class="tk-string">"@/components/ui/button";
import { RotateCcw, Mountain } from class="tk-string">"lucide-react";
function MapController() {
const { map, isLoaded } = useMap();
const [pitch, setPitch] = useState(0);
const [bearing, setBearing] = useState(0);
useEffect(() => {
if (!map || !isLoaded) return;
const handleMove = () => {
setPitch(Math.round(map.getPitch()));
setBearing(Math.round(map.getBearing()));
};
map.on(class="tk-string">"move", handleMove);
return () => {
map.off(class="tk-string">"move", handleMove);
};
}, [map, isLoaded]);
const handle3DView = () => {
map?.easeTo({
pitch: 60,
bearing: -20,
duration: 1000,
});
};
const handleReset = () => {
map?.easeTo({
pitch: 0,
bearing: 0,
duration: 1000,
});
};
if (!isLoaded) return null;
return (
<div className=class="tk-string">"absolute top-3 left-3 z-10 flex flex-col gap-2">
<div className=class="tk-string">"flex gap-2">
<Button size=class="tk-string">"sm" variant=class="tk-string">"secondary" onClick={handle3DView}>
<Mountain className=class="tk-string">"mr-1.5 size-4" />
3D 视图
</Button>
<Button size=class="tk-string">"sm" variant=class="tk-string">"secondary" onClick={handleReset}>
<RotateCcw className=class="tk-string">"mr-1.5 size-4" />
重置
</Button>
</div>
<div className=class="tk-string">"bg-background/90 rounded-md border px-3 py-2 font-mono text-xs backdrop-blur">
<div>倾斜:{pitch}°</div>
<div>方向:{bearing}°</div>
</div>
</div>
);
}
export function AdvancedUsageExample() {
return (
<div className=class="tk-string">"h-[420px] w-full">
<Map center={[-73.9857, 40.7484]} zoom={15}>
<MapController />
</Map>
</div>
);
}自定义 GeoJSON 图层
添加自定义 GeoJSON 数据为图层,支持填充和轮廓样式及悬停交互:
CustomLayer.tsx
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
class="tk-string">"use client";
import { useCallback, useEffect, useState } from class="tk-string">"react";
import { Map, MapControls, useMap } from class="tk-string">"@/components/ui/map";
import { Button } from class="tk-string">"@/components/ui/button";
import { Layers, X } from class="tk-string">"lucide-react";
const geojsonData = {
type: class="tk-string">"FeatureCollection" as const,
features: [
{
type: class="tk-string">"Feature" as const,
properties: { name: class="tk-string">"中央公园", type: class="tk-string">"park" },
geometry: {
type: class="tk-string">"Polygon" as const,
coordinates: [[
[-73.9731, 40.7644],
[-73.9819, 40.7681],
[-73.958, 40.8006],
[-73.9493, 40.7969],
[-73.9731, 40.7644],
]],
},
},
],
};
function CustomLayer() {
const { map, isLoaded } = useMap();
const [isLayerVisible, setIsLayerVisible] = useState(false);
const [hoveredPark, setHoveredPark] = useState<string | null>(null);
const addLayers = useCallback(() => {
if (!map) return;
if (!map.getSource(class="tk-string">"parks")) {
map.addSource(class="tk-string">"parks", { type: class="tk-string">"geojson", data: geojsonData });
}
if (!map.getLayer(class="tk-string">"parks-fill")) {
map.addLayer({
id: class="tk-string">"parks-fill",
type: class="tk-string">"fill",
source: class="tk-string">"parks",
paint: {
class="tk-string">"fill-color": class="tk-string">"#22c55e",
class="tk-string">"fill-opacity": 0.4,
},
layout: {
visibility: isLayerVisible ? class="tk-string">"visible" : class="tk-string">"none",
},
});
}
}, [map, isLayerVisible]);
useEffect(() => {
if (!map || !isLoaded) return;
addLayers();
const handleMouseMove = (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: [class="tk-string">"parks-fill"],
});
if (features.length > 0) {
setHoveredPark(features[0].properties?.name || null);
}
};
map.on(class="tk-string">"mousemove", class="tk-string">"parks-fill", handleMouseMove);
return () => {
map.off(class="tk-string">"mousemove", class="tk-string">"parks-fill", handleMouseMove);
};
}, [map, isLoaded, isLayerVisible]);
const toggleLayer = () => {
if (!map) return;
const visibility = isLayerVisible ? class="tk-string">"none" : class="tk-string">"visible";
map.setLayoutProperty(class="tk-string">"parks-fill", class="tk-string">"visibility", visibility);
setIsLayerVisible(!isLayerVisible);
};
return (
<>
<div className=class="tk-string">"absolute top-3 left-3 z-10">
<Button size=class="tk-string">"sm" variant={isLayerVisible ? class="tk-string">"default" : class="tk-string">"secondary"} onClick={toggleLayer}>
{isLayerVisible ? <X className=class="tk-string">"mr-1.5 size-4" /> : <Layers className=class="tk-string">"mr-1.5 size-4" />}
{isLayerVisible ? class="tk-string">"隐藏公园" : class="tk-string">"显示公园"}
</Button>
</div>
{hoveredPark && (
<div className=class="tk-string">"bg-background/90 absolute bottom-3 left-3 z-10 rounded-md border px-3 py-2 text-sm font-medium backdrop-blur">
{hoveredPark}
</div>
)}
</>
);
}通过图层渲染标记(性能优化)
当需要显示数百或数千个标记时,使用 GeoJSON 图层代替基于 DOM 的MapMarker 组件。 这种方式在 WebGL 画布上渲染标记,性能显著提升。
MarkersLayer.tsx
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
class="tk-string">"use client";
import { useEffect, useState, useId } from class="tk-string">"react";
import { Map, MapPopup, useMap } from class="tk-string">"@/components/ui/map";
function MarkersLayer() {
const { map, isLoaded } = useMap();
const id = useId();
const sourceId = class="tk-string">`markers-source-${id}`;
const layerId = class="tk-string">`markers-layer-${id}`;
const [selectedPoint, setSelectedPoint] = useState(null);
useEffect(() => {
if (!map || !isLoaded) return;
map.addSource(sourceId, {
type: class="tk-string">"geojson",
data: pointsData, class=class="tk-string">"tk-comment">// 包含 200 个点的 FeatureCollection
});
map.addLayer({
id: layerId,
type: class="tk-string">"circle",
source: sourceId,
paint: {
class="tk-string">"circle-radius": 6,
class="tk-string">"circle-color": class="tk-string">"#3b82f6",
class="tk-string">"circle-stroke-width": 2,
class="tk-string">"circle-stroke-color": class="tk-string">"#ffffff",
},
});
const handleClick = (e) => {
if (!e.features?.length) return;
const feature = e.features[0];
const coords = feature.geometry.coordinates;
setSelectedPoint({
id: feature.properties?.id,
name: feature.properties?.name,
coordinates: coords,
});
};
map.on(class="tk-string">"click", layerId, handleClick);
return () => {
map.off(class="tk-string">"click", layerId, handleClick);
if (map.getLayer(layerId)) map.removeLayer(layerId);
if (map.getSource(sourceId)) map.removeSource(sourceId);
};
}, [map, isLoaded]);
return (
<>
{selectedPoint && (
<MapPopup
longitude={selectedPoint.coordinates[0]}
latitude={selectedPoint.coordinates[1]}
onClose={() => setSelectedPoint(null)}
closeButton
>
<div className=class="tk-string">"min-w-24">
<p className=class="tk-string">"font-medium">{selectedPoint.name}</p>
</div>
</MapPopup>
)}
</>
);
}