-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[table] Perf improvements with selections #2005
Changes from 2 commits
2a04c30
7d3780e
e6ba864
ef2688e
61d93d3
23ecc46
6173d33
00097ed
e99ee77
f660043
5873014
1e1bae2
8d85259
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ import * as classNames from "classnames"; | |
import * as React from "react"; | ||
import * as Classes from "../common/classes"; | ||
|
||
import { Classes as CoreClasses, IIntentProps, IProps, Utils as CoreUtils } from "@blueprintjs/core"; | ||
import { Classes as CoreClasses, IIntentProps, IProps } from "@blueprintjs/core"; | ||
|
||
import { LoadableContent } from "../common/loadableContent"; | ||
import { JSONFormat } from "./formats/jsonFormat"; | ||
|
@@ -93,20 +93,19 @@ export type ICellRenderer = (rowIndex: number, columnIndex: number) => React.Rea | |
|
||
export const emptyCellRenderer = () => <Cell />; | ||
|
||
/* | ||
* Not a PureComponent because any children don't get included in the shallow comparison. | ||
* - if we have children we always want to rerender in case the children have changed. | ||
* - if we don't have children the rerender is so cheap it isn't a problem. | ||
* | ||
* Note that due to React's typings we can't check for the presence of children in shouldComponentUpdate() in a typesafe way. | ||
*/ | ||
export class Cell extends React.Component<ICellProps, {}> { | ||
public static defaultProps = { | ||
truncated: true, | ||
wrapText: false, | ||
}; | ||
|
||
public shouldComponentUpdate(nextProps: ICellProps) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a concerning deletion. input from @themadcreator @cmslewis on the impact here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm I suppose it is inconsistent with my comment above where I claim that children can be memoized, so the shallow compare would be useful. Might be better to revert to the old code and add a comment making clear that the shallow compare will fail on children unless they are memoized. |
||
// deeply compare "style," because a new but identical object might have been provided. | ||
return ( | ||
!CoreUtils.shallowCompareKeys(this.props, nextProps, { exclude: ["style"] }) || | ||
!CoreUtils.deepCompareKeys(this.props.style, nextProps.style) | ||
); | ||
} | ||
|
||
public render() { | ||
const { | ||
cellRef, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -681,28 +681,36 @@ export class Table extends AbstractComponent<ITableProps, ITableState> { | |
selectionModes, | ||
} = nextProps; | ||
|
||
const newChildArray = React.Children.toArray(children) as Array<React.ReactElement<IColumnProps>>; | ||
const hasNewChildren = this.props.children !== nextProps.children; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if i'm not mistaken, this will be true every time as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. children may be memoized There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Also, RE: "may be": do you mean the children will be memoized only if a developer does so explicitly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes exactly - it needs to be explicitly done by the developer. Memoizing props is a standard part of getting better perf in react so seems reasonable? |
||
const newChildArray = hasNewChildren ? React.Children.toArray(children) as Array<React.ReactElement<IColumnProps>> : this.childrenArray; | ||
const numCols = newChildArray.length; | ||
|
||
// Try to maintain widths of columns by looking up the width of the | ||
// column that had the same `ID` prop. If none is found, use the | ||
// previous width at the same index. | ||
const previousColumnWidths = newChildArray.map((child: React.ReactElement<IColumnProps>, index: number) => { | ||
const mappedIndex = this.columnIdToIndex[child.props.id]; | ||
return this.state.columnWidths[mappedIndex != null ? mappedIndex : index]; | ||
}); | ||
|
||
// Make sure the width/height arrays have the correct length, but keep | ||
// as many existing widths/heights when possible. Also, apply the | ||
// sparse width/heights from props. | ||
let gridInvalidationRequired = false; | ||
giladgray marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let newColumnWidths = this.state.columnWidths; | ||
newColumnWidths = Utils.arrayOfLength(newColumnWidths, numCols, defaultColumnWidth); | ||
newColumnWidths = Utils.assignSparseValues(newColumnWidths, previousColumnWidths); | ||
newColumnWidths = Utils.assignSparseValues(newColumnWidths, columnWidths); | ||
if (defaultColumnWidth !== this.props.defaultColumnWidth || columnWidths !== this.props.columnWidths || (hasNewChildren && !this.areChildrenSameShape(newChildArray))) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess my assumption here is that clients who are performance-sensitive will take care to memoize these arrays where possible, which is standard practice to get the benefits of pure-render semantics in react. |
||
// Try to maintain widths of columns by looking up the width of the | ||
// column that had the same `ID` prop. If none is found, use the | ||
// previous width at the same index. | ||
const previousColumnWidths = newChildArray.map((child: React.ReactElement<IColumnProps>, index: number) => { | ||
const mappedIndex = this.columnIdToIndex[child.props.id]; | ||
return this.state.columnWidths[mappedIndex != null ? mappedIndex : index]; | ||
}); | ||
|
||
// Make sure the width/height arrays have the correct length, but keep | ||
// as many existing widths/heights when possible. Also, apply the | ||
giladgray marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// sparse width/heights from props. | ||
newColumnWidths = Utils.arrayOfLength(newColumnWidths, numCols, defaultColumnWidth); | ||
newColumnWidths = Utils.assignSparseValues(newColumnWidths, previousColumnWidths); | ||
newColumnWidths = Utils.assignSparseValues(newColumnWidths, columnWidths); | ||
gridInvalidationRequired = true; | ||
giladgray marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
let newRowHeights = this.state.rowHeights; | ||
newRowHeights = Utils.arrayOfLength(newRowHeights, numRows, defaultRowHeight); | ||
newRowHeights = Utils.assignSparseValues(newRowHeights, rowHeights); | ||
if (defaultRowHeight !== this.props.defaultRowHeight || rowHeights !== this.props.rowHeights) { | ||
newRowHeights = Utils.arrayOfLength(newRowHeights, numRows, defaultRowHeight); | ||
newRowHeights = Utils.assignSparseValues(newRowHeights, rowHeights); | ||
gridInvalidationRequired = true; | ||
} | ||
|
||
let newSelectedRegions = selectedRegions; | ||
if (selectedRegions == null) { | ||
|
@@ -724,9 +732,13 @@ export class Table extends AbstractComponent<ITableProps, ITableState> { | |
newSelectedRegions, | ||
); | ||
|
||
this.childrenArray = newChildArray; | ||
this.columnIdToIndex = Table.createColumnIdIndex(this.childrenArray); | ||
this.invalidateGrid(); | ||
if (hasNewChildren) { | ||
this.childrenArray = newChildArray; | ||
this.columnIdToIndex = Table.createColumnIdIndex(this.childrenArray); | ||
} | ||
if (gridInvalidationRequired) { | ||
this.invalidateGrid(); | ||
} | ||
this.setState({ | ||
columnWidths: newColumnWidths, | ||
focusedCell: newFocusedCell, | ||
|
@@ -737,6 +749,22 @@ export class Table extends AbstractComponent<ITableProps, ITableState> { | |
}); | ||
} | ||
|
||
private areChildrenSameShape(newChildArray: Array<React.ReactElement<IColumnProps>>) { | ||
if (this.childrenArray.length !== newChildArray.length) { | ||
return false; | ||
} | ||
return this.childrenArray.every((child, index) => { | ||
const newChild = newChildArray[index]; | ||
if (child === newChild) { | ||
return true; | ||
} | ||
if (child == null || newChild == null) { | ||
return false; | ||
} | ||
return child.props.id === newChild.props.id; | ||
}); | ||
} | ||
|
||
public render() { | ||
const { | ||
children, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be optional. i think that's the source of compiler errors.
but also you have lint failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks