簡體   English   中英

反應葉中標記篩選的性能降低

[英]Slow Performance for filtering markers in react-leaflet

我需要有關小葉的React-Leftlet端口的建議。 我在地圖上生成標記,並使用帶有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 此函數計算標記是否在視口中。 在數學上,每個標記只有兩個乘法。

我看到了一些潛在的問題checkVisibleMarkers函數的性能, 以及使用uuid()在每次<Marker />重新呈現時創建唯一的(和不同的) key

checkVisibleMarkers

關於checkVisibleMarkers函數。 有一些可以優化的調用和模式。 這是當前正在發生的事情:

  1. 創建一個標記鍵數組
  2. 遍歷鍵,引用相應的標記並使用L.latLngBounds().contains()按位置過濾
  3. 循環過濾已過濾的鍵,以創建一個對象數組作為{key: marker}
  4. 使用Object.assign()從對象數組創建對象

最后,我們有一個對象,每個值都是一個標記。

我不確定L.latLngBounds的內部結構,但可能部分原因是瓶頸。 忽略這一點,我將專注於使用for...in語句重構Object.assign({}, ...Object.keys().filter().map())模式。

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方法稍慢。

<Marker />key道具

<Marker />組件中,您正在使用uuid()生成唯一鍵。 盡管是唯一的,但是每個重新渲染都將生成一個新密鑰,並且每次組件的密鑰更改時,React都會創建一個新的組件實例。 這意味着在每個重新渲染上都將重新創建每個<Marker />

解決方案是為每個<Marker />使用唯一且永久的密鑰。 幸運的是,聽起來您已經有了一個可以使用的值,即visibleMarkerskey 所以改用這個:

<Marker 
  key={key}
  position={ [latitude, longitude] } 
  icon={icon}    
>
  <Tooltip>{visibleMarkers[key].category}</Tooltip>
</Marker>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM