2017-02-16 4 views
0

Извините, если заголовок несколько вводит в заблуждение. Я использую InfiniteLoader с Table, и проблема в том, что почти всегда общее количество данных, которые я хочу загрузить, огромно. И если бы я должен был добавлять данные каждый раз, когда был вызван loadMoreRows, я бы в конечном итоге сохранил в состоянии более 100000 записей, что, я думаю, было бы плохо для производительности.всегда загружать данные только для одного диапазона индексов

Мне было интересно, нельзя ли добавлять данные каждый раз. Я пробовал только устанавливать журналы и статус для загрузки только с startIndex на stopIndex, но каждый раз, когда я прокручиваю loadMoreRows, вызывается несколько раз.

Вот то, что я до сих пор, с тем, что я пытался, как упоминалось выше

'use strict'; 

import React = require('react'); 
import _ = require('lodash'); 
import Immutable = require('immutable'); 
import moment = require('moment-timezone'); 
import {AutoSizer, InfiniteLoader, Table, Column} from 'react-virtualized'; 

interface Props { 
    logEntries: Immutable.List<Immutable.Map<string, any>>; 
    count: number; 
    timezone: string; 
    logLimit: number; 
    loadedRowsMap: { [index: number]: number; }; 
    onLoadMoreRows: (param: {startIndex: number, stopIndex: number}) => Promise<any>; 
} 

class LogLoader extends React.Component<Props, {}> { 
    render() { 
     const {logEntries, count, logLimit} = this.props; 
     const headers: { 
      name: string; 
      dataKey: string; 
      width: number; 
      cellDataGetter?: (param: {rowData: any}) => any; 
     }[] = [ 
      { name: 'Time', dataKey: 'dtCreated', width: 95, cellDataGetter: this.renderTime.bind(this) }, 
      { name: 'Level', dataKey: 'levelname', width: 65 }, 
      { name: 'Message', dataKey: 'message', width: 70, cellDataGetter: this.renderMessage.bind(this) } 
     ]; 

     return (
      <InfiniteLoader isRowLoaded={this.isRowLoaded.bind(this)} 
          loadMoreRows={this.props.onLoadMoreRows} 
          minimumBatchSize={logLimit} 
          rowCount={count} > 
       { 
        ({onRowsRendered, registerChild}) => (
         <AutoSizer disableHeight> 
          { 
           ({width}) => (
            <Table headerHeight={20} 
              height={400} 
              onRowsRendered={onRowsRendered} 
              rowRenderer={this.rowRenderer} 
              ref={registerChild} 
              rowCount={count} 
              className='log-entries' 
              gridClassName='grid' 
              headerClassName='header' 
              rowClassName={this.getRowClassName.bind(this)} 
              rowGetter={({index}) => logEntries.get(index)} 
              rowHeight={this.calculateRowHeight.bind(this)} 
              width={width} > 
             { 
              headers.map(({name, dataKey, cellDataGetter, width}) => 
               <Column label={name} 
                 key={name} 
                 className={`${name.toLowerCase()} column`} 
                 dataKey={dataKey} 
                 cellDataGetter={cellDataGetter || this.renderTableCell.bind(this)} 
                 width={width} /> 
              ) 
             } 
            </Table> 
           ) 
          } 
         </AutoSizer> 
        ) 
       } 
      </InfiniteLoader> 
     ); 
    } 

    private calculateRowHeight({index}) { 
     const rowData = this.props.logEntries.get(index); 
     if(!rowData) { 
      return 0; 
     } 
     const msg = this.renderMessage({rowData}); 

     const div = document.createElement('div'); 
     const span = document.createElement('span'); 
     span.style.whiteSpace = 'pre'; 
     span.style.wordBreak = 'break-all'; 
     span.style.fontSize = '12px'; 
     span.style.fontFamily = 'monospace'; 
     span.style.display = 'table-cell'; 
     span.innerHTML = msg; 

     div.appendChild(span); 
     document.body.appendChild(div); 
     const height = div.offsetHeight; 
     document.body.removeChild(div); 

     return height; 
    } 

    private rowRenderer(params: any) { 
     const {key, className, columns, rowData, style} = params; 
     if(!rowData) { 
      return (
       <div className={className} 
        key={key} 
        style={style} > 
        Loading... 
       </div> 
      ); 
     } 

     return (
      <div className={className} 
       key={key} 
       style={style} > 
       {columns} 
      </div> 
     ); 
    } 

    private renderTableCell({rowData, dataKey}) { 
     if(!rowData) { 
      return null; 
     } 

     return rowData.get(dataKey); 
    } 

    private renderMessage({rowData}) { 
     if(!rowData) { 
      return null; 
     } 

     return rowData.get('message'); 
    } 

    private renderTime({rowData}) { 
     if(!rowData) { 
      return null; 
     } 

     return moment(rowData.get('dtCreated')) 
      .tz(this.props.timezone) 
      .format('HH:mm:ss.SSS'); 
    } 

    private getRowClassName({index}) { 
     const {logEntries} = this.props; 
     const data = logEntries.get(index); 

     if(data) { 
      return `log-entry ${data.get('levelname').toLowerCase()}`; 
     } 

     return ''; 
    } 

    private isRowLoaded({index}) { 
     return !!this.props.loadedRowsMap[index]; 
    } 
} 

export = LogLoader; 

и здесь loadMoreRows передается от родительского компонента

private loadMoreRows({startIndex, stopIndex}) { 
    const {loadedRowsMap, logEntries} = this.state, 
      indexRange = _.range(startIndex, stopIndex + 1), 
      updatedLoadedRowsMap = {}; 

    indexRange.forEach(i => { updatedLoadedRowsMap[i] = STATUS_LOADING; }); 
    this.setState({ loadedRowsMap: updatedLoadedRowsMap, loading: true }); 

    return Api.scriptLogs(null, { id: this.props.id }) 
     .then(({body: [count, logs]}) => { 
      indexRange.forEach(i => { updatedLoadedRowsMap[i] = STATUS_LOADED; }); 
      const newLogs = logEntries.splice((stopIndex - startIndex + 1), 0, ...logs).toJS(); 
      this.setState({ 
       count, 
       logEntries: Immutable.fromJS(newLogs), 
       loadedRowsMap: updatedLoadedRowsMap, 
       loading: false 
      }); 
     }); 
} 

ответ

1

Обновленный ответ

Ключ к пониманию того, что вы видите, включает в себя несколько вещей.

  1. Во-первых, вы уничтожаете loadedRowsMap в вашем примере каждый раз, когда вы загружаете новые строки. (Как только вы загружаете строки 200-399, вы выбрасываете ряды 0-199.)
  2. Вы устанавливаете очень большой minimumBatchSize в своем примере (200). Это сообщает InfiniteLoader, что каждый кусок строк, которые он загружает, должен, если это вообще возможно, охватывать диапазон не менее 200 предметов.
  3. InfiniteLoader определяет threshold prop, который контролирует, насколько далеко он предварительно загружает строки. Это defaults to 15, что означает, что при прокрутке в пределах 15 строк разгруженного диапазона (например, 185 в вашем случае) он будет загружать блок заранее - с надеждой, что к тому моменту, когда пользователь прокрутит остальную часть пути до этого диапазона, будут загружены.
  4. InfiniteLoaderchecks both ahead and behind by the threshold amount, так как пользователь мог прокручивать вверх или вниз и в любом направлении может содержать разгруженные строки.

Это все приводит к следующему сценарию:

  1. свитки Пользователь пока не будет загружен новый диапазон строк.
  2. Ваше демонстрационное приложение выбрасывает предыдущий диапазон.
  3. Пользователь немного прокручивается, а InfiniteLoader проверяет 15 строк вперед и назад, чтобы увидеть, были ли данные загружены в обоих направлениях (согласно причине 4 выше). появляется
  4. Данные не были загружены выше/до и поэтому InfiniteLoader пытается загрузить по крайней мере minimumBatchSize записей в этом направлении (например, 0-199)

Решение этой проблемы может быть несколько вещей:

  • Не выбрасывайте ранее загруженные данные. (Это, вероятно, занимает минимальную память, так что просто держите ее вокруг.)
  • Если вы должны выбросить его - не выбрасывайте все это. Храните рядом с диапазоном, в котором находится пользователь. (Достаточно, чтобы убедиться, что threshold проверка безопасности в обоих направлениях.)

Оригинальный ответ

Может быть, я недопонимание вас, но это то, что minimumBatchSize опора для. Он настраивает InfiniteLoader для загрузки достаточно больших фрагментов данных, которые не будут получать много повторяющихся запросов нагрузки, когда пользователь медленно прокручивает данные. Если пользователь быстро прокручивается - вы можете. Впрочем, об этом нет.

Если это проблематично для вас, я бы рекомендовал использовать подход к отказу и/или дросселю, чтобы предотвратить слишком много запросов HTTP. Debounce может помочь вам избежать загрузки строк, которые пользователь быстро прокручивает (и не видел в любом случае), и дроссель может помочь вам избежать отправки слишком большого количества HTTP-запросов параллельно.

С первого взгляда ваша функция isRowLoaded и отображение, сделанное в loadMoreRows, выглядят правильно. Но вы также можете проверить это. InfiniteLoader не поддерживает состояние, поэтому он будет продолжать запрашивать одни и те же строки снова и снова, если вы не сообщите ему, что они уже загружены или находятся в процессе загрузки.

+0

Я думаю, вы неправильно поняли мой вопрос. Посмотрим, смогу ли я объяснить это лучше. Каждый раз, когда вызывается 'loadMoreRows', я просто устанавливаю данные из' startIndex' в 'stopIndex'. Например, я храню от 0-199, а затем от 200-399, поэтому 0-199 пуст. Но всякий раз, когда я прокручиваю где-то между 200-399, 'loadMoreRows' вызывается для 0-199. Я бы хотел, чтобы это вызывалось только тогда, когда я собираюсь отображать индексы до или после моих сохраненных данных. Это возможно? – XeniaSis

+0

Это звучит не так. 'InfiniteLoader' только сканирует и запрашивает загружаемые строки, которые в настоящее время отображаются. У вас это работает в любом месте, где я мог видеть это в действии? – brianvaughn

+0

К сожалению нет. Я отредактировал свой «loadMoreRows» до версии, которую я сейчас имею, и журналов, которые я вижу, когда 'console.log'ging' startIndex' и 'stopIndex': 200 399, 0 199, 200 399, 0 199, 400 58727 (моя общая count), 133 332. Честно говоря, я не знаю – XeniaSis