immutable data structures in tables 2/n: managed table

Summary:
Migrating tables' row collection to Immutable.js List:
1. Change native array to Immutable list in ManagedTable_immutable

-----
Current implementation of tables forces to copy arrays on new data arrival which causes O(N^2) complexity ->
tables can't handle a lot of new rows in short period of time -> tables freeze and become unresponsive for a few seconds.
Immutable data structures will bring us to O(N) complexity

Reviewed By: jknoxville

Differential Revision: D16416869

fbshipit-source-id: 6d5690d8f5f70286f31a423e319b2cb22deab8ff
This commit is contained in:
Timur Valiev
2019-07-23 07:57:00 -07:00
committed by Facebook Github Bot
parent af7830d94a
commit 6deaf2106f
3 changed files with 46 additions and 24 deletions

View File

@@ -11,7 +11,7 @@ import type {
TableColumns,
TableHighlightedRows,
TableRowSortOrder,
TableRows,
TableRows_immutable,
TableBodyRow,
TableOnAddFilter,
} from './types.js';
@@ -33,7 +33,7 @@ import debounce from 'lodash.debounce';
import {DEFAULT_ROW_HEIGHT} from './types';
import textContent from '../../../utils/textContent.js';
export type ManagedTableProps = {|
export type ManagedTableProps_immutable = {|
/**
* Column definitions.
*/
@@ -41,7 +41,7 @@ export type ManagedTableProps = {|
/**
* Row definitions.
*/
rows: TableRows,
rows: TableRows_immutable,
/*
* Globally unique key for persisting data between uses of a table such as column sizes.
*/
@@ -148,7 +148,7 @@ const Container = styled(FlexColumn)(props => ({
const globalTableState: {[string]: TableColumnSizes} = {};
class ManagedTable extends React.Component<
ManagedTableProps,
ManagedTableProps_immutable,
ManagedTableState,
> {
static defaultProps = {
@@ -202,7 +202,7 @@ class ManagedTable extends React.Component<
document.removeEventListener('keydown', this.onKeyDown);
}
componentWillReceiveProps(nextProps: ManagedTableProps) {
componentWillReceiveProps(nextProps: ManagedTableProps_immutable) {
// if columnSizes has changed
if (nextProps.columnSizes !== this.props.columnSizes) {
this.setState({
@@ -228,7 +228,7 @@ class ManagedTable extends React.Component<
}
if (
this.props.rows.length > nextProps.rows.length &&
this.props.rows.size > nextProps.rows.size &&
this.tableRef &&
this.tableRef.current
) {
@@ -238,11 +238,11 @@ class ManagedTable extends React.Component<
}
componentDidUpdate(
prevProps: ManagedTableProps,
prevProps: ManagedTableProps_immutable,
prevState: ManagedTableState,
) {
if (
this.props.rows.length !== prevProps.rows.length &&
this.props.rows.size !== prevProps.rows.size &&
this.state.shouldScrollToBottom &&
this.state.highlightedRows.size < 2
) {
@@ -314,13 +314,14 @@ class ManagedTable extends React.Component<
row => row.key === lastItemKey,
);
const newIndex = Math.min(
rows.length - 1,
rows.size - 1,
Math.max(0, e.keyCode === 38 ? lastItemIndex - 1 : lastItemIndex + 1),
);
if (!e.shiftKey) {
highlightedRows.clear();
}
highlightedRows.add(rows[newIndex].key);
// $FlowFixMe 0 <= newIndex <= rows.size - 1
highlightedRows.add(rows.get(newIndex).key);
this.onRowHighlighted(highlightedRows, () => {
const {current} = this.tableRef;
if (current) {
@@ -374,8 +375,8 @@ class ManagedTable extends React.Component<
scrollToBottom() {
const {current: tableRef} = this.tableRef;
if (tableRef && this.props.rows.length > 1) {
tableRef.scrollToItem(this.props.rows.length - 1);
if (tableRef && this.props.rows.size > 1) {
tableRef.scrollToItem(this.props.rows.size - 1);
}
}
@@ -437,11 +438,13 @@ class ManagedTable extends React.Component<
const selected = [];
let startIndex = -1;
let endIndex = -1;
for (let i = 0; i < this.props.rows.length; i++) {
if (this.props.rows[i].key === fromKey) {
for (let i = 0; i < this.props.rows.size; i++) {
// $FlowFixMe 0 <= newIndex <= rows.size - 1
if (this.props.rows.get(i).key === fromKey) {
startIndex = i;
}
if (this.props.rows[i].key === toKey) {
// $FlowFixMe 0 <= newIndex <= rows.size - 1
if (this.props.rows.get(i).key === toKey) {
endIndex = i;
}
if (endIndex > -1 && startIndex > -1) {
@@ -455,7 +458,8 @@ class ManagedTable extends React.Component<
i++
) {
try {
selected.push(this.props.rows[i].key);
// $FlowFixMe 0 <= newIndex <= rows.size - 1
selected.push(this.props.rows.get(i).key);
} catch (e) {}
}
@@ -477,7 +481,8 @@ class ManagedTable extends React.Component<
!e.shiftKey // When shift key is pressed, it's a range select not a drag select
) {
current.scrollToItem(index + 1);
const startKey = this.props.rows[dragStartIndex].key;
// $FlowFixMe 0 <= newIndex <= rows.size - 1
const startKey = this.props.rows.get(dragStartIndex).key;
const highlightedRows = new Set(this.selectInRange(startKey, row.key));
this.onRowHighlighted(highlightedRows);
}
@@ -618,17 +623,22 @@ class ManagedTable extends React.Component<
.map(k => (k.visible ? k.key : null))
.filter(Boolean);
const row = rows.get(index);
if (row == null) {
return;
}
return (
<TableRow
key={rows[index].key}
key={row.key}
columnSizes={columnSizes}
columnKeys={columnKeys}
onMouseDown={e => this.onHighlight(e, rows[index], index)}
onMouseEnter={e => this.onMouseEnterRow(e, rows[index], index)}
onMouseDown={e => this.onHighlight(e, row, index)}
onMouseEnter={e => this.onMouseEnterRow(e, row, index)}
multiline={multiline}
rowLineHeight={24}
highlighted={highlightedRows.has(rows[index].key)}
row={rows[index]}
highlighted={highlightedRows.has(row.key)}
row={row}
index={index}
style={style}
onAddFilter={onAddFilter}
@@ -694,9 +704,9 @@ class ManagedTable extends React.Component<
this.buildContextMenuItems
}>
<List
itemCount={rows.length}
itemCount={rows.size}
itemSize={index =>
(rows[index] && rows[index].height) ||
(rows.get(index) && rows.get(index).height) ||
rowLineHeight ||
DEFAULT_ROW_HEIGHT
}

View File

@@ -7,6 +7,8 @@
import type {Filter} from '../filter/types.js';
import {List} from 'immutable';
export const MINIMUM_COLUMN_WIDTH = 100;
export const DEFAULT_COLUMN_WIDTH = 200;
export const DEFAULT_ROW_HEIGHT = 23;
@@ -71,6 +73,8 @@ export type TableColumns = {
export type TableRows = Array<TableBodyRow>;
export type TableRows_immutable = List<TableBodyRow>;
export type TableRowSortOrder = {|
key: string,
direction: 'up' | 'down',

View File

@@ -30,6 +30,7 @@ export {default as Popover} from './components/Popover.js';
export type {
TableColumns,
TableRows,
TableRows_immutable,
TableBodyColumn,
TableBodyRow,
TableHighlightedRows,
@@ -40,6 +41,13 @@ export type {
} from './components/table/types.js';
export {default as ManagedTable} from './components/table/ManagedTable.js';
export type {ManagedTableProps} from './components/table/ManagedTable.js';
export {
default as ManagedTable_immutable,
} from './components/table/ManagedTable_immutable.js';
export type {
ManagedTableProps_immutable,
} from './components/table/ManagedTable_immutable.js';
export type {Value} from './components/table/TypeBasedValueRenderer.js';
export {renderValue} from './components/table/TypeBasedValueRenderer.js';