始终只加载一个索引范围的数据
load always data for only one index range
抱歉,如果标题有些误导。我正在使用 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;
这里是从 parent 组件
传递下来的 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
});
});
}
更新答案
理解您所看到的内容的关键涉及几件事。
- 首先,每次加载新行时,您都会清除示例中的
loadedRowsMap
。 (加载第 200-399 行后,您将丢弃第 0-199 行。)
- 您在您的示例 (200) 中设置了一个非常大的
minimumBatchSize
。这告诉 InfiniteLoader
它加载的每个行块应该——如果可能的话——覆盖至少 200 个项目的范围。
InfiniteLoader
定义了一个 threshold
prop that controls how far ahead it pre-loads rows. This defaults to 15,这意味着当您在未加载范围的 15 行内滚动时(例如,在您的情况下为 185),它将提前加载一个块 - 希望通过当用户滚动到该范围的剩余时间时,它将被加载。
InfiniteLoader
checks both ahead and behind by the threshold
amount 因为用户可以向上滚动 或 向下并且任一方向都可能包含未加载的行。
这一切都会导致以下情况:
- 用户滚动直到加载新的行范围。
- 您的演示应用放弃了之前的范围。
- 用户再滚动一点,
InfiniteLoader
检查前后 15 行以查看数据是否已双向加载(根据上述原因 4)。
- 数据似乎尚未加载 above/before,因此
InfiniteLoader
尝试在该方向上至少加载 minimumBatchSize
条记录(例如 0-199)
这个问题的解决方案可能有以下几点:
- 不要丢弃您之前加载的数据。 (无论如何,它可能占用的内存最少,所以请保留它。)
- 如果你必须把它扔掉——不要把它全部扔掉。在用户当前所在的范围内保留一些。 (足以确保
threshold
检查在两个方向上都是安全的。)
原回答
也许我误会了你,但这就是 minimumBatchSize
道具的用途。它配置 InfiniteLoader
以加载足够大的数据块,这样您就不会在用户缓慢滚动数据时收到大量重复的加载请求。如果用户快速滚动 - 你可能会。但是没有办法解决这个问题。
如果这对您来说有问题,我建议您使用去抖 and/or 节流方法来防止触发过多的 HTTP 请求。 Debounce 可以帮助您避免加载用户快速滚动过去(无论如何都不会看到)的行,而 throttle 可以帮助您避免并行发送过多的 HTTP 请求。
乍一看,您的 isRowLoaded
函数和 loadMoreRows
中完成的映射看起来是正确的。但您可能还想验证这一点。 InfiniteLoader
不是有状态的 - 所以它会一遍又一遍地询问相同的行,除非你让它知道它们已经加载或正在加载。
抱歉,如果标题有些误导。我正在使用 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;
这里是从 parent 组件
传递下来的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
});
});
}
更新答案
理解您所看到的内容的关键涉及几件事。
- 首先,每次加载新行时,您都会清除示例中的
loadedRowsMap
。 (加载第 200-399 行后,您将丢弃第 0-199 行。) - 您在您的示例 (200) 中设置了一个非常大的
minimumBatchSize
。这告诉InfiniteLoader
它加载的每个行块应该——如果可能的话——覆盖至少 200 个项目的范围。 InfiniteLoader
定义了一个threshold
prop that controls how far ahead it pre-loads rows. This defaults to 15,这意味着当您在未加载范围的 15 行内滚动时(例如,在您的情况下为 185),它将提前加载一个块 - 希望通过当用户滚动到该范围的剩余时间时,它将被加载。InfiniteLoader
checks both ahead and behind by thethreshold
amount 因为用户可以向上滚动 或 向下并且任一方向都可能包含未加载的行。
这一切都会导致以下情况:
- 用户滚动直到加载新的行范围。
- 您的演示应用放弃了之前的范围。
- 用户再滚动一点,
InfiniteLoader
检查前后 15 行以查看数据是否已双向加载(根据上述原因 4)。 - 数据似乎尚未加载 above/before,因此
InfiniteLoader
尝试在该方向上至少加载minimumBatchSize
条记录(例如 0-199)
这个问题的解决方案可能有以下几点:
- 不要丢弃您之前加载的数据。 (无论如何,它可能占用的内存最少,所以请保留它。)
- 如果你必须把它扔掉——不要把它全部扔掉。在用户当前所在的范围内保留一些。 (足以确保
threshold
检查在两个方向上都是安全的。)
原回答
也许我误会了你,但这就是 minimumBatchSize
道具的用途。它配置 InfiniteLoader
以加载足够大的数据块,这样您就不会在用户缓慢滚动数据时收到大量重复的加载请求。如果用户快速滚动 - 你可能会。但是没有办法解决这个问题。
如果这对您来说有问题,我建议您使用去抖 and/or 节流方法来防止触发过多的 HTTP 请求。 Debounce 可以帮助您避免加载用户快速滚动过去(无论如何都不会看到)的行,而 throttle 可以帮助您避免并行发送过多的 HTTP 请求。
乍一看,您的 isRowLoaded
函数和 loadMoreRows
中完成的映射看起来是正确的。但您可能还想验证这一点。 InfiniteLoader
不是有状态的 - 所以它会一遍又一遍地询问相同的行,除非你让它知道它们已经加载或正在加载。