我想在 NextJS 14 应用程序中显示传单地图。尝试使其发挥作用会带来以下挑战:
我的依赖项是:
"leaflet": "1.9",
"next": "14",
"react": "18",
"react-leaflet": "4",
最小工作配置的设置是什么?
我在 Stackoverflow 和 Github 上搜索了好的解决方案。我最终找到了我需要知道的一切,但收集所有信息花了一些时间。
因此,我想在一篇文章中提供所有必要的信息。
实例:codesandbox
为了实现此目的,我们需要 leaflet-defaulticon-compatibility。完整的设置如下所示:
npx create-next-app@latest
npm install leaflet react-leaflet leaflet-defaulticon-compatibility
npm install -D @types/leaflet
为
leaflet
和leaflet-defaulticon-compatibility
导入css和js。顺序很重要!创建一个地图组件并确保在其上设置width和height。
组件/Map.tsx
"use client";
// START: Preserve spaces to avoid auto-sorting
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css";
import "leaflet-defaulticon-compatibility";
// END: Preserve spaces to avoid auto-sorting
import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet";
export default function Map() {
return (
<MapContainer
preferCanvas={true}
center={[51.505, -0.09]}
zoom={11}
scrollWheelZoom={true}
style={{ height: "400px", width: "600px" }}
>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[51.505, -0.09]}>
<Popup>
This Marker icon is displayed correctly with <i>leaflet-defaulticon-compatibility</i>.
</Popup>
</Marker>
</MapContainer>
);
}
导入地图组件:
应用程序/page.tsx
"use client";
import dynamic from "next/dynamic";
const LazyMap = dynamic(() => import("@/components/Map"), {
ssr: false,
loading: () => <p>Loading...</p>,
});
export default function Home() {
return (
<main>
<LazyMap />
</main>
);
}
page.tsx
作为服务器组件如果您希望页面成为服务器组件,您可以 将客户端组件沿树向下移动并将数据作为 props 传递。
组件/Map.tsx
"use client";
import { useState } from 'react';
/* ... more imports ... */
export default function Map({ initialData }) {
const [data, setData] = useState(initialData);
/* ... more code ... */
}
组件/MapCaller.tsx
'use client';
import dynamic from 'next/dynamic';
const LazyMap = dynamic(() => import("@/components/Map"), {
ssr: false,
loading: () => <p>Loading...</p>,
});
function MapCaller(props) {
return <LazyMap {...props} />;
}
export default MapCaller;
应用程序/page.tsx
import MapCaller from '@/components/MapCaller';
/* your fetch function that fetches data server side */
import { fetchData } from '@/lib/data';
export default async function Page() {
const data = await fetchData();
return <MapCaller initialData={data} />;
}
我们可以使用简单的
custom SVG
图标,它可以与next.js
兼容。
自定义图标可以为您的地图添加更多控制,以根据您的要求用不同的颜色表示,例如
status
。
这是使用自定义标记的示例用例。
import { useEffect, useRef } from "react";
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import { Box } from "@mui/material";
interface MarkerProps {
lat: number;
lng: number;
status?: string;
popupData?: { [key: string]: any };
}
interface MapProps {
markers: MarkerProps[];
width?: string | number;
height?: string | number;
coordinates?: [number, number];
}
const LeafletMap: React.FC<MapProps> = ({
markers,
width = "100%",
height = 400,
coordinates = [42.505, -1.12],
}) => {
const mapRef = useRef<L.Map | null>(null);
const mapContainerRef = useRef<HTMLDivElement | null>(null);
const markersRef = useRef<L.Marker[]>([]);
const getIcon = (status: string) => {
const baseSvg = `
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 2.16125C7.8 2.16125 4 5.38125 4 10.3612C4 13.5413 6.45 17.2813 11.34 21.5913C11.72 21.9213 12.29 21.9213 12.67 21.5913C17.55 17.2813 20 13.5413 20 10.3612C20 5.38125 16.2 2.16125 12 2.16125ZM12 12.1613C10.9 12.1613 10 11.2613 10 10.1613C10 9.06125 10.9 8.16125 12 8.16125C13.1 8.16125 14 9.06125 14 10.1613C14 11.2613 13.1 12.1613 12 12.1613Z" />
</svg>
`;
const getColor = () => {
switch (status) {
case "Active":
return "blue";
case "Inactive":
return "red";
default:
return "grey";
}
};
const finalSvg = baseSvg.replace('fill="none"', `fill="${getColor()}"`);
return L.icon({
iconUrl: `data:image/svg+xml,${encodeURIComponent(finalSvg)}`,
iconSize: [20, 32], // size of the icon
});
};
useEffect(() => {
if (mapContainerRef.current && !mapRef.current) {
mapRef.current = L.map(mapContainerRef.current, {
attributionControl: false,
}).setView(coordinates, 13);
L.tileLayer(
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
{}
).addTo(mapRef.current);
}
}, []);
useEffect(() => {
if (mapRef.current) {
// To Clear existing markers
markersRef.current.forEach((marker) => {
mapRef.current!.removeLayer(marker);
});
markersRef.current = [];
markers.forEach((markerProps) => {
const marker = L.marker([markerProps.lat, markerProps.lng], {
icon: getIcon(markerProps.status || "default"),
});
if (markerProps.popupData) {
const popupContent = Object.entries(markerProps.popupData)
.map(([key, value]) => `<b>${key}:</b> ${value}`)
.join("<br/>");
marker.bindPopup(popupContent);
}
marker.addTo(mapRef.current!);
markersRef.current.push(marker);
});
}
}, [markers]);
return <Box ref={mapContainerRef} style={{ height, width }} />;
};
export default LeafletMap;
您可以调用该组件并添加道具,如下所示:
<LeafletMap
markers={markers}
width="100%"
height="300px"
coordinates={[51.505, -0.09]}
/>
此输入
markers
道具和其他道具可以根据您的要求进行调整。