我遇到了一些问题,该问题在PlaceKit上为管理面板实施了NextJ。因此,让我们将我的发现收集到一篇文章中,希望它可以节省一些时间。
由于NextJS具有SSR(服务器端渲染)层,因此导入第三方前端库有时会导致头痛。
大多数情况下,您只需要用koude0包装前端组件即可使其懒惰,使SSR Pass只需忽略它:
// MyComponent.jsx
import frontLib from '<your-front-end-library>';
const MyComponent = (props) => {
// do something with frontLib
};
export default MyLazyComponent;
// MyPage.jsx
import dynamic from 'next/dynamic';
const MyComponent = dynamic(
() => import('./MyComponent'),
{
ssr: false,
loading: () => (<div>loading...</div>),
}
);
const MyPage = (props) => (
<MyComponent />
);
但对于React Leaflet,您可能需要做更多的努力。
通过ref
如果您只是将ref
分配给负载的<MapContainer>
,您将获得来自dynamic
的无法使用的代理参考:
// Map.jsx
import dynamic from 'next/dynamic';
import { useEffect, useRef } from 'react';
const MapContainer = dynamic(
() => import('react-leaflet').then((m) => m.MapContainer),
{ ssr: false }
);
const Map = (props) => {
const mapRef = useRef(null);
useEffect(
() => console.log(mapRef.current), // { retry: fn, ... }
[mapRef.current]
);
return (
<MapContainer ref={mapRef} ?>
);
};
export default Map;
诀窍有点笨重,但是您必须将其包装在另一个组件下,然后将ref
作为标准属性(在此处mapRef
)转发,然后您懒惰地加载了那个:
// MapLazyComponents.jsx
import {
MapContainer as LMapContainer,
} from 'react-leaflet';
export const MapContainer = ({ mapRef, ...props }) => (
<LMapContainer {...props} ref={mapRef} />
);
// Map.jsx
import dynamic from 'next/dynamic';
import { forwardRef, useEffect, useRef } from 'react';
const LazyMapContainer = dynamic(
() => import('./MapLazyComponents').then((m) => m.MapContainer),
{ ssr: false }
);
const MapContainer = forwardRef((props, ref) => (
<LazyMapContainer {...props} mapRef={ref} />
));
const Map = (props) => {
const mapRef = useRef(null);
useEffect(
() => console.log(mapRef.current), // this works!
[mapRef.current]
);
return (
<MapContainer ref={mapRef} />
);
};
export default Map;
组织组件
因为我们将在以下示例中准备一些其他React传单组件,所以让我们将其重新组织为3个文件:
-
Map.jsx
:您的最终组件或页面显示地图。 -
MapComponents.jsx
:将lazy-Load React Flaylet的组件。这些将准备好进口。 -
MapLazyComponents.jsx
:that that thatref
或使用前端特定功能的包装器,由MapComponents.jsx
懒惰。
我们还添加<TileLayer>
和<ZoomControl>
,因为除了加载dynamic
之外,我们不需要任何特定的更改。
所以在这一点上您得到了:
// MapLazyComponents.jsx
import {
MapContainer as LMapContainer,
} from 'react-leaflet';
export const MapContainer = ({ mapRef, ...props }) => (
<LMapContainer {...props} ref={mapRef} />
);
// MapComponents.jsx
import dynamic from 'next/dynamic';
import { forwardRef } from 'react';
export const LazyMapContainer = dynamic(
() => import('./MapLazyComponents').then((m) => m.MapContainer),
{
ssr: false,
loading: () => (<div style={{ height: '400px' }} />),
}
);
export const MapContainer = forwardRef((props, ref) => (
<LazyMapContainer {...props} mapRef={ref} />
));
// direct import from 'react-leaflet'
export const TileLayer = dynamic(
() => import('react-leaflet').then((m) => m.TileLayer),
{ ssr: false }
);
export const ZoomControl = dynamic(
() => import('react-leaflet').then((m) => m.ZoomControl),
{ ssr: false }
);
// Map.jsx
import { useEffect, useRef } from 'react';
// import and use components as usual
import { MapContainer, TileLayer, ZoomControl } from './MapComponents.jsx';
const Map = (props) => {
const mapRef = useRef(null);
return (
<MapContainer
ref={mapRef}
touchZoom={false}
zoomControl={false}
style={{ height: '400px', zIndex: '0!important' }}
>
<TileLayer url="..." attribution="..." style={{ zIndex: '0!important' }} />
<ZoomControl position="topright" style={{ zIndex: '10!important' }} />
</MapContainer>
);
};
export default Map;
使用自定义标记图标
好吧,现在我们开始拥有地图,让我们添加一个标记。但是大多数时候,您想使用自定义图标。
自定义标记图标需要使用leaflet
本身的L.Icon()
,这是库德17中的库实例化内容,因此在下一步导入时会破坏SSR。但是,它不能加载dynamic()
,甚至可以用React.lazy()
加载。
所以,让我们将我们的<Marker>
组件包装在MapLazyComponents.jsx
中,因为它将取决于前端独家功能:
// MapLazyComponents.jsx
import { useEffect, useState } from 'react';
import {
MapContainer as LMapContainer,
Marker as LMarker,
} from 'react-leaflet';
// ...
export const Marker = ({ markerRef, icon: iconProps, ...props }) => {
const [icon, setIcon] = useState();
useEffect(
() => {
// loading 'leaflet' dynamically when the component mounts
const loadIcon = async () => {
const L = await import('leaflet');
setIcon(L.icon(iconProps));
}
loadIcon();
},
[iconProps]
);
// waiting for icon to be loaded before rendering
return (!!iconProps && !icon) ? null : (
<LMarker
{...props}
icon={icon}
ref={markerRef}
/>
);
};
// MapComponents.jsx
// ...
const LazyMarker = dynamic(() => import('./MapLazyComponents').then((m) => m.Marker), { ssr: false });
export const Marker = forwardRef((props, ref) => (
<LazyMarker {...props} forwardedRef={ref} />
));
// Map.jsx
// ...
import { MapContainer, TileLayer, ZoomControl, Marker } from './MapComponents.jsx';
import CustomIcon from '../public/custom-icon.svg';
const Map = (props) => {
const mapRef = useRef(null);
const markerRef = useRef(null);
return (
<MapContainer
ref={mapRef}
touchZoom={false}
zoomControl={false}
style={{ height: '400px', zIndex: '0!important' }}
>
<TileLayer url="..." attribution="..." style={{ zIndex: '0!important' }} />
<ZoomControl position="topright" style={{ zIndex: '10!important' }} />
<Marker
ref={markerRef}
icon={{
iconUrl: CustomIcon.src,
iconAnchor: [16,32],
iconSize: [32,32]
}}
style={{ zIndex: '1!important' }}
/>
</MapContainer>
);
};
//...
处理地图事件
对于标记事件,您已经可以通过eventHandlers
属性,并且可以使用。但是要处理地图事件,无法在<MapContainer>
组件上完成,您需要使用儿童组件中的react feaflet的useMapEvents()
钩子。
在这里,我们需要包装它,然后将其在自定义的<MapConsumer>
元素中进行简化:
// MapLazyComponents.jsx
//...
import { useMapEvents } from 'react-leaflet/hooks';
export const MapConsumer = ({ eventsHandler }) => {
useMapEvents(eventsHandler);
return null;
};
// MapComponents.jsx
//...
export const MapConsumer = dynamic(
() => import('./MapLazyComponents').then((m) => m.MapConsumer),
{ ssr: false }
);
所以在您的Map.jsx
文件中,您现在可以在<MapContainer>
中添加<MapConsumer>
:
// Map.jsx
//...
const Map = (props) => {
const mapRef = useRef(null);
const markerRef = useRef(null);
const mapHandlers = useMemo(
() => ({
click(e) {
// center view on the coordinates of the click
// `this` is the Leaflet map object
this.setView([e.latlng.lat, e.latlng.lng]);
},
}),
[]
);
return (
<MapContainer
ref={mapRef}
touchZoom={false}
zoomControl={false}
style={{ height: '400px', zIndex: '0!important' }}
>
<TileLayer url="..." attribution="..." style={{ zIndex: '0!important' }} />
<ZoomControl position="topright" style={{ zIndex: '10!important' }} />
<MapConsumer
eventsHandler={mapHandlers}
/>
<Marker
ref={markerRef}
icon={{
iconUrl: CustomIcon.src,
iconAnchor: [16,32],
iconSize: [32,32]
}}
style={{ zIndex: '1!important' }}
/>
</MapContainer>
);
};
几个状态和CSS之后,这是我的结果:
所以我们已经看到了:
- 带有
next/dynamic
的懒负载组件, - 使
ref
与懒惰的组件一起使用, - 动态加载
leaflet
以访问其方法,例如L.Icon
, - 包裹
react-leaflet
自定义挂钩处理事件。
适应这些技巧应涵盖您的大多数边缘案例。我希望分解这些特定用例将帮助您在NextJ上更好地与React Leflet一起工作!
当然,如果您需要反向地理编码API来从地址获得坐标,请查看PlaceKit.io:)!