反应叶中标记筛选的性能降低

时间:2018-12-11 08:11:19

标签: javascript reactjs leaflet maps react-leaflet

我需要有关传单的反应叶端口的建议。我在地图上生成标记,并使用带有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="&copy; <a href=&quot;http://osm.org/copyright&quot;>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 。此函数计算标记是否在视口中。 从数学上讲,每个标记只有两个乘法。

1 个答案:

答案 0 :(得分:1)

我看到了一些潜在的问题-checkVisibleMarkers函数的性能, 使用uuid()创建唯一(不同)每次重新呈现key时的<Marker />值。

checkVisibleMarkers

关于checkVisibleMarkers功能。有一些可以优化的调用和模式。这是当前正在发生的事情:

  1. 创建标记键数组
  2. 浏览各个键,引用相应的标记并使用L.latLngBounds().contains()按位置过滤
  3. 循环浏览已过滤的键,以{key: marker}的形式创建对象数组
  4. 使用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