Fix performance issues [1/N]
Summary: This issue fixes a bunch of performance issues: * The introduction of a context menu around every _row_ in D18808544 breaks the internal contract between lists and rows that react-window has, causing empty element to appear during scrolling, and some optimizations not working. Fixed by wrapping the context menu at the right level * Every time a new row is created for the listview, it gets fresh event handlers and column configurations. THis has been fixed by precomputing the column configuration and avoiding the need to close over the row data in the event handlers * Added some stricter immutable typings to make sure we don't break the immutable contract somewhere * Fix the introduction of on the fly styling generation, which isn't needed Reviewed By: passy Differential Revision: D19853595 fbshipit-source-id: dc82b6586889f4e8c7a437cfdc27a50dc33ba2a2
This commit is contained in:
committed by
Facebook Github Bot
parent
a05b8d1ba2
commit
2dad8809c4
@@ -34,6 +34,9 @@ import {DEFAULT_ROW_HEIGHT} from './types';
|
||||
import textContent from '../../../utils/textContent';
|
||||
import {notNull} from '../../../utils/typeUtils';
|
||||
|
||||
const EMPTY_OBJECT = {};
|
||||
Object.freeze(EMPTY_OBJECT);
|
||||
|
||||
export type ManagedTableProps = {
|
||||
/**
|
||||
* Column definitions.
|
||||
@@ -140,6 +143,7 @@ type ManagedTableState = {
|
||||
highlightedRows: Set<string>;
|
||||
sortOrder?: TableRowSortOrder;
|
||||
columnOrder: TableColumnOrder;
|
||||
columnKeys: string[];
|
||||
columnSizes: TableColumnSizes;
|
||||
shouldScrollToBottom: boolean;
|
||||
};
|
||||
@@ -172,20 +176,6 @@ export class ManagedTable extends React.Component<
|
||||
);
|
||||
};
|
||||
|
||||
state: ManagedTableState = {
|
||||
columnOrder:
|
||||
JSON.parse(window.localStorage.getItem(this.getTableKey()) || 'null') ||
|
||||
this.props.columnOrder ||
|
||||
Object.keys(this.props.columns).map(key => ({key, visible: true})),
|
||||
columnSizes:
|
||||
this.props.tableKey && globalTableState[this.props.tableKey]
|
||||
? globalTableState[this.props.tableKey]
|
||||
: this.props.columnSizes || {},
|
||||
highlightedRows: this.props.highlightedRows || new Set(),
|
||||
sortOrder: this.props.initialSortOrder || undefined,
|
||||
shouldScrollToBottom: Boolean(this.props.stickyBottom),
|
||||
};
|
||||
|
||||
tableRef = React.createRef<List>();
|
||||
|
||||
scrollRef: {
|
||||
@@ -200,6 +190,25 @@ export class ManagedTable extends React.Component<
|
||||
// trigger actions on the first update instead.
|
||||
firstUpdate = true;
|
||||
|
||||
constructor(props: ManagedTableProps) {
|
||||
super(props);
|
||||
const columnOrder =
|
||||
JSON.parse(window.localStorage.getItem(this.getTableKey()) || 'null') ||
|
||||
this.props.columnOrder ||
|
||||
Object.keys(this.props.columns).map(key => ({key, visible: true}));
|
||||
this.state = {
|
||||
columnOrder,
|
||||
columnKeys: this.computeColumnKeys(columnOrder),
|
||||
columnSizes:
|
||||
this.props.tableKey && globalTableState[this.props.tableKey]
|
||||
? globalTableState[this.props.tableKey]
|
||||
: this.props.columnSizes || {},
|
||||
highlightedRows: this.props.highlightedRows || new Set(),
|
||||
sortOrder: this.props.initialSortOrder || undefined,
|
||||
shouldScrollToBottom: Boolean(this.props.stickyBottom),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this.onKeyDown);
|
||||
}
|
||||
@@ -233,6 +242,7 @@ export class ManagedTable extends React.Component<
|
||||
}
|
||||
this.setState({
|
||||
columnOrder: nextProps.columnOrder,
|
||||
columnKeys: this.computeColumnKeys(nextProps.columnOrder),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -277,6 +287,10 @@ export class ManagedTable extends React.Component<
|
||||
this.firstUpdate = false;
|
||||
}
|
||||
|
||||
computeColumnKeys(columnOrder: TableColumnOrder) {
|
||||
return columnOrder.map(k => (k.visible ? k.key : null)).filter(notNull);
|
||||
}
|
||||
|
||||
scrollToHighlightedRows = () => {
|
||||
const {current} = this.tableRef;
|
||||
const {highlightedRows} = this.state;
|
||||
@@ -609,34 +623,24 @@ export class ManagedTable extends React.Component<
|
||||
|
||||
getRow = ({index, style}: {index: number; style: React.CSSProperties}) => {
|
||||
const {onAddFilter, multiline, zebra, rows} = this.props;
|
||||
const {columnOrder, columnSizes, highlightedRows} = this.state;
|
||||
const columnKeys: Array<string> = columnOrder
|
||||
.map(k => (k.visible ? k.key : null))
|
||||
.filter(notNull);
|
||||
const {columnKeys, columnSizes, highlightedRows} = this.state;
|
||||
|
||||
return (
|
||||
<ContextMenu
|
||||
buildItems={
|
||||
this.props.buildContextMenuItems || this.buildContextMenuItems
|
||||
}>
|
||||
<TableRow
|
||||
key={rows[index].key}
|
||||
columnSizes={columnSizes}
|
||||
columnKeys={columnKeys}
|
||||
onMouseDown={e => this.onHighlight(e, rows[index], index)}
|
||||
onMouseEnter={e => this.onMouseEnterRow(e, rows[index], index)}
|
||||
multiline={multiline}
|
||||
rowLineHeight={24}
|
||||
highlighted={highlightedRows.has(rows[index].key)}
|
||||
row={rows[index]}
|
||||
index={index}
|
||||
style={
|
||||
rows[index].height ? {...style, height: rows[index].height} : style
|
||||
}
|
||||
onAddFilter={onAddFilter}
|
||||
zebra={zebra}
|
||||
/>
|
||||
</ContextMenu>
|
||||
<TableRow
|
||||
key={rows[index].key}
|
||||
columnSizes={columnSizes}
|
||||
columnKeys={columnKeys}
|
||||
onMouseDown={this.onHighlight}
|
||||
onMouseEnter={this.onMouseEnterRow}
|
||||
multiline={multiline}
|
||||
rowLineHeight={24}
|
||||
highlighted={highlightedRows.has(rows[index].key)}
|
||||
row={rows[index]}
|
||||
index={index}
|
||||
style={style}
|
||||
onAddFilter={onAddFilter}
|
||||
zebra={zebra}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -686,7 +690,14 @@ export class ManagedTable extends React.Component<
|
||||
)}
|
||||
<Container>
|
||||
{this.props.autoHeight ? (
|
||||
this.props.rows.map((_, index) => this.getRow({index, style: {}}))
|
||||
<ContextMenu
|
||||
buildItems={
|
||||
this.props.buildContextMenuItems || this.buildContextMenuItems
|
||||
}>
|
||||
{this.props.rows.map((_, index) =>
|
||||
this.getRow({index, style: EMPTY_OBJECT}),
|
||||
)}
|
||||
</ContextMenu>
|
||||
) : (
|
||||
<AutoSizer>
|
||||
{({width, height}) => (
|
||||
|
||||
@@ -115,8 +115,12 @@ TableBodyColumnContainer.displayName = 'TableRow:TableBodyColumnContainer';
|
||||
type Props = {
|
||||
columnSizes: TableColumnSizes;
|
||||
columnKeys: TableColumnKeys;
|
||||
onMouseDown: (e: React.MouseEvent) => any;
|
||||
onMouseEnter?: (e: React.MouseEvent) => void;
|
||||
onMouseDown: (e: React.MouseEvent, row: TableBodyRow, index: number) => void;
|
||||
onMouseEnter?: (
|
||||
e: React.MouseEvent,
|
||||
row: TableBodyRow,
|
||||
index: number,
|
||||
) => void;
|
||||
multiline?: boolean;
|
||||
rowLineHeight: number;
|
||||
highlighted: boolean;
|
||||
@@ -132,6 +136,14 @@ export default class TableRow extends React.PureComponent<Props> {
|
||||
zebra: true,
|
||||
};
|
||||
|
||||
handleMouseDown = (e: React.MouseEvent) => {
|
||||
this.props.onMouseDown(e, this.props.row, this.props.index);
|
||||
};
|
||||
|
||||
handleMouseEnter = (e: React.MouseEvent) => {
|
||||
this.props.onMouseEnter?.(e, this.props.row, this.props.index);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
index,
|
||||
@@ -142,8 +154,6 @@ export default class TableRow extends React.PureComponent<Props> {
|
||||
multiline,
|
||||
columnKeys,
|
||||
columnSizes,
|
||||
onMouseEnter,
|
||||
onMouseDown,
|
||||
zebra,
|
||||
onAddFilter,
|
||||
} = this.props;
|
||||
@@ -157,8 +167,8 @@ export default class TableRow extends React.PureComponent<Props> {
|
||||
multiline={multiline}
|
||||
even={index % 2 === 0}
|
||||
zebra={zebra}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
style={style}
|
||||
highlightOnHover={row.highlightOnHover}
|
||||
data-key={row.key}
|
||||
|
||||
Reference in New Issue
Block a user