我需要有关传单的反应叶端口的建议。我在地图上生成标记,并使用带有react-leaflet-markercluster的标记聚类。每个标记数据都与一些数据相关联。我想根据视口中的标记过滤数据。
我的想法:获取地图的边界并与每个标记进行交叉检查。是的,它有效。但是,当添加超过500个标记时,性能非常慢(计算时间为4.5秒)。
我该怎么做才能提高性能?
这是我的代码:
import React, { Component, Fragment } from 'react';
import CustomMarkers from './components/CustomMarkers';
import { Map, TileLayer } from 'react-leaflet';
import ImageContainer from './components/ImageContainer';
import { checkIfMarkerOnMap, createSampleData } from './utils/helpers';
import L from 'leaflet';
class App extends Component {
constructor(props){
super(props);
this.state = {
viewport: {
width: '100%',
height: '400px',
latitude: 40.00,
longitude: 20.00,
zoom: 5
},
visibleMarkers: {},
markers : {},
}
}
componentDidMount = () => {
const sampleData = createSampleData(1000);
this.setState({ markers: sampleData, visibleMarkers: sampleData });
const mapBoundaries = this.mapRef.contextValue.map.getBounds();
this.setState({ mapBoundaries });
}
getMapBoundaries = () => {
// Get map boundaries
const mapBoundaries = this.mapRef.contextValue.map.getBounds();
if(this.state.mapBoundaries !== mapBoundaries){
console.log("different");
this.setState({ mapBoundaries } );
} else return;
}
checkVisibleMarkers = () => {
console.time("checkVisibleMarkers");
const { markers, mapBoundaries } = this.state;
let visibleMarkers = Object.keys(markers)
.filter(key => (L.latLngBounds([[mapBoundaries._southWest.lat, mapBoundaries._southWest.lng], [mapBoundaries._northEast.lat, mapBoundaries._northEast.lng]]).contains([markers[key].coordinates.latitude,markers[key].coordinates.longitude])))
.map(key => { return { [key] : markers[key] } });
visibleMarkers = Object.assign({}, ...visibleMarkers);
console.log("visibleMarkers", visibleMarkers);
// this.setState({ visibleMarkers })
console.timeEnd("checkVisibleMarkers");
}
handleViewportChanged = () => {
this.getMapBoundaries();
this.checkVisibleMarkers();
}
render() {
console.log("this.mapRef", this.mapRef);
const { viewport, markers, visibleMarkers } = this.state;
const position = [viewport.latitude, viewport.longitude]
return (
<Fragment>
<Map
ref={(ref) => { this.mapRef = ref }}
center={position}
zoom={viewport.zoom}
maxZoom={15}
onViewportChanged={() => this.handleViewportChanged()}
style={{ height: '400px' }}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
/>
<CustomMarkers visibleMarkers={markers} />
</Map>
{/* <ImageContainer visibleMarkers={visibleMarkers} /> */}
</Fragment>
)
}
}
export default App;
CustomMarker.js:
import React, { Component } from 'react';
import { Marker, Tooltip } from 'react-leaflet';
import uuid from 'uuid-v4';
import {
heartIcon,
heartIconYellow,
heartIconLightblue,
heartIconDarkblue } from './../icons/icons';
import MarkerClusterGroup from 'react-leaflet-markercluster';
import L from 'leaflet';
class CustomMarkers extends Component {
render() {
const { visibleMarkers } = this.props;
let markers;
if(Object.keys(visibleMarkers).length > 0) {
markers = Object.keys(visibleMarkers).map(key => {
let latitude = visibleMarkers[key].coordinates.latitude;
let longitude = visibleMarkers[key].coordinates.longitude;
let icon = heartIcon;
if(visibleMarkers[key].category === 'fb') icon = heartIconLightblue;
if(visibleMarkers[key].category === 'blogs') icon = heartIconYellow;
if(visibleMarkers[key].category === 'artisan') icon = heartIcon;
if(visibleMarkers[key].category === 'website') icon = heartIconDarkblue;
return (
<Marker
key={uuid()}
position={ [latitude, longitude] }
icon={icon}
>
<Tooltip>{visibleMarkers[key].category}</Tooltip>
</Marker>
)
});
}
const createClusterCustomIcon = (cluster) => {
return L.divIcon({
html: `<span>${cluster.getChildCount()}</span>`,
className: 'marker-cluster-custom',
iconSize: L.point(40, 40, true),
});
}
return (
<MarkerClusterGroup
iconCreateFunction={createClusterCustomIcon}
disableClusteringAtZoom={10}
zoomToBoundsOnClick={true}
spiderfyOnMaxZoom={false}
removeOutsideVisibleBounds={true}
maxClusterRadius={150}
showCoverageOnHover={false}
>
{markers}
</MarkerClusterGroup>
)
}
}
export default CustomMarkers;
createSampleData将要生成的样本数据量作为输入,并为样本数据创建json结构{id:1 {坐标:{},...}
瓶颈是功能 checkVisibleMarkers 。此函数计算标记是否在视口中。 从数学上讲,每个标记只有两个乘法。
答案 0 :(得分:1)
我看到了一些潜在的问题-checkVisibleMarkers
函数的性能, 和 使用uuid()
创建唯一(不同)每次重新呈现key
时的<Marker />
值。
checkVisibleMarkers
关于checkVisibleMarkers
功能。有一些可以优化的调用和模式。这是当前正在发生的事情:
L.latLngBounds().contains()
按位置过滤{key: marker}
的形式创建对象数组Object.assign()
从对象数组创建对象最后,我们有一个对象,每个值都是一个标记。
我不确定L.latLngBounds
的内部结构,但可能部分归因于瓶颈。忽略这一点,我将专注于使用Object.assign({}, ...Object.keys().filter().map())
语句重构for...in
模式。
checkVisibleMarkers = () => {
const visibleMarkers = {};
const { markers, mapBoundaries } = this.state;
for (let key in markers) {
const marker = markers[key];
const { latitude, longitude } = marker.coordinates;
const isVisible = mapBoundaries.contains([latitude, longitude]);
if (isVisible) {
visibleMarkers[key] = marker;
}
}
this.setState({ visibleMarkers });
}
对jsPerf的快速检查显示,上述方法比您使用的方法快50%,但是它不包含L.latLngBounds().contains()
调用,因此不是精确的比较。
我还尝试了一种使用Object.entries(markers).forEach()
的方法,该方法比上面的for...in
方法要慢一些。
key
的{{1}}道具在<Marker />
组件中,您正在使用<Marker />
生成唯一密钥。尽管是唯一的,但是每个重新渲染都将生成一个新密钥,并且每次组件的密钥更改时,React都会创建一个新的组件实例。这意味着将在每次重新渲染时重新创建每个uuid()
。
解决方案是为每个<Marker />
使用唯一且永久的密钥。幸运的是,听起来您已经有了一个适用于此的值,即<Marker />
中的key
。因此,请改用此:
visibleMarkers