Files
react/packages/react-reconciler/src/ReactFiberUnwindWork.js
Sebastian Markbåge 50addf4c0e Refactor Partial Hydration (#16346)
* Move dehydrated to be child of regular SuspenseComponent

We now store the comment node on SuspenseState instead and that indicates
that this SuspenseComponent is still dehydrated.

We also store a child but that is only used to represent the DOM node for
deletions and getNextHostSibling.

* Move logic from DehydratedSuspenseComponent to SuspenseComponent

Forked based on SuspenseState.dehydrated instead.

* Retry logic for dehydrated boundary

We can now simplify the logic for retrying dehydrated boundaries without
hydrating. This is becomes simply a reconciliation against the dehydrated
fragment which gets deleted, and the new children gets inserted.

* Remove dehydrated from throw

Instead we use the regular Suspense path. To save code, we attach retry
listeners in the commit phase even though technically we don't have to.

* Pop to nearest Suspense

I think this is right...?

* Popping hydration state should skip past the dehydrated instance

* Split mount from update and special case suspended second pass

The DidCapture flag isn't used consistently in the same way. We need
further refactor for this.

* Reorganize update path

If we remove the dehydration status in the first pass and then do a second
pass because we suspended, then we need to continue as if it didn't
previously suspend. Since there is no fragment child etc.

However, we must readd the deletion.

* Schedule context work on the boundary and not the child

* Warn for Suspense hydration in legacy mode

It does a two pass render that client renders the content.

* Rename DehydratedSuspenseComponent -> DehydratedFragment

This now doesn't represent a suspense boundary itself. Its parent does.

This Fiber represents the fragment around the dehydrated content.

* Refactor returns

Avoids the temporary mutable variables. I kept losing track of them.

* Add a comment explaining the type.

Placing it in the type since that's the central point as opposed to spread
out.
2019-08-12 15:58:38 -07:00

147 lines
4.4 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {SuspenseState} from './ReactFiberSuspenseComponent';
import {
ClassComponent,
HostRoot,
HostComponent,
HostPortal,
ContextProvider,
SuspenseComponent,
SuspenseListComponent,
} from 'shared/ReactWorkTags';
import {DidCapture, NoEffect, ShouldCapture} from 'shared/ReactSideEffectTags';
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
import {popHostContainer, popHostContext} from './ReactFiberHostContext';
import {popSuspenseContext} from './ReactFiberSuspenseContext';
import {resetHydrationState} from './ReactFiberHydrationContext';
import {
isContextProvider as isLegacyContextProvider,
popContext as popLegacyContext,
popTopLevelContextObject as popTopLevelLegacyContextObject,
} from './ReactFiberContext';
import {popProvider} from './ReactFiberNewContext';
import invariant from 'shared/invariant';
function unwindWork(
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
switch (workInProgress.tag) {
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
const effectTag = workInProgress.effectTag;
if (effectTag & ShouldCapture) {
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
return null;
}
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
const effectTag = workInProgress.effectTag;
invariant(
(effectTag & DidCapture) === NoEffect,
'The root failed to unmount after an error. This is likely a bug in ' +
'React. Please file an issue.',
);
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
case HostComponent: {
// TODO: popHydrationState
popHostContext(workInProgress);
return null;
}
case SuspenseComponent: {
popSuspenseContext(workInProgress);
if (enableSuspenseServerRenderer) {
const suspenseState: null | SuspenseState =
workInProgress.memoizedState;
if (suspenseState !== null && suspenseState.dehydrated !== null) {
invariant(
workInProgress.alternate !== null,
'Threw in newly mounted dehydrated component. This is likely a bug in ' +
'React. Please file an issue.',
);
resetHydrationState();
}
}
const effectTag = workInProgress.effectTag;
if (effectTag & ShouldCapture) {
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
// Captured a suspense effect. Re-render the boundary.
return workInProgress;
}
return null;
}
case SuspenseListComponent: {
popSuspenseContext(workInProgress);
// SuspenseList doesn't actually catch anything. It should've been
// caught by a nested boundary. If not, it should bubble through.
return null;
}
case HostPortal:
popHostContainer(workInProgress);
return null;
case ContextProvider:
popProvider(workInProgress);
return null;
default:
return null;
}
}
function unwindInterruptedWork(interruptedWork: Fiber) {
switch (interruptedWork.tag) {
case ClassComponent: {
const childContextTypes = interruptedWork.type.childContextTypes;
if (childContextTypes !== null && childContextTypes !== undefined) {
popLegacyContext(interruptedWork);
}
break;
}
case HostRoot: {
popHostContainer(interruptedWork);
popTopLevelLegacyContextObject(interruptedWork);
break;
}
case HostComponent: {
popHostContext(interruptedWork);
break;
}
case HostPortal:
popHostContainer(interruptedWork);
break;
case SuspenseComponent:
popSuspenseContext(interruptedWork);
break;
case SuspenseListComponent:
popSuspenseContext(interruptedWork);
break;
case ContextProvider:
popProvider(interruptedWork);
break;
default:
break;
}
}
export {unwindWork, unwindInterruptedWork};