Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Suspense for CPU-bound trees (#19936)
Adds a new prop to the Suspense component type, `unstable_expectedLoadTime`. The presence of this prop indicates that the content is computationally expensive to render. During the initial mount, React will skip over expensive trees by rendering a placeholder — just like we do with trees that are waiting for data to resolve. That will help unblock the initial skeleton for the new screen. Then we will continue rendering in the next commit. For now, while we experiment with the API internally, any number passed to `unstable_expectedLoadTime` will be treated as "computationally expensive", no matter how large or small. So it's basically a boolean. The reason it's a number is that, in the future, we may try to be clever with this additional information. For example, SuspenseList could use it as part of its heuristic to determine whether to keep rendering additional rows. Background ---------- Much of our early messaging and research into Suspense focused on its ability to throttle the appearance of placeholder UIs. Our theory was that, on a fast network, if everything loads quickly, excessive placeholders will contribute to a janky user experience. This was backed up by user research and has held up in practice. However, our original demos made an even stronger assertion: not only is it preferable to throttle successive loading states, but up to a certain threshold, it’s also preferable to remain on the previous screen; or in other words, to delay the transition. This strategy has produced mixed results. We’ve found it works well for certain transitions, but not for all them. When performing a full page transition, showing an initial skeleton as soon as possible is crucial to making the transition feel snappy. You still want throttle the nested loading states as they pop in, but you need to show something on the new route. Remaining on the previous screen can make the app feel unresponsive. That’s not to say that delaying the previous screen always leads to a bad user experience. Especially if you can guarantee that the delay is small enough that the user won’t notice it. This threshold is a called a Just Noticeable Difference (JND). If we can stay under the JND, then it’s worth skipping the first placeholder to reduce overall thrash. Delays that are larger than the JND have some use cases, too. The main one we’ve found is to refresh existing data, where it’s often preferable to keep stale content on screen while the new data loads in the background. It’s also useful as a fallback strategy if something suspends unexpectedly, to avoid hiding parts of the UI that are already visible. We’re still in the process of optimizing our heuristics for the most common patterns. In general, though, we are trending toward being more aggressive about prioritizing the initial skeleton. For example, Suspense is usually thought of as a feature for displaying placeholders when the UI is missing data — that is, when rendering is bound by pending IO. But it turns out that the same principles apply to CPU-bound transitions, too. It’s worth deferring a tree that’s slow to render if doing so unblocks the rest of the transition — regardless of whether it’s slow because of missing data or because of expensive CPU work. We already take advantage of this idea in a few places, such as hydration. Instead of hydrating server-rendered UI in a single pass, React splits it into chunks. It can do this because the initial HTML acts as its own placeholder. React can defer hydrating a chunk of UI as long as it wants until the user interacts it. The boundary we use to split the UI into chunks is the same one we use for IO-bound subtrees: the <Suspense /> component. SuspenseList does something similar. When streaming in a list of items, it will occasionally stop to commit whatever items have already finished, before continuing where it left off. It does this by showing a placeholder for the remaining items, again using the same <Suspense /> component API, even if the item is CPU-bound. Unresolved questions -------------------- There is a concern that showing a placeholder without also loading new data could be disorienting. Users are trained to believe that a placeholder signals fresh content. So there are still some questions we’ll need to resolve.
- Loading branch information