-
Notifications
You must be signed in to change notification settings - Fork 7.8k
An update on async rendering #596
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
Changes from 1 commit
544c0bc
e298b47
9ff0f12
0674c34
cdb4b9e
289a2da
99fedea
49464f7
fe6b133
1314117
f005c04
06d5be4
3696388
ac23b1f
5cae7c6
f70c0dd
c1e67be
75a43aa
fb3b91f
60d65ce
f632f22
626ac42
7456327
2909738
5400338
813be17
8de7dc4
c45fb40
858c1a7
98d5a09
442591c
b1ce572
2312173
16eb646
4d16523
9905159
1ca6cfc
7408e07
3c75def
55650fc
97a109d
21fa116
92cf72d
fa34fcf
b3bf0bd
254fc8b
b0c22f7
558d576
7425aed
65b1496
6eae811
a2139de
030980e
e110ac5
ce060eb
e143823
65eca09
a3ea63a
7ced9ce
7cf5b58
9f72403
712f4de
4610392
b824bd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,9 +27,9 @@ We have been fine-tuning the performance of React with every new release. Howeve | |
|
||
We found that asynchronous rendering can help in several ways. For example: | ||
|
||
1. As users navigate within an app, newly displayed components often have asynchronous dependencies (including data, images, and code splitting). This can lead to a "cascade of spinners" as the data loads. We'd like to make it easier for product developers to express asynchronous dependencies of components- keeping the old UI "alive" for a certain period while the new UI is not "ready" yet. React could render this new UI in the background and provide a declarative way to show a spinner if it takes more than a second. | ||
1. As users navigate within an app, newly displayed components often have asynchronous dependencies (including data, images, and code splitting). This leads to a lot of boilerplate code managing data fetching and displaying the loading states. It can also lead to a “cascade of spinners” as the data loads, causing DOM reflows and janky user experience. We'd like to make it easier for product developers to express asynchronous dependencies of components- keeping the old UI "alive" for a certain period while the new UI is not "ready" yet. React could render this new UI in the background and provide a declarative way to show a loading indicator if it takes more than a second. | ||
2. Fast updates within a short timeframe often cause jank because React processes each update individually. We'd like to automatically "combine" updates within a few hundred milliseconds when possible so that there is less re-rendering. | ||
3. Some updates are inherently "less important" than others. For example, if you're writing a live-updating search filter input like [this](https://zeit.co/blog/domains-search-web#asynchronous-rendering), it is essential that the input is updated immediately (within a few milliseconds). Re-rendering the result list can be done later, and should not block the thread or cause stutter when typing. It would be nice if React had a way to mark the latter updates as having a "lower priority". | ||
3. Some updates are inherently less important than others. For example, if you're writing a live-updating search filter input like [this](https://zeit.co/blog/domains-search-web#asynchronous-rendering), it is essential that the input is updated immediately (within a few milliseconds). Re-rendering the result list can be done later, and should not block the thread or cause stutter when typing. It would be nice if React had a way to mark the latter updates as having a lower priority. (Note that even debouncing the input doesn't help because if the rendering is synchronous—like in React today—a keystroke can't interrupt the rendering if it already started. Asynchronous rendering solves this by splitting rendering into small chunks that can be paused and later restarted.) | ||
4. For UI elements like hidden popups and tabs, we'd like to be able to start pre-rendering their content when the browser isn't busy. This way, they can appear instantaneously in response to a later user interaction. However, we don't want to make the initial rendering slower, so it's essential to render such elements lazily ([when the browser is idle](https://developers.google.com/web/updates/2015/08/using-requestidlecallback)). | ||
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. Doesn't "lazily" mean "when needed" which is the opposite here? "However, we'd like to do this only [when the browser is idle] to avoid slowing down other parts of the page." |
||
5. For many apps, React is not the only JavaScript on the page. It often has to coordinate with other JS libraries, server-rendered widgets, and so on. Asynchronous rendering lets React better coordinate with non-React code regarding when components are inserted into the DOM so that [the user experience is smooth](https://twitter.com/acdlite/status/909926793536094209). | ||
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. Is this truly a different facet? It is a little unclear to me what exactly this means. Andrew's demo shows that with createBatch we can get back to what the sync behavior previously was without blocking, but I wouldn't say that async rendering helps these integrations. More than these integrations are something we need to support while doing items 1-4. I think it is fine to leave this bullet out unless I'm missing something. |
||
|
||
|
@@ -39,7 +39,7 @@ In the next section, we'll look at how to update your existing components to pre | |
|
||
## Updating class components | ||
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 this is a standalone post (my preference), we will need a section hereabouts summarizing the changes. I would like us to call out that the intention of the change is to make the lifecycle methods more functional and pure. And also say why instance variables are problematic (it's sorta implicit in your post but I'd love to have a sentence people can point to for this saying it's bad). 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. We kind of summarized most of the relevant changes in the intro to this (standalone) post. I think the only thing we haven't explicitly mentioned yet is the new static lifecycle. I'll try to introduce this. I'm not sure on wording yet. 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 added a section, "Before we begin, here’s a quick reminder of the lifecyle changes in version 16.3". Hopefully this is something like what yo had in mind. |
||
|
||
#### If you're an application developer, **you don't have to do anything about the deprecated methods yet**. The primary purpose of this update (v16.3) is to enable OSS maintainers to update their libraries in advance of any deprecation warnings. Those warnings will be enabled with the next minor release, v16.4. | ||
#### If you're an application developer, **you don't have to do anything about the deprecated methods yet**. The primary purpose of this update (v16.3) is to enable open source project maintainers to update their libraries in advance of any deprecation warnings. Those warnings will be enabled with the next minor release, v16.4. | ||
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. How do you feel about calling them "legacy" instead of "deprecated" throughout the post? Even the fact that they survive in the UNSAFE_ form means they're different from our usual deprecations. 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 think there's only two places I use the word "deprecated" in this post:
I don't mind replacing the second occurrence with "legacy" though. The phrase "deprecation warning" also appears twice, but I don't think "legacy warning" makes as much sense. |
||
|
||
However, if you'd like to start using the new component API (or if you're a maintainer looking to update your library in advance) here are a few examples that we hope will help you to start thinking about components a bit differently. Over time, we plan to add additional “recipes” to our documentation that show how to perform common tasks in a way that's async-safe. | ||
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. "new component API" sounds like we overhauled the whole component API |
||
|
||
|
@@ -72,7 +72,9 @@ Here is an example of a component that subscribes to an external event dispatche | |
|
||
Unfortunately, this can cause memory leaks for server rendering (where `componentWillUnmount` will never be called) and async rendering (where rendering might be interrupted before it completes, causing `componentWillUnmount` not to be called). | ||
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 and the lifecycle method is being deprecated 😝 might be worth calling out that there's more symmetry between 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. Yeah, I like this way of framing it. I'm a little unsure of the wording, but I'll take a stab at it. 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. Nice hammering of the point that most of these patterns are already SSR-unsafe 👍 |
||
|
||
The solution is to use the `componentDidMount` lifecycle instead: | ||
People often assume that `componentWillMount` and `componentWillUnmount` are paired, but that is not guaranteed. Only once `componentDidMount` has been called does React guarantee that `componentWillUnmount` will later be called (for clean up). | ||
|
||
For this reason, the recommended way to add listeners (or subscriptions) is to use the `componentDidMount` lifecycle: | ||
`embed:update-on-async-rendering/adding-event-listeners-after.js` | ||
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. After this example, can we acknowledge that this is a little clunky (since it is definitely more code even without the proper cWRP here) and we'll try to improve it later? "Although this is slightly more code, this pattern means that the subscription creation can be deferred until after the component renders on screen, reducing the amount of time in the critical render path. In the future, React may include more tools to manage data fetching efficiently and reduce code complexity." something like that 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. Great. I had the same concern. |
||
|
||
### Updating `state` based on `props` | ||
|
@@ -97,7 +99,7 @@ Here is an example of a component that calls an external function when its inter | |
This would not be safe to do in async mode, because the external callback might get called multiple times for a single update. Instead, the `componentDidUpdate` lifecycle should be used since it is guaranteed to be invoked only once per update: | ||
`embed:update-on-async-rendering/invoking-external-callbacks-after.js` | ||
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. Is calling props.onChange during render even a supported pattern? I guess it can be but it feels dicey and strikes me as not-well-thought-out data flow. (esp. if it leads to another rerender) Maybe we can replace this with something that is clearly not side effectful within the application. Performance logging came to mind but I guess that is the one case where you would want componentWillUpdate. Would really love to find a better example here before publishing. 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 found places within Facebook code where we were doing this. That made me want to specifically call it out as a pattern to avoid. |
||
|
||
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'd add:
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 like this clarification. Side note: I wonder how async will impact this. I think it will be important to at least have the option of doing a sync 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 think the idea is that even in async, |
||
## OSS maintainers | ||
## Open source project maintainers | ||
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'd add a section before this one. Something like
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. Love it. |
||
|
||
Open source maintainers might be wondering what these changes mean for shared components. If you implement the above suggestions, what happens with components that depend on the new static `getDerivedStateFromProps` lifecycle? Do you also have to release a new major version and drop compatibility for React 16.2 and older? | ||
|
||
|
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.
Maybe: insert a paragraph about how React batches/coalesces updates already? I've always found that people are receptive to batching and in some sense, async rendering is an extension of that.