带有 next.js 的传单?
Leaflet with next.js?
我收到一个 ReferenceError:
window is not defined when using next.js with leaflet.js .
想知道是否有解决此问题的简单方法 - 使用 next.js 是否会使我的工作流程过于复杂?
对于那些对确切代码感兴趣的人,
import React, { createRef, Component } from "react";
import L from "leaflet";
import { Map, TileLayer, Marker, Popup, DivOverlay } from "react-leaflet";
import axios from "axios";
import Header from "./Header";
export default class PDXMap extends Component {
state = {
hasLocation: false,
latlng: {
lat: 45.5127,
lng: -122.679565
},
geoJSON: null
};
mapRef = createRef();
componentDidMount() {
this.addLegend();
if (!this.state.hasLocation) {
this.mapRef.current.leafletElement.locate({
setView: true
});
}
axios
.get(
"https://opendata.arcgis.com/datasets/40151125cedd49f09d211b48bb33f081_183.geojson"
)
.then(data => {
const geoJSONData = data.data;
this.setState({ geoJSON: geoJSONData });
return L.geoJSON(this.state.geoJSON).addTo(
this.mapRef.current.leafletElement
);
});
}
handleClick = () => {
this.mapRef.current.leafletElement.locate();
};
handleLocationFound = e => {
console.log(e);
this.setState({
hasLocation: true,
latlng: e.latlng
});
};
getGeoJsonStyle = (feature, layer) => {
return {
color: "#006400",
weight: 10,
opacity: 0.5
};
};
addLegend = () => {
const map = this.mapRef.current.leafletElement;
L.Control.Watermark = L.Control.extend({
onAdd: function(map) {
var img = L.DomUtil.create("img");
img.src = "https://leafletjs.com/docs/images/logo.png";
img.style.width = "200px";
return img;
}
});
L.control.watermark = function(opts) {
return new L.Control.Watermark(opts);
};
L.control.watermark({ position: "bottomleft" }).addTo(map);
};
render() {
const marker = this.state.hasLocation ? (
<Marker position={this.state.latlng}>
<Popup>
<span>You are here</span>
</Popup>
</Marker>
) : null;
return (
<Map
className="map-element"
center={this.state.latlng}
length={4}
onClick={this.handleClick}
setView={true}
onLocationfound={this.handleLocationFound}
ref={this.mapRef}
zoom={14}
>
<TileLayer
attribution='&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{marker}
</Map>
);
}
}
/**
* TODO: Add Header + Legend to map
* - Header to be styled
* - Legend to be present in header
*
*/
import React from 'react';
import PDXMap from "../components/map";
export default function SignIn() {
const classes = useStyles();
return (
<PDXMap/>
);
}
我很乐意使用任何方式 - 只是对获得功能性产品感兴趣。
干杯!
更新
大家好,
我仍然遇到这个错误(比我计划的晚回来了哈哈)。
我目前正在将这种方法与 useEffects 结合使用,
import React, {useEffect, useState} from 'react';
function RenderCompleted() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true)
return () => {
setMounted(false)
}
});
return mounted;
}
export default RenderCompleted;
这是它显示的页面
import React, { useEffect } from "react";
import Router, { useRouter } from "next/router";
import { useRef, useState } from "react";
//viz
import PDXMap from "../../components/Visualization/GIS/map";
import RenderCompleted from "../../components/utils/utils";
// import fetch from 'isomorphic-unfetch';
import { Cookies, CookiesProvider } from "react-cookie";
const cookies = new Cookies();
//containers
// Layouts
import Layout from "../../components/Layout/Layout_example";
import Chart from "../../components/Visualization/Graphs/Chart";
import Table from "../../components/Visualization/Tables/Table";
import Sidebar from "../../components/Layout/Sidebar/SidebarProperty";
export default function Bargains() {
// const [inbrowser, setBrowser] = useState(false);
const choiceRef = useRef<any>();
const [message, setMessage] = useState<any>(null);
const [productList, setProductList] = useState<any>([]);
const [searched, setSearched] = useState(false);
const router = useRouter();
let token = cookies.get("token");
// useEffect(() => {
// setBrowser(true);
// });
const isMounted = RenderCompleted();
const columns = React.useMemo(
() => [
....
],
[]
)
async function handleChoice() {
console.log("searching...", choiceRef.current?.value);
setMessage("Searching...");
var headers = {
"Content-Type": "application/x-www-form-urlencoded",
"auth-token": token,
};
fetch(
....
}
<div className="flex flex-wrap ">
{isMounted && <PDXMap/>}
<Table columns={columns as any} data={productList as any} />
</div>
</div>
</div>
</div>
</Layout>
)
}
与
相同的错误信息
ReferenceError: window is not defined
##更新二
好吧,奇怪的是,当我从另一个页面浏览到站点时它确实有效,但当我加载页面本身时却无效。
会考虑一下,但也许是因为地图正在使用 componentDidMount() 加载数据并且交互很奇怪?
更新
好的,我已经基于 https://github.com/rajeshdh/react-leaflet-with-nextjs
创建了一个更简单的示例
现在正在加载,但图块显示不正确,有些图块未加载。
这就是我为了简单起见使用的地图组件,
import React, { Component, createRef } from 'react';
import { Map, TileLayer, Marker, Popup, MapControl, withLeaflet } from 'react-leaflet';
import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch';
class SearchBox extends MapControl {
constructor(props) {
super(props);
props.leaflet.map.on('geosearch/showlocation', (e) => props.updateMarker(e));
}
createLeafletElement() {
const searchEl = GeoSearchControl({
provider: new OpenStreetMapProvider(),
style: 'bar',
showMarker: true,
showPopup: false,
autoClose: true,
retainZoomLevel: false,
animateZoom: true,
keepResult: false,
searchLabel: 'search'
});
return searchEl;
}
}
export default class MyMap extends Component {
state = {
center: {
lat: 31.698956,
lng: 76.732407,
},
marker: {
lat: 31.698956,
lng: 76.732407,
},
zoom: 13,
draggable: true,
}
refmarker = createRef(this.state.marker)
toggleDraggable = () => {
this.setState({ draggable: !this.state.draggable });
}
updateMarker = (e) => {
// const marker = e.marker;
this.setState({
marker: e.marker.getLatLng(),
});
console.log(e.marker.getLatLng());
}
updatePosition = () => {
const marker = this.refmarker.current;
if (marker != null) {
this.setState({
marker: marker.leafletElement.getLatLng(),
});
}
console.log(marker.leafletElement.getLatLng());
}
render() {
const position = [this.state.center.lat, this.state.center.lng];
const markerPosition = [this.state.marker.lat, this.state.marker.lng];
const SearchBar = withLeaflet(SearchBox);
return (
<div className="map-root">
<Map center={position} zoom={this.state.zoom} style={{
height:"700px"
}}>
<TileLayer
attribution='&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker
draggable={true}
onDragend={this.updatePosition}
position={markerPosition}
animate={true}
ref={this.refmarker}>
<Popup minWidth={90}>
<span onClick={this.toggleDraggable}>
{this.state.draggable ? 'DRAG MARKER' : 'MARKER FIXED'}
</span>
</Popup>
</Marker>
<SearchBar updateMarker={this.updateMarker} />
</Map>
<style jsx>{`
.map-root {
height: 100%;
}
.leaflet-container {
height: 400px !important;
width: 80%;
margin: 0 auto;
}
`}
</style>
</div>
);
}
}
我正在使用这个,
const SimpleExample = dynamic(() => import("../../components/Visualization/GIS/map"), {
ssr: false
});
并且尝试过这个
{已安装 && }
window
在 SSR 中不可用,您可能在 SSR 环境中遇到此错误。
解决此问题的一种方法是在浏览器中加载组件时进行标记(通过使用 componentDidMount
方法),然后才呈现您的 window
所需组件。
class MyComp extends React.Component {
state = {
inBrowser: false,
};
componentDidMount() {
this.setState({ inBrowser: true });
}
render() {
if (!this.state.inBrowser) {
return null;
}
return <YourRegularComponent />;
}
}
这会起作用,因为 componentDidMount
生命周期方法在 浏览器 中仅被调用 。
编辑 - 添加“挂钩”方式
import { useEffect, useState } from 'react';
const MyComp = () => {
const [isBrowser, setIsBrowser] = useState(false);
useEffect(() => {
setIsBrowser(true);
}, []);
if (!isBrowser) {
return null;
}
return <YourRegularComponent />;
};
useEffect
钩子是 componentDidMount
的替代品,它只在浏览器中运行。
创建文件loader.js,将代码放在下面:
export const canUseDOM = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
if (canUseDOM) {
//example how to load jquery in next.js;
window.$ = window.jQuery = require('jquery');
}
在任何组件或页面中导入文件
import {canUseDOM} from "../../utils/loader";
{canUseDOM && <FontAwesomeIcon icon={['fal', 'times']} color={'#4a4a4a'}/>}
或挂钩版本
import React, {useEffect, useState} from 'react';
function RenderCompleted() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true)
return () => {
setMounted(false)
}
});
return mounted;
}
export default RenderCompleted;
调用挂钩:
const isMounted = RenderCompleted();
{isMounted && <FontAwesomeIcon icon={['fal', 'times']} color={'#4a4a4a'}/>}
let divIcon, Map, TileLayer, Marker, Popup;
// first you must check your environment
if (process.browser) {
// then you can import
divIcon = require('leaflet').divIcon;
Map = require('react-leaflet').Map;
TileLayer = require('react-leaflet').TileLayer;
Marker = require('react-leaflet').Marker;
Popup = require('react-leaflet').Popup;
}
由于您错过了 leaflet.css,图块显示不正确。从官方网站下载并添加到您的项目中。突然间,地图图块将正确显示。好吧,您不会看到任何东西,因为默认容器大小为 0,0。要修复它,请给容器一个大小,将其添加到您的 styles.css:
.leaflet-container {
height: 450px;
width: 100%;
}
2020 年的答案
我也有这个问题,在我自己的项目中解决了,所以我想我会分享我所做的。
NextJS 可以动态加载库并限制该事件,因此它不会在服务器端渲染期间发生。有关详细信息,请参阅 documentation。
在我下面的示例中,我将使用和修改来自 NextJS 10.0 和 React-Leaflet 3.0 的文档网站的示例代码。
旁注:如果您使用 TypeScript,请确保安装 @types/leaflet
,否则您会在 center
和 attribution
属性上遇到编译错误。
首先,我将 react-leaflet
代码拆分成一个单独的组件文件,如下所示:
import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
const Map = () => {
return (
<MapContainer center={[51.505, -0.09]} zoom={13} scrollWheelZoom={false} style={{height: 400, width: "100%"}}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[51.505, -0.09]}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</MapContainer>
)
}
export default Map
我将该文件命名为 map.tsx
并将其放在名为 components
的文件夹中,但如果您不使用 TypeScript,则可以将其命名为 map.jsx
。
注意:请务必将这段代码放在一个单独的文件中,而不是将其嵌入到您的页面中,否则您仍然会遇到 window undefined
错误。
另请注意:不要忘记指定 MapContainer
组件的样式,这样它就不会在 height/width 中呈现为零像素。在上面的示例中,我为此添加了属性 style={{height: 400, width: "100%"}}
。
现在要使用该组件,请像这样利用 NextJS 的动态加载:
import dynamic from 'next/dynamic'
function HomePage() {
const Map = dynamic(
() => import('@components/map'), // replace '@components/map' with your component's location
{ ssr: false } // This line is important. It's what prevents server-side render
)
return <Map />
}
export default HomePage
如果您希望地图在加载时被其他东西替换(可能是一个好习惯),您应该像这样使用动态函数的加载 属性:
import dynamic from 'next/dynamic'
function HomePage() {
const Map = dynamic(
() => import('@components/map'), // replace '@components/map' with your component's location
{
loading: () => <p>A map is loading</p>,
ssr: false // This line is important. It's what prevents server-side render
}
)
return <Map />
}
export default HomePage
Adrian Ciura 评论了您的组件 re-render 可能出现的闪烁,即使地图没有任何变化。他们建议使用新的 React.useMemo
钩子来解决这个问题。如果这样做,您的代码可能如下所示:
import React from 'react'
import dynamic from 'next/dynamic'
function HomePage() {
const Map = React.useMemo(() => dynamic(
() => import('@components/map'), // replace '@components/map' with your component's location
{
loading: () => <p>A map is loading</p>,
ssr: false // This line is important. It's what prevents server-side render
}
), [/* list variables which should trigger a re-render here */])
return <Map />
}
export default HomePage
希望对您有所帮助。如果 react-leaflet
对 window
的存在进行测试会更容易,这样它就可以优雅地失败,但在此之前此解决方法应该有效。
最有效的方法是由@FlippingBinary 建议的我只需要争辩使用不同的 useMemo
功能。由于 useMemo
不一致以防止在函数内部使用时重新渲染,因此您必须单独使用它。这只适用于我
vloz.tsx === 页数
import React, { Suspense, lazy } from 'react';
const Vloz: React.FC = () => {
const [position, setPosition] = useState<number[] | undefined>();
const AddAd = lazy(() => import('@components/maps/AddAd'));
const MemoMap = React.useMemo(() => {
return (
<Suspense fallback={<p>Loading</p>}>
<AddAd position={position as LatLngTuple} setPosition={setPosition} />
</Suspense>
);
}, [position]);
return (
<div>
{MemoMap}
</div>
);
};
其他组件通常按照其他教程的建议使用。
这种方法将保持 Map 组件呈现直到 position
变化
任何从 leaflet 或 react-leaflet 导入的组件都应该使用选项 ssr false 动态导入。
import dynamic from 'next/dynamic';
const MyAwesomeMap = dynamic(() => import('components/MyAwesomeMap'), { ssr: false });
Leaflet 认为它超出了他们的范围,因为他们只对 vanilla JS 环境中发生的问题提供支持,所以他们不会修复(到目前为止)
如果有人遇到地图组件在所有页面之间共享的情况,并且您遇到路线更改时地图组件重新呈现的问题(在页面之间切换时),您必须在 _app.js
(使用接受的答案中解释的技术)并考虑使用 shallow routing
我收到一个 ReferenceError:
window is not defined when using next.js with leaflet.js .
想知道是否有解决此问题的简单方法 - 使用 next.js 是否会使我的工作流程过于复杂?
对于那些对确切代码感兴趣的人,
import React, { createRef, Component } from "react";
import L from "leaflet";
import { Map, TileLayer, Marker, Popup, DivOverlay } from "react-leaflet";
import axios from "axios";
import Header from "./Header";
export default class PDXMap extends Component {
state = {
hasLocation: false,
latlng: {
lat: 45.5127,
lng: -122.679565
},
geoJSON: null
};
mapRef = createRef();
componentDidMount() {
this.addLegend();
if (!this.state.hasLocation) {
this.mapRef.current.leafletElement.locate({
setView: true
});
}
axios
.get(
"https://opendata.arcgis.com/datasets/40151125cedd49f09d211b48bb33f081_183.geojson"
)
.then(data => {
const geoJSONData = data.data;
this.setState({ geoJSON: geoJSONData });
return L.geoJSON(this.state.geoJSON).addTo(
this.mapRef.current.leafletElement
);
});
}
handleClick = () => {
this.mapRef.current.leafletElement.locate();
};
handleLocationFound = e => {
console.log(e);
this.setState({
hasLocation: true,
latlng: e.latlng
});
};
getGeoJsonStyle = (feature, layer) => {
return {
color: "#006400",
weight: 10,
opacity: 0.5
};
};
addLegend = () => {
const map = this.mapRef.current.leafletElement;
L.Control.Watermark = L.Control.extend({
onAdd: function(map) {
var img = L.DomUtil.create("img");
img.src = "https://leafletjs.com/docs/images/logo.png";
img.style.width = "200px";
return img;
}
});
L.control.watermark = function(opts) {
return new L.Control.Watermark(opts);
};
L.control.watermark({ position: "bottomleft" }).addTo(map);
};
render() {
const marker = this.state.hasLocation ? (
<Marker position={this.state.latlng}>
<Popup>
<span>You are here</span>
</Popup>
</Marker>
) : null;
return (
<Map
className="map-element"
center={this.state.latlng}
length={4}
onClick={this.handleClick}
setView={true}
onLocationfound={this.handleLocationFound}
ref={this.mapRef}
zoom={14}
>
<TileLayer
attribution='&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{marker}
</Map>
);
}
}
/**
* TODO: Add Header + Legend to map
* - Header to be styled
* - Legend to be present in header
*
*/
import React from 'react';
import PDXMap from "../components/map";
export default function SignIn() {
const classes = useStyles();
return (
<PDXMap/>
);
}
我很乐意使用任何方式 - 只是对获得功能性产品感兴趣。
干杯!
更新
大家好,
我仍然遇到这个错误(比我计划的晚回来了哈哈)。
我目前正在将这种方法与 useEffects 结合使用,
import React, {useEffect, useState} from 'react';
function RenderCompleted() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true)
return () => {
setMounted(false)
}
});
return mounted;
}
export default RenderCompleted;
这是它显示的页面
import React, { useEffect } from "react";
import Router, { useRouter } from "next/router";
import { useRef, useState } from "react";
//viz
import PDXMap from "../../components/Visualization/GIS/map";
import RenderCompleted from "../../components/utils/utils";
// import fetch from 'isomorphic-unfetch';
import { Cookies, CookiesProvider } from "react-cookie";
const cookies = new Cookies();
//containers
// Layouts
import Layout from "../../components/Layout/Layout_example";
import Chart from "../../components/Visualization/Graphs/Chart";
import Table from "../../components/Visualization/Tables/Table";
import Sidebar from "../../components/Layout/Sidebar/SidebarProperty";
export default function Bargains() {
// const [inbrowser, setBrowser] = useState(false);
const choiceRef = useRef<any>();
const [message, setMessage] = useState<any>(null);
const [productList, setProductList] = useState<any>([]);
const [searched, setSearched] = useState(false);
const router = useRouter();
let token = cookies.get("token");
// useEffect(() => {
// setBrowser(true);
// });
const isMounted = RenderCompleted();
const columns = React.useMemo(
() => [
....
],
[]
)
async function handleChoice() {
console.log("searching...", choiceRef.current?.value);
setMessage("Searching...");
var headers = {
"Content-Type": "application/x-www-form-urlencoded",
"auth-token": token,
};
fetch(
....
}
<div className="flex flex-wrap ">
{isMounted && <PDXMap/>}
<Table columns={columns as any} data={productList as any} />
</div>
</div>
</div>
</div>
</Layout>
)
}
与
相同的错误信息ReferenceError: window is not defined
##更新二
好吧,奇怪的是,当我从另一个页面浏览到站点时它确实有效,但当我加载页面本身时却无效。
会考虑一下,但也许是因为地图正在使用 componentDidMount() 加载数据并且交互很奇怪?
更新
好的,我已经基于 https://github.com/rajeshdh/react-leaflet-with-nextjs
创建了一个更简单的示例现在正在加载,但图块显示不正确,有些图块未加载。
这就是我为了简单起见使用的地图组件,
import React, { Component, createRef } from 'react';
import { Map, TileLayer, Marker, Popup, MapControl, withLeaflet } from 'react-leaflet';
import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch';
class SearchBox extends MapControl {
constructor(props) {
super(props);
props.leaflet.map.on('geosearch/showlocation', (e) => props.updateMarker(e));
}
createLeafletElement() {
const searchEl = GeoSearchControl({
provider: new OpenStreetMapProvider(),
style: 'bar',
showMarker: true,
showPopup: false,
autoClose: true,
retainZoomLevel: false,
animateZoom: true,
keepResult: false,
searchLabel: 'search'
});
return searchEl;
}
}
export default class MyMap extends Component {
state = {
center: {
lat: 31.698956,
lng: 76.732407,
},
marker: {
lat: 31.698956,
lng: 76.732407,
},
zoom: 13,
draggable: true,
}
refmarker = createRef(this.state.marker)
toggleDraggable = () => {
this.setState({ draggable: !this.state.draggable });
}
updateMarker = (e) => {
// const marker = e.marker;
this.setState({
marker: e.marker.getLatLng(),
});
console.log(e.marker.getLatLng());
}
updatePosition = () => {
const marker = this.refmarker.current;
if (marker != null) {
this.setState({
marker: marker.leafletElement.getLatLng(),
});
}
console.log(marker.leafletElement.getLatLng());
}
render() {
const position = [this.state.center.lat, this.state.center.lng];
const markerPosition = [this.state.marker.lat, this.state.marker.lng];
const SearchBar = withLeaflet(SearchBox);
return (
<div className="map-root">
<Map center={position} zoom={this.state.zoom} style={{
height:"700px"
}}>
<TileLayer
attribution='&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker
draggable={true}
onDragend={this.updatePosition}
position={markerPosition}
animate={true}
ref={this.refmarker}>
<Popup minWidth={90}>
<span onClick={this.toggleDraggable}>
{this.state.draggable ? 'DRAG MARKER' : 'MARKER FIXED'}
</span>
</Popup>
</Marker>
<SearchBar updateMarker={this.updateMarker} />
</Map>
<style jsx>{`
.map-root {
height: 100%;
}
.leaflet-container {
height: 400px !important;
width: 80%;
margin: 0 auto;
}
`}
</style>
</div>
);
}
}
我正在使用这个,
const SimpleExample = dynamic(() => import("../../components/Visualization/GIS/map"), {
ssr: false
});
并且尝试过这个 {已安装 && }
window
在 SSR 中不可用,您可能在 SSR 环境中遇到此错误。
解决此问题的一种方法是在浏览器中加载组件时进行标记(通过使用 componentDidMount
方法),然后才呈现您的 window
所需组件。
class MyComp extends React.Component {
state = {
inBrowser: false,
};
componentDidMount() {
this.setState({ inBrowser: true });
}
render() {
if (!this.state.inBrowser) {
return null;
}
return <YourRegularComponent />;
}
}
这会起作用,因为 componentDidMount
生命周期方法在 浏览器 中仅被调用 。
编辑 - 添加“挂钩”方式
import { useEffect, useState } from 'react';
const MyComp = () => {
const [isBrowser, setIsBrowser] = useState(false);
useEffect(() => {
setIsBrowser(true);
}, []);
if (!isBrowser) {
return null;
}
return <YourRegularComponent />;
};
useEffect
钩子是 componentDidMount
的替代品,它只在浏览器中运行。
创建文件loader.js,将代码放在下面:
export const canUseDOM = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
if (canUseDOM) {
//example how to load jquery in next.js;
window.$ = window.jQuery = require('jquery');
}
在任何组件或页面中导入文件
import {canUseDOM} from "../../utils/loader";
{canUseDOM && <FontAwesomeIcon icon={['fal', 'times']} color={'#4a4a4a'}/>}
或挂钩版本
import React, {useEffect, useState} from 'react';
function RenderCompleted() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true)
return () => {
setMounted(false)
}
});
return mounted;
}
export default RenderCompleted;
调用挂钩:
const isMounted = RenderCompleted();
{isMounted && <FontAwesomeIcon icon={['fal', 'times']} color={'#4a4a4a'}/>}
let divIcon, Map, TileLayer, Marker, Popup;
// first you must check your environment
if (process.browser) {
// then you can import
divIcon = require('leaflet').divIcon;
Map = require('react-leaflet').Map;
TileLayer = require('react-leaflet').TileLayer;
Marker = require('react-leaflet').Marker;
Popup = require('react-leaflet').Popup;
}
由于您错过了 leaflet.css,图块显示不正确。从官方网站下载并添加到您的项目中。突然间,地图图块将正确显示。好吧,您不会看到任何东西,因为默认容器大小为 0,0。要修复它,请给容器一个大小,将其添加到您的 styles.css:
.leaflet-container {
height: 450px;
width: 100%;
}
2020 年的答案
我也有这个问题,在我自己的项目中解决了,所以我想我会分享我所做的。
NextJS 可以动态加载库并限制该事件,因此它不会在服务器端渲染期间发生。有关详细信息,请参阅 documentation。
在我下面的示例中,我将使用和修改来自 NextJS 10.0 和 React-Leaflet 3.0 的文档网站的示例代码。
旁注:如果您使用 TypeScript,请确保安装 @types/leaflet
,否则您会在 center
和 attribution
属性上遇到编译错误。
首先,我将 react-leaflet
代码拆分成一个单独的组件文件,如下所示:
import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
const Map = () => {
return (
<MapContainer center={[51.505, -0.09]} zoom={13} scrollWheelZoom={false} style={{height: 400, width: "100%"}}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[51.505, -0.09]}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</MapContainer>
)
}
export default Map
我将该文件命名为 map.tsx
并将其放在名为 components
的文件夹中,但如果您不使用 TypeScript,则可以将其命名为 map.jsx
。
注意:请务必将这段代码放在一个单独的文件中,而不是将其嵌入到您的页面中,否则您仍然会遇到 window undefined
错误。
另请注意:不要忘记指定 MapContainer
组件的样式,这样它就不会在 height/width 中呈现为零像素。在上面的示例中,我为此添加了属性 style={{height: 400, width: "100%"}}
。
现在要使用该组件,请像这样利用 NextJS 的动态加载:
import dynamic from 'next/dynamic'
function HomePage() {
const Map = dynamic(
() => import('@components/map'), // replace '@components/map' with your component's location
{ ssr: false } // This line is important. It's what prevents server-side render
)
return <Map />
}
export default HomePage
如果您希望地图在加载时被其他东西替换(可能是一个好习惯),您应该像这样使用动态函数的加载 属性:
import dynamic from 'next/dynamic'
function HomePage() {
const Map = dynamic(
() => import('@components/map'), // replace '@components/map' with your component's location
{
loading: () => <p>A map is loading</p>,
ssr: false // This line is important. It's what prevents server-side render
}
)
return <Map />
}
export default HomePage
Adrian Ciura 评论了您的组件 re-render 可能出现的闪烁,即使地图没有任何变化。他们建议使用新的 React.useMemo
钩子来解决这个问题。如果这样做,您的代码可能如下所示:
import React from 'react'
import dynamic from 'next/dynamic'
function HomePage() {
const Map = React.useMemo(() => dynamic(
() => import('@components/map'), // replace '@components/map' with your component's location
{
loading: () => <p>A map is loading</p>,
ssr: false // This line is important. It's what prevents server-side render
}
), [/* list variables which should trigger a re-render here */])
return <Map />
}
export default HomePage
希望对您有所帮助。如果 react-leaflet
对 window
的存在进行测试会更容易,这样它就可以优雅地失败,但在此之前此解决方法应该有效。
最有效的方法是由@FlippingBinary 建议的我只需要争辩使用不同的 useMemo
功能。由于 useMemo
不一致以防止在函数内部使用时重新渲染,因此您必须单独使用它。这只适用于我
vloz.tsx === 页数
import React, { Suspense, lazy } from 'react';
const Vloz: React.FC = () => {
const [position, setPosition] = useState<number[] | undefined>();
const AddAd = lazy(() => import('@components/maps/AddAd'));
const MemoMap = React.useMemo(() => {
return (
<Suspense fallback={<p>Loading</p>}>
<AddAd position={position as LatLngTuple} setPosition={setPosition} />
</Suspense>
);
}, [position]);
return (
<div>
{MemoMap}
</div>
);
};
其他组件通常按照其他教程的建议使用。
这种方法将保持 Map 组件呈现直到 position
变化
任何从 leaflet 或 react-leaflet 导入的组件都应该使用选项 ssr false 动态导入。
import dynamic from 'next/dynamic';
const MyAwesomeMap = dynamic(() => import('components/MyAwesomeMap'), { ssr: false });
Leaflet 认为它超出了他们的范围,因为他们只对 vanilla JS 环境中发生的问题提供支持,所以他们不会修复(到目前为止)
如果有人遇到地图组件在所有页面之间共享的情况,并且您遇到路线更改时地图组件重新呈现的问题(在页面之间切换时),您必须在 _app.js
(使用接受的答案中解释的技术)并考虑使用 shallow routing