-
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] Batch Child Rendering #1201
Conversation
Add or remove a limited number of children at a time to reduce to DOM reconcilation time. This removes jank from scrolling, but introduces a "scanline" render style for table cells. This is preferable to slow, janky framerates.
packages/table/src/common/batcher.ts
Outdated
|
||
export interface IIndex<T> { | ||
[key: string]: T; | ||
} |
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.
Dict<string, T>
😱
packages/table/src/common/batcher.ts
Outdated
[key: string]: T; | ||
} | ||
|
||
export type SimpleStringifyable = string | number | null; |
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.
undefined
too?
packages/table/src/common/batcher.ts
Outdated
* | ||
* A typical usage would be: | ||
* | ||
* ```typescript |
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.
"```tsx", and remove initial blank line
packages/table/src/common/batcher.ts
Outdated
* batcher.startNewBatch(); | ||
* | ||
* allChildrenKeys.forEach((prop1, index) => { | ||
* batcher.addArgsToBatch(prop1, "prop2", index); |
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.
prop1, "prop2"
1, 2 intentional?
packages/table/src/common/batcher.ts
Outdated
* The arguments must be simple stringifyable objects. | ||
*/ | ||
public addArgsToBatch(...args: SimpleStringifyable[]) { | ||
this.batchArgs[args.join("-")] = args; |
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.
if you must join-hash
, at least use a less common character, such as |
or 😱
packages/table/src/common/batcher.ts
Outdated
* part of the "batch" set but not the "current" set are candidates for | ||
* addition. | ||
* | ||
* The number of objects added and removed maybe limited with the `..Limit` |
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.
may be limited
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.
ok comments in this file need some general editing, i'm not going to call out each typo here 📖
"startIndex", | ||
"style", | ||
"viewportRect", | ||
] as Array<keyof IInternalHeaderProps>; |
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.
declare type up front instead of casting. const X: Array<...> = [...]
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.
we should expand https://palantir.github.io/tslint/rules/no-object-literal-type-assertion/ to cover this kind of thing
@PureRender | ||
export class Header extends React.Component<IInternalHeaderProps, IHeaderState> { | ||
public state: IHeaderState = { | ||
hasSelectionEnded: false, | ||
}; | ||
|
||
protected activationIndex: number; | ||
private batcher = new Batcher<React.ReactElement<any>>(); |
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.
use JSX.Element
instead--it's equivalent and shorter
@PureRender | ||
export class Header extends React.Component<IInternalHeaderProps, IHeaderState> { | ||
public state: IHeaderState = { | ||
hasSelectionEnded: false, | ||
}; | ||
|
||
protected activationIndex: number; | ||
private batcher = new Batcher<React.ReactElement<any>>(); |
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.
use JSX.Element
instead--it's equivalent and shorter
public componentWillReceiveProps(nextProps?: IHeaderProps) { | ||
public componentWillReceiveProps(nextProps?: IInternalHeaderProps) { | ||
const resetKeysBlacklist = { exclude: RESET_CELL_KEYS_BLACKLIST }; | ||
const resetBatcher = !Utils.shallowCompareKeys(this.props, nextProps, resetKeysBlacklist); |
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.
shouldResetBatcher
?
fix isotestPreview: documentation | table |
Performance comparison: Prior to these changes: With these changes: |
packages/table/src/common/batcher.ts
Outdated
/** | ||
* This class helps batch updates to large lists. | ||
* | ||
* For example, if your react component has many children, updating them all at |
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.
ubernit: React
packages/table/src/common/batcher.ts
Outdated
* The arguments must be simple stringifyable objects. | ||
*/ | ||
public addArgsToBatch(...args: SimpleStringifyable[]) { | ||
this.batchArgs[args.join("|")] = args; |
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.
nit: going off of @giladgray's earlier comment, would be clearer for me as a newcomer to this code to see this character pulled out as a DELIMITER
or RARE_DELIMITER
constant.
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.
👍
addNewLimit = Batcher.DEFAULT_ADD_LIMIT, | ||
removeOldLimit = Batcher.DEFAULT_REMOVE_LIMIT, | ||
updateLimit = Batcher.DEFAULT_UPDATE_LIMIT, | ||
) { |
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.
Cool formatting. 👍
packages/table/src/common/batcher.ts
Outdated
const toRemove = this.setKeysOperation(this.currentObjects, this.batchArgs, "difference", removeOldLimit); | ||
toRemove.forEach((key) => delete this.currentObjects[key]); | ||
|
||
// remove ALL old object not in batch |
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.
nit: objects
packages/table/src/common/batcher.ts
Outdated
toRemove.forEach((key) => delete this.currentObjects[key]); | ||
|
||
// remove ALL old object not in batch | ||
const toRemoveOld = this.setKeysOperation(this.oldObjects, this.batchArgs, "difference", -1); |
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.
nit: a null
limit seems more intuitive to me than a -1
limit.
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.
i disagree. I think it's more natural (or at least more common) to use a number than a null
value
*/ | ||
requestAnimationFrame(() => { | ||
requestAnimationFrame(() => { | ||
postMessage(MESSAGE_EVENT_DATA, "*"); |
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.
Putting the BILL in BrILLiant. 👏 👏 👏
* At most one callback per frame is invoked, and the callback may be delayed | ||
* multiple frames until the page is idle. | ||
* | ||
* TODO: return a token from this method that allows you to cancel the callback |
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.
Can we track this TODO in a Github issue if it's not going to get done immediately?
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.
let shouldResetBatcher = !Utils.shallowCompareKeys(this.props, nextProps, resetKeysBlacklist); | ||
// const stateDiff = Utils.shallowCompareKeys(this.state, nextState); | ||
// //tslint:disable-next-line | ||
// console.log(stateDiff, shouldResetBatcher, this.state, nextState); |
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.
remove commented code
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.
oh whoops
packages/table/src/tableBody.tsx
Outdated
return this.renderCell(row, col, extremaClasses, isGhost); | ||
}); | ||
if (!this.batcher.isDone()) { | ||
this.batcher.idleCallback(() => this.forceUpdate()); |
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.
To avoid instantiating a new anonymous function on each render
, can we just do this.batcher.idleCallback(this.forceUpdate)
?
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.
the forceUpdate
method not bound to this
and this.forceUpdate.bind(this)
is equivalent.
packages/table/src/tableBody.tsx
Outdated
@@ -146,6 +180,7 @@ export class TableBody extends React.Component<ITableBodyProps, {}> { | |||
</ContextMenuTargetWrapper> | |||
</DragSelectable> | |||
); | |||
|
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.
nit: don't need this newline
lintPreview: documentation | table |
strangely, the big table stops rendering at 838,861 rows. this seems to be a problem on |
Merge remote-tracking branch 'origin/master' into bd/table-perfPreview: documentation | table |
Merge remote-tracking branch 'origin/master' into bd/table-perfPreview: documentation | table |
PR CommentsPreview: documentation | table |
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.
Looks and feels good to me. Approving! 😬
Allows react components to add or remove a limited number of children at a time to reduce to DOM reconciliation time.
This removes jank while scrolling, but introduces a "scanline" render style for table cells. This is preferable to slow, janky frame rates.
Includes unit test for
Batcher
.Batch renders are invoked using a polyfill for
requestIdleCallback
that has these semantics: