【问题标题】:load always data for only one index range总是只为一个索引范围加载数据
【发布时间】:2017-02-16 10:28:30
【问题描述】:

抱歉,如果标题有些误导。我正在使用InfiniteLoaderTable,问题是我要加载的数据总数几乎总是很大。如果我每次调用 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
            });
        });
}

【问题讨论】:

    标签: react-virtualized


    【解决方案1】:

    更新答案

    了解所见内容的关键包括几件事。

    1. 首先,每次加载新行时,您都会清除示例中的loadedRowsMap。 (加载第 200-399 行后,您会丢弃第 0-199 行。)
    2. 您在示例中设置了一个非常大的minimumBatchSize (200)。这告诉InfiniteLoader,它加载的每个行块应该(如果可能的话)覆盖至少 200 个项目的范围。
    3. InfiniteLoader 定义了一个 threshold prop,它控制预加载行的距离。这defaults to 15,这意味着当您在未加载范围的 15 行内滚动时(例如,在您的情况下为 185 行),它将提前加载一个块 - 希望当用户滚动到该范围的其余部分时,它就会被加载。
    4. InfiniteLoader checks 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 不是有状态的 - 所以它会一遍又一遍地请求相同的行,除非你让它知道它们已经加载或正在加载。

    【讨论】:

    • 我想你误解了我的问题。让我们看看我是否可以更好地解释它。每次调用loadMoreRows 时,我只需将数据从startIndex 设置为stopIndex。例如,我从 0-199 存储,然后从 200-399 存储,因此 0-199 为空。但是每当我在 200-399 之间滚动时,loadMoreRows 就会被调用为 0-199。我希望仅当我要在存储数据之前或之后渲染索引时才调用它。这可能吗?
    • 这听起来不对。 InfiniteLoader 仅扫描并请求加载当前呈现的行。它在我能看到的任何地方运行吗?
    • 很遗憾没有。我将我的loadMoreRows 编辑为我当前拥有的版本,当console.logging startIndexstopIndex 时我看到的日志是:200 399、0 199、200 399、0 199、400 58727(我的总数) , 133 332. 我不明白
    • 我创建了一个示例小提琴。不在那里工作,所以如果可能的话,你必须在本地运行它。 jsfiddle.net/XeniaSiskaki/0Ld61rdo
    • 这可以在 JSfiddle 上工作。要求某人设置本地项目、安装 deps、编译 TS 等以重现错误是很多的。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-17
    • 2020-06-25
    • 2012-08-07
    • 2021-06-28
    • 2015-07-02
    • 1970-01-01
    相关资源
    最近更新 更多