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

Move legacy hidden API to new internal Fiber type #18782

Merged
merged 3 commits into from
May 1, 2020

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Apr 29, 2020

Based on #18733

Diff compared to previous PR in stack

Adds a new LegacyHidden fiber type.

This only exists so we can clean up the internal implementation of <div hidden={isHidden} />, which is not a stable feature. The goal is to move everything to the new Offscreen type instead. However, Offscreen has different semantics, so before we can remove the legacy API, we have to migrate our internal usage at Facebook. So we'll need to maintain both temporarily.

If a host component receives a hidden prop, we wrap its children in an LegacyHidden fiber. This is similar to what we do for Suspense children.

The LegacyHidden type happens to share the same implementation as the new Offscreen type, for now, but using separate types allows us to fork the behavior later when we implement our planned changes to the Offscreen API.

There are two subtle semantic changes here. One is that the children of the host component wil have their visibility toggled using the same mechanism we use for Offscreen and Suspense: find the nearest host node children and give them a style of display: none. We didn't used to do this in the old API, because the hidden DOM attribute on the parent already hides them. So with this change, we're actually "overhiding" the children. I considered addressing this, but I figure I'll leave it as-is in case we want to expose the LegacyHidden component type temporarily to ease migration of Facebook's internal callers to the Offscreen type.

The other subtle semantic change is that, because of the extra fiber that wraps around the children, this pattern will cause the children to lose state:

return isHidden ? <div hidden={true} /> : <div />;

The reason is that I didn't want to wrap every single host component in an extra fiber. So I only wrap them if a hidden prop exists. In the above example, that means the children are conditionally wrapped in an extra fiber, so they don't line up during reconciliation, so they get remounted every time isHidden changes.

The fix is to rewrite to:

return <div hidden={isHidden} />;

I don't anticipate this will be a problem at Facebook, especially since we're only supposed to use hidden via a userspace wrapper component. (And since the bad pattern isn't very React-y, anyway.)

Again, the eventual goal is to delete this completely and replace it with Offscreen.

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Apr 29, 2020
@codesandbox-ci
Copy link

codesandbox-ci bot commented Apr 29, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 9b12dd6:

Sandbox Source
vigorous-shadow-igceb Configuration

@sizebot
Copy link

sizebot commented Apr 29, 2020

Details of bundled changes.

Comparing: ac533fd...9b12dd6

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.1 KB 13.1 KB 4.8 KB 4.8 KB NODE_PROD
react-dom-server.browser.development.js +0.1% +0.1% 154.84 KB 154.95 KB 39.46 KB 39.49 KB UMD_DEV
react-dom-unstable-fizz.node.production.min.js 0.0% 🔺+0.2% 1.16 KB 1.16 KB 658 B 659 B NODE_PROD
ReactDOMForked-dev.js +0.3% +0.4% 1.01 MB 1.01 MB 230.33 KB 231.14 KB FB_WWW_DEV
ReactDOMForked-prod.js 🔺+0.5% 🔺+0.5% 434.41 KB 436.49 KB 76.07 KB 76.44 KB FB_WWW_PROD
react-dom.development.js 0.0% 0.0% 908.34 KB 908.44 KB 199.77 KB 199.81 KB UMD_DEV
ReactDOMForked-profiling.js +0.5% +0.4% 444.81 KB 446.89 KB 77.82 KB 78.14 KB FB_WWW_PROFILING
react-dom.production.min.js 0.0% -0.0% 119.97 KB 119.97 KB 38.45 KB 38.45 KB UMD_PROD
react-dom.profiling.min.js 0.0% 0.0% 123.74 KB 123.74 KB 39.66 KB 39.66 KB UMD_PROFILING
ReactDOMTesting-dev.js 0.0% 0.0% 935.96 KB 936.06 KB 208.76 KB 208.79 KB FB_WWW_DEV
react-dom.development.js 0.0% 0.0% 864.63 KB 864.73 KB 197.24 KB 197.27 KB NODE_DEV
ReactDOMTesting-prod.js 0.0% 0.0% 390.82 KB 390.82 KB 71.16 KB 71.17 KB FB_WWW_PROD
react-dom-server.node.development.js +0.1% +0.1% 146.91 KB 147.01 KB 38.94 KB 38.97 KB NODE_DEV
react-dom.production.min.js 0.0% -0.0% 120.09 KB 120.09 KB 37.66 KB 37.66 KB NODE_PROD
react-dom-test-utils.development.js +0.1% +0.2% 75.22 KB 75.32 KB 20.15 KB 20.18 KB UMD_DEV
ReactDOMTesting-profiling.js 0.0% 0.0% 390.82 KB 390.82 KB 71.16 KB 71.17 KB FB_WWW_PROFILING
react-dom-server.node.production.min.js 0.0% -0.0% 23.27 KB 23.27 KB 8.67 KB 8.67 KB NODE_PROD
react-dom.profiling.min.js 0.0% -0.0% 123.98 KB 123.98 KB 38.82 KB 38.82 KB NODE_PROFILING
react-dom-test-utils.production.min.js 0.0% 0.0% 13.23 KB 13.23 KB 4.89 KB 4.89 KB UMD_PROD
ReactDOM-dev.js 0.0% 0.0% 1.02 MB 1.02 MB 231.67 KB 231.72 KB FB_WWW_DEV
react-dom-test-utils.development.js +0.1% +0.2% 70.06 KB 70.16 KB 19.64 KB 19.68 KB NODE_DEV
react-dom-unstable-fizz.node.development.js +1.8% +1.8% 5.5 KB 5.6 KB 1.82 KB 1.86 KB NODE_DEV
ReactTestUtils-dev.js +0.2% +0.2% 65.05 KB 65.15 KB 17.54 KB 17.57 KB FB_WWW_DEV
react-dom-server.browser.development.js +0.1% +0.1% 145.69 KB 145.79 KB 38.69 KB 38.72 KB NODE_DEV
react-dom-unstable-fizz.browser.development.js +2.0% +1.9% 5.25 KB 5.35 KB 1.76 KB 1.8 KB UMD_DEV
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.3% 1.19 KB 1.19 KB 696 B 698 B UMD_PROD
ReactDOMServer-dev.js +0.1% +0.1% 163.75 KB 163.85 KB 41.73 KB 41.76 KB FB_WWW_DEV
ReactDOMServer-prod.js 0.0% 0.0% 52.9 KB 52.9 KB 12.74 KB 12.74 KB FB_WWW_PROD
react-dom-unstable-fizz.browser.development.js +2.1% +1.9% 4.75 KB 4.85 KB 1.67 KB 1.7 KB NODE_DEV
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.3% 1 KB 1 KB 608 B 610 B NODE_PROD

react

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react.development.js +0.1% +0.1% 101.94 KB 102.04 KB 24.81 KB 24.84 KB UMD_DEV
react-unstable-cache.production.min.js 0.0% 🔺+0.4% 855 B 855 B 554 B 556 B NODE_PROD
react.production.min.js 0.0% 0.0% 11.39 KB 11.39 KB 4.55 KB 4.55 KB UMD_PROD
react-unstable-cache.profiling.min.js 0.0% +0.4% 854 B 854 B 554 B 556 B NODE_PROFILING
JSXDEVRuntime-dev.js +0.3% +0.3% 39.28 KB 39.38 KB 11.12 KB 11.15 KB FB_WWW_DEV
react-jsx-runtime.development.js +0.3% +0.4% 30.33 KB 30.43 KB 8.97 KB 9 KB NODE_DEV
react-jsx-runtime.production.min.js 0.0% 🔺+0.3% 947 B 947 B 594 B 596 B NODE_PROD
react-jsx-runtime.profiling.min.js 0.0% +0.3% 946 B 946 B 593 B 595 B NODE_PROFILING
JSXRuntime-dev.js +0.2% +0.3% 39.87 KB 39.97 KB 11.29 KB 11.33 KB FB_WWW_DEV
react-unstable-cache.development.js 0.0% +0.3% 1.04 KB 1.04 KB 579 B 581 B NODE_DEV
ReactCache-dev.js -87.6% -79.6% 8.16 KB 1.01 KB 2.65 KB 553 B FB_WWW_DEV
react.development.js +0.2% +0.2% 63.5 KB 63.6 KB 17.13 KB 17.16 KB NODE_DEV
ReactCache-prod.js -78.7% -62.3% 5.09 KB 1.08 KB 1.57 KB 608 B FB_WWW_PROD
react.production.min.js 0.0% 0.0% 6.32 KB 6.32 KB 2.62 KB 2.62 KB NODE_PROD
React-dev.js +0.1% +0.1% 98.87 KB 98.97 KB 24.18 KB 24.21 KB FB_WWW_DEV
React-prod.js 0.0% 0.0% 17.4 KB 17.4 KB 4.55 KB 4.55 KB FB_WWW_PROD
react-jsx-dev-runtime.development.js +0.3% +0.4% 29.75 KB 29.85 KB 8.8 KB 8.83 KB NODE_DEV
React-profiling.js 0.0% 0.0% 17.4 KB 17.4 KB 4.55 KB 4.55 KB FB_WWW_PROFILING
react-jsx-dev-runtime.production.min.js 0.0% 🔺+0.3% 441 B 441 B 314 B 315 B NODE_PROD
react-jsx-dev-runtime.profiling.min.js 0.0% +0.3% 440 B 440 B 312 B 313 B NODE_PROFILING

React: size: 0.0%, gzip: 0.0%

Size changes (stable)

Generated by 🚫 dangerJS against 9b12dd6

@sizebot
Copy link

sizebot commented Apr 29, 2020

Details of bundled changes.

Comparing: ac533fd...9b12dd6

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom-test-utils.development.js +0.1% +0.2% 75.23 KB 75.34 KB 20.16 KB 20.19 KB UMD_DEV
react-dom-unstable-fizz.browser.development.js +2.0% +1.9% 5.26 KB 5.36 KB 1.77 KB 1.8 KB UMD_DEV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.24 KB 13.24 KB 4.9 KB 4.9 KB UMD_PROD
ReactDOMTesting-dev.js 0.0% 0.0% 910.17 KB 910.27 KB 203.24 KB 203.27 KB FB_WWW_DEV
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.3% 1.2 KB 1.2 KB 704 B 706 B UMD_PROD
react-dom.development.js 0.0% 0.0% 942.22 KB 942.32 KB 206.23 KB 206.27 KB UMD_DEV
react-dom-server.browser.development.js +0.1% +0.1% 162.15 KB 162.26 KB 41.26 KB 41.29 KB UMD_DEV
react-dom.production.min.js 0.0% 0.0% 124.81 KB 124.81 KB 39.96 KB 39.96 KB UMD_PROD
ReactDOMForked-dev.js +0.3% +0.4% 1009.55 KB 1013.03 KB 224.79 KB 225.58 KB FB_WWW_DEV
react-dom-server.browser.production.min.js 0.0% 0.0% 23.43 KB 23.43 KB 8.61 KB 8.61 KB UMD_PROD
react-dom.profiling.min.js 0.0% 0.0% 128.68 KB 128.68 KB 41.1 KB 41.1 KB UMD_PROFILING
ReactDOMForked-prod.js 🔺+0.5% 🔺+0.5% 422.95 KB 424.94 KB 74.08 KB 74.43 KB FB_WWW_PROD
react-dom.development.js 0.0% 0.0% 897.06 KB 897.16 KB 203.7 KB 203.73 KB NODE_DEV
ReactDOMForked-profiling.js +0.5% +0.4% 433.29 KB 435.28 KB 75.81 KB 76.13 KB FB_WWW_PROFILING
react-dom-server.browser.development.js +0.1% +0.1% 152.64 KB 152.74 KB 40.49 KB 40.52 KB NODE_DEV
react-dom-server.browser.production.min.js 0.0% 0.0% 23.32 KB 23.32 KB 8.6 KB 8.6 KB NODE_PROD
react-dom.profiling.min.js 0.0% 0.0% 129.02 KB 129.02 KB 40.33 KB 40.33 KB NODE_PROFILING
ReactDOM-dev.js 0.0% 0.0% 1014.27 KB 1014.37 KB 226.14 KB 226.17 KB FB_WWW_DEV
ReactDOMServer-dev.js +0.1% +0.1% 160.25 KB 160.35 KB 40.82 KB 40.85 KB FB_WWW_DEV
ReactDOMServer-prod.js 0.0% 0.0% 52.01 KB 52.01 KB 12.55 KB 12.55 KB FB_WWW_PROD
react-dom-unstable-fizz.node.development.js +1.8% +1.8% 5.51 KB 5.61 KB 1.83 KB 1.87 KB NODE_DEV
react-dom-unstable-fizz.node.production.min.js 0.0% 🔺+0.3% 1.17 KB 1.17 KB 666 B 668 B NODE_PROD
react-dom-test-utils.development.js +0.1% +0.2% 70.07 KB 70.17 KB 19.65 KB 19.68 KB NODE_DEV
react-dom-unstable-fizz.browser.development.js +2.1% +1.9% 4.77 KB 4.87 KB 1.67 KB 1.7 KB NODE_DEV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.12 KB 13.12 KB 4.81 KB 4.81 KB NODE_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.3% 1.02 KB 1.02 KB 616 B 618 B NODE_PROD
ReactTestUtils-dev.js +0.2% +0.2% 65.04 KB 65.14 KB 17.55 KB 17.58 KB FB_WWW_DEV
react-dom-server.node.development.js +0.1% +0.1% 153.86 KB 153.96 KB 40.74 KB 40.77 KB NODE_DEV
react-dom-server.node.production.min.js 0.0% 0.0% 23.73 KB 23.73 KB 8.75 KB 8.75 KB NODE_PROD

react-cache

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-cache.development.js 0.0% 0.0% 9.35 KB 9.35 KB 3.03 KB 3.04 KB UMD_DEV
react-cache.production.min.js 0.0% 🔺+0.2% 2.36 KB 2.36 KB 1.2 KB 1.2 KB UMD_PROD
react-cache.development.js 0.0% +0.1% 8.61 KB 8.61 KB 2.93 KB 2.93 KB NODE_DEV
react-cache.production.min.js 0.0% 🔺+0.1% 2.15 KB 2.15 KB 1.1 KB 1.1 KB NODE_PROD
ReactCache-dev.js +706.6% +391.1% 1.01 KB 8.16 KB 553 B 2.65 KB FB_WWW_DEV
ReactCache-prod.js 🔺+369.4% 🔺+165.0% 1.08 KB 5.09 KB 608 B 1.57 KB FB_WWW_PROD

ReactDOM: size: 0.0%, gzip: 0.0%

Size changes (experimental)

Generated by 🚫 dangerJS against 9b12dd6

@acdlite
Copy link
Collaborator Author

acdlite commented Apr 29, 2020

Need to move the bailout here, too: EDIT: Done

case HostComponent:
pushHostContext(workInProgress);
if (
workInProgress.mode & ConcurrentMode &&
!isSameExpirationTime(
renderExpirationTime,
(Never: ExpirationTimeOpaque),
) &&
shouldDeprioritizeSubtree(workInProgress.type, newProps)
) {
if (enableSchedulerTracing) {
markSpawnedWork((Never: ExpirationTimeOpaque));
}
// Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime_opaque = workInProgress.childExpirationTime_opaque = Never;
return null;
}
break;

When a Suspense boundary is in its fallback state, you cannot switch
back to the main content without also finishing any updates inside the
tree that might have been skipped. That would be a form of tearing.

Before we fixed this in facebook#18411, the way this bug manifested was that a
boundary was suspended by an update that originated from a child
component (as opposed to props from a parent). While the fallback was
showing, it received another update, this time at high priority. React
would render the high priority update without also including the
original update. That would cause the fallback to switch back to the
main content, since the update that caused the tree to suspend was no
longer part of the render. But then, React would immediately try to
render the original update, which would again suspend and show the
fallback, leading to a momentary flicker in the UI.

The approach added in facebook#18411 is, when receiving a high priority update
to a Suspense tree that's in its fallback state is to bail out, keep
showing the fallback and finish the update in the rest of the tree.
After that commits, render again at the original priority. Because low
priority expiration times are inclusive of higher priority expiration
times, this ensures that all the updates are committed together.

The new approach in this commit is to turn `renderExpirationTime` into a
context-like value that lives on the stack. Then, when unhiding the
Suspense boundary, we can push a new `renderExpirationTime` that is
inclusive of both the high pri update and the original update that
suspended. Then the boundary can be unblocked in a single render pass.

An advantage of the old approach is that by deferring the work of
unhiding, there's less work to do in the high priority update.

The key advantage of the new approach is that it solves the consistency
problem without having to entangle the entire root.
This only exists so we can clean up the internal implementation of
`<div hidden={isHidden} />`, which is not a stable feature. The goal
is to move everything to the new Offscreen type instead. However,
Offscreen has different semantics, so before we can remove the legacy
API, we have to migrate our internal usage at Facebook. So we'll need
to maintain both temporarily.

In this initial commit, I've only added the type. It's not used
anywhere. The next step is to use it to implement `hidden`.
If a host component receives a `hidden` prop, we wrap its children in
an Offscreen fiber. This is similar to what we do for Suspense children.

The LegacyHidden type happens to share the same implementation as the
new Offscreen type, for now, but using separate types allows us to fork
the behavior later when we implement our planned changes to the
Offscreen API.

There are two subtle semantic changes here. One is that the children of
the host component will have their visibility toggled using the same
mechanism we use for Offscreen and Suspense: find the nearest host node
children and give them a style of `display: none`. We didn't used to do
this in the old API, because the `hidden` DOM attribute on the parent
already hides them. So with this change, we're actually "overhiding" the
children. I considered addressing this, but I figure I'll leave it as-is
in case we want to expose the LegacyHidden component type temporarily
to ease migration of Facebook's internal callers to the Offscreen type.

The other subtle semantic change is that, because of the extra fiber
that wraps around the children, this pattern will cause the children
to lose state:

```js
return isHidden ? <div hidden={true} /> : <div />;
```

The reason is that I didn't want to wrap every single host component
in an extra fiber. So I only wrap them if a `hidden` prop exists. In
the above example, that means the children are conditionally wrapped
in an extra fiber, so they don't line up during reconciliation, so
they get remounted every time `isHidden` changes.

The fix is to rewrite to:

```js
return <div hidden={isHidden} />;
```

I don't anticipate this will be a problem at Facebook, especially since
we're only supposed to use `hidden` via a userspace wrapper component.
(And since the bad pattern isn't very React-y, anyway.)

Again, the eventual goal is to delete this completely and replace it
with Offscreen.
@acdlite
Copy link
Collaborator Author

acdlite commented May 1, 2020

@sebmarkbage I'm going to go ahead and merge this so Luna can build on top. It only affects the new reconciler. If you have feedback after the fact, I'll address in a follow up.

@acdlite acdlite merged commit 914b57b into facebook:master May 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants