0

抱歉,如果标题有些误导。我正在使用InfiniteLoadera Table,问题是我要加载的数据总数几乎总是巨大的。如果每次loadMoreRows调用时都附加数据,我最终会在状态中存储超过 100000 个条目,我认为这对性能不利。

我想知道是否可以不每次都附加数据。我尝试仅将日志和状态设置为仅从 to 加载startIndexstopIndex但每次滚动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
            });
        });
}
4

1 回答 1

2

更新的答案

理解你所看到的关键涉及一些事情。

  1. loadedRowsMap首先,每次加载新行时,您都会清除示例中的 。(加载第 200-399 行后,您会丢弃第 0-199 行。)
  2. minimumBatchSize在示例中设置了一个非常大的值(200)。这告诉InfiniteLoader它加载的每一行块应该——如果可能的话——覆盖至少 200 个项目的范围。
  3. InfiniteLoader定义一个控制它预加载行的距离的threshold道具。默认为 15,这意味着当您在未加载范围的 15 行内滚动时(例如,在您的情况下为 185 ),它将提前加载一个块 - 希望当用户滚动其余的方式到该范围时,它将已加载。
  4. InfiniteLoader 检查前后的threshold数量,因为用户可以向上向下滚动,并且任一方向都可能包含未加载的行。

这一切都导致了以下情况:

  1. 用户滚动直到加载了新的行范围。
  2. 您的演示应用程序丢弃了之前的范围。
  3. 用户进一步滚动并InfiniteLoader检查前后 15 行以查看数据是否已在两个方向上加载(根据上述原因 4)。
  4. 数据似乎尚未在上方/之前加载,因此InfiniteLoader尝试至少minimumBatchSize在该方向加载记录(例如 0-199)

这个问题的解决方案可能是几件事:

  • 不要丢弃之前加载的数据。(无论如何,它可能占用最少的内存,所以请保留它。)
  • 如果你必须把它扔掉——不要把它全部扔掉。保持一些靠近用户当前所在的范围。(足以确保threshold支票在两个方向上都是安全的。)

原始答案

也许我误解了你,但这就是minimumBatchSize道具的用途。它配置InfiniteLoader为加载足够大的数据块,当用户缓慢滚动数据时,您不会收到大量重复的加载请求。如果用户快速滚动 - 你可能会。不过没有办法。

如果这对您有问题,我建议使用去抖动和/或限制方法来防止触发过多的 HTTP 请求。Debounce 可以帮助您避免加载用户快速滚动过去的行(并且无论如何都不会看到),并且 throttle 可以帮助您避免并行发送过多的 HTTP 请求。

一目了然,您的isRowLoaded功能和完成的映射loadMoreRows看起来是正确的。但您可能还想验证这一点。InfiniteLoader不是有状态的 - 所以它会一遍又一遍地请求相同的行,除非你让它知道它们已经加载或正在加载。

于 2017-02-16T16:57:20.453 回答