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

Calling ReactDOM.render() many many times is slow #12700

Closed
walkerburgin opened this issue Apr 26, 2018 · 8 comments
Closed

Calling ReactDOM.render() many many times is slow #12700

walkerburgin opened this issue Apr 26, 2018 · 8 comments

Comments

@walkerburgin
Copy link

walkerburgin commented Apr 26, 2018

Do you want to request a feature or report a bug?

Bug (?)

What is the current behavior?

Calling ReactDOM.render() many many times seems to have meaningfully worse performance than rendering many elements within a single React root.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:

// Edited to add dev/production builds

(These examples are obviously pretty contrived)

What is the expected behavior?

In a perfect world there wouldn't be such a large performance discrepancy between these two approaches.

For context, I'm working with a frontend plugin framework and trying not to expose React (which should ideally be an implementation detail) as part of the plugin interface.

An interface like this requires both the host and the plugin to be implemented with React and to share the same instance of React... but is fast and convenient when they do:

render(props: T): JSX.Element;

An interface like this treats React as an implementation detail, but is less convenient and (more importantly) incurs the above performance problem:

render(props:T, element: HTMLElement): void; 
unmount(element: HTMLElement): void;

I imagine this use case isn't a high priority for React/ReactDOM, but I'd love to understand a bit better what it is that really causes the performance difference and whether it's likely to ever change.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

  • Seems to impact both React 15 & 16 similarly
  • I don't think it's browser dependent
@gaearon
Copy link
Collaborator

gaearon commented Apr 26, 2018

I haven't looked yet but my first question is whether the difference is significant in production mode. The fiddles you're showing are running in development mode.

@walkerburgin
Copy link
Author

From what I've seen the difference is similar in production mode, you just start to really see it at higher element counts.

I've played around a little trying to understand how different usage patters impact it. Frequency of updates which require a DOM change, for example, or updates vs lots of mounting & unmounting components. So far I haven't really isolated anything particularly useful.

@miraage
Copy link

miraage commented Apr 27, 2018

Calling ReactDOM.render() many many times seems to have meaningfully worse performance than rendering many elements within a single React root.

Not sure if I understand React under the hood correctly, but let's think about it.
When you call ReactDOM.render you need to destroy/replace contents of the target node.
Meanwhile, if you use a single root then Virtual DOM will take care of DOM operations.

We know that DOM mutations are pretty (very?) expensive.

So.. I wouldn't be surprised.

@walkerburgin
Copy link
Author

When you call ReactDOM.render you need to destroy/replace contents of the target node.

Is this actually true? My understanding is that that's actually not the case. With the repro examples what's actually being rendered is the same in either case way so the number of DOM mutations don't have to be wildly different.

That said, I can understand some overhead for each root and how many roots might make batching / scheduling worse -- I just don't know enough about the guts of React's implementation to fully explain it.

@gaearon
Copy link
Collaborator

gaearon commented Apr 27, 2018

There shouldn't be a difference in DOM mutations.

@gaearon
Copy link
Collaborator

gaearon commented Aug 7, 2018

I fixed the biggest difference that jumped out at me in #13335.

There's some other differences in DEV (e.g. in React 15 we don't emit perf measurements by default, but in React 16 we do, and they have a fixed cost). But in production it should get better in the next patch release that includes #13335 (presumably 16.4.3 or 16.5.0).

Note in general though we don't recommend using many roots when it's avoidable. It's supported of course but it makes React's job more difficult. As far as I can see, if you use createPortal() in the render method instead of having many ReactDOM.render() calls, the performance significantly improves even in the version of React that's currently published. In general, I think your goal of abstracting away React can make it more difficult for you to benefit from React-specific optimizations like time slicing in the future — so that's another thing to consider.

Thanks for the repro case!

@gaearon gaearon closed this as completed Aug 7, 2018
@NE-SmallTown
Copy link
Contributor

NE-SmallTown commented Aug 15, 2018

@gaearon

if you use createPortal() in the render method instead of having many ReactDOM.render() calls

If so, why in the React tests we use calling the ReactNoop.render or ReactDOM.render many times, should we use other way to test such as setState in the root component?

@gaearon
Copy link
Collaborator

gaearon commented Aug 15, 2018

The difference is not significant enough to matter in tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants