Skip to content
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] Add @PureRender everywhere and eliminate excessive RegionLayer re-renders #988

Closed
wants to merge 5 commits into from

Conversation

cmslewis
Copy link
Contributor

@cmslewis cmslewis commented Apr 14, 2017

Checklist

  • Include tests

Changes proposed in this pull request:

  • Added PureRender wherever it was missing. Nothing huge to be gained from here, probably.
  • Bigger win: RegionLayer was re-rendering A TON before. In particular, it would re-render on every mousemove when re-ordering, because the unchanging RegionLayer.props.getRegionStyle functions were being recreated inline in table.tsx every cycle. I moved those styler functions out into the <Table> component body. Now the only re-render happens when reordering is complete and the selection needs to move. (That's hundreds of renders down to ONE!)

Reviewers should focus on:

  • Does it make sense to add @PureRender everywhere I added it?
  • The shouldComponentUpdate logic in RegionLayer. I had to omit props.regions from the shallowCompare check and add a new function in Regions to explicitly check regionArraysEqual. @PureRender wasn't sufficient here.
  • There's a commit+revert in the commit history on this PR. That was from earlier work in a different direction. You can ignore those two commits.

@blueprint-bot
Copy link

Revert "Add Utils.shallowCompare plus unit tests for shallowCompare and shallowCompareKeys"

Preview: documentation | table
Coverage: core | datetime

@blueprint-bot
Copy link

Oops, fix test description

Preview: documentation | table
Coverage: core | datetime

Copy link
Contributor

@giladgray giladgray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good!

export class RegionLayer extends React.Component<IRegionLayerProps, {}> {
public shouldComponentUpdate(nextProps: IRegionLayerProps) {
return !Utils.shallowCompareKeys(this.props, nextProps, UPDATE_PROPS_KEYS)
|| !Regions.regionArraysEqual(this.props.regions, nextProps.regions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is array equality not sufficient here such that we have to do this deep compare?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, see Regions.joinStyledRegionGroups, which creates a new regions array every time props are passed into RegionLayer.

@@ -528,6 +528,20 @@ export class Regions {
return regionGroups;
}

public static regionArraysEqual(regionsA: IRegion[], regionsB: IRegion[]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i might call this DeepEqual to clarify that it's much more than a === check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted.

public static regionArraysEqual(regionsA: IRegion[], regionsB: IRegion[]) {
if (regionsA === undefined && regionsB === undefined) {
return true;
} else if (regionsA === null && regionsB === null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you have a good reason for differentiating btw null and undefined here? given deep equality, those two seem the same to me.

null == undefined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No great reason aside from the desire to use === everywhere for consistency. Strong aversion to keeping as is? I'm ambivalent.

} else {
return regionsA
.map((regionA, i) => Regions.regionsEqual(regionA, regionsB[i]))
.every((isEqual) => isEqual);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please just replace map with every. all Array iterators receive the same three args. no need to create a new array of booleans.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, realized this after the fact haha.

style.right = "0px";
style.bottom = "0px";
style.top = "0px";
style.left = "0px";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be safe (and preferred, by stylelint at least) to use number 0 directly. style.right = 0;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, though I'd prefer to wait until a future PR just to minimize sneaky changes in this refactor.

}
}

private styleRowHeaderRegion = (region: IRegion): React.CSSProperties => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder how much work it would be to make all these functions static? they could even live in a separate file.

you'd have to pull the this.grid and this.state.viewportRect logic out. maybe even accept an optional object of base styles to modify?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh, interesting. Would love to try, but okay if I wait until a future PR?

}
};
return this.maybeRenderRegions(styler);
return this.maybeRenderRegions(this.styleColumnHeaderRegion);
}

private maybeRenderRowHeaderRegions() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

each of these maybeRender* functions above are used exactly once, so consider just inlining them.

Copy link
Contributor Author

@cmslewis cmslewis Apr 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulled these out for the perf benefit: before, the constant recreation of new inline functions (const styleFn = ... was causing === checks to fail when comparing function instances in RegionLayer.shouldComponentUpdate, triggering tons of additional re-renders.

My latest work work passes the computed region styles directly into RegionLayer, so we no longer care about ===-ing these function instances within RegionLayer (the function isn't even passed in anymore). But we still need to pass styler functions into maybeRenderRegions, and I'd rather refer to a single, unchanging function instance than constantly recreate styler functions like we used to do. Helps reduce activity on each render.

@cmslewis
Copy link
Contributor Author

Some of the bugs that emerged in this PR were pretty nefarious (big one: region layers not updating when you resize columns), to the point that I made a second attempt at this in a separate branch. Closing this PR in favor of a new one that I'll open momentarily.

@cmslewis cmslewis closed this Apr 17, 2017
@cmslewis cmslewis deleted the cl/table-perf branch April 17, 2017 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants