-
Notifications
You must be signed in to change notification settings - Fork 48.7k
Refactor Lazy Components to use teh Suspense (and wrap Blocks in Lazy) #18362
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 all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
import type {ReactElement} from 'shared/ReactElementType'; | ||
import type {ReactPortal} from 'shared/ReactTypes'; | ||
import type {BlockComponent} from 'react/src/ReactBlock'; | ||
import type {LazyComponent} from 'react/src/ReactLazy'; | ||
import type {Fiber} from './ReactFiber'; | ||
import type {ExpirationTime} from './ReactFiberExpirationTime'; | ||
|
||
|
@@ -20,6 +21,7 @@ import { | |
REACT_ELEMENT_TYPE, | ||
REACT_FRAGMENT_TYPE, | ||
REACT_PORTAL_TYPE, | ||
REACT_LAZY_TYPE, | ||
REACT_BLOCK_TYPE, | ||
} from 'shared/ReactSymbols'; | ||
import { | ||
|
@@ -48,7 +50,6 @@ import { | |
} from './ReactCurrentFiber'; | ||
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading'; | ||
import {StrictMode} from './ReactTypeOfMode'; | ||
import {initializeBlockComponentType} from 'shared/ReactLazyComponent'; | ||
|
||
let didWarnAboutMaps; | ||
let didWarnAboutGenerators; | ||
|
@@ -263,6 +264,22 @@ function warnOnFunctionType() { | |
} | ||
} | ||
|
||
// We avoid inlining this to avoid potential deopts from using try/catch. | ||
/** @noinline */ | ||
function resolveLazyType<T, P>( | ||
lazyComponent: LazyComponent<T, P>, | ||
): LazyComponent<T, P> | T { | ||
try { | ||
// If we can, let's peek at the resulting type. | ||
let payload = lazyComponent._payload; | ||
let init = lazyComponent._init; | ||
return init(payload); | ||
} catch (x) { | ||
// Leave it in place and let it throw again in the begin phase. | ||
return lazyComponent; | ||
} | ||
} | ||
|
||
// This wrapper function exists because I expect to clone the code in each path | ||
// to be able to optimize each path individually by branching early. This needs | ||
// a compiler or we can do it manually. Helpers that don't need this branching | ||
|
@@ -419,22 +436,22 @@ function ChildReconciler(shouldTrackSideEffects) { | |
existing._debugOwner = element._owner; | ||
} | ||
return existing; | ||
} else if ( | ||
enableBlocksAPI && | ||
current.tag === Block && | ||
element.type.$$typeof === REACT_BLOCK_TYPE | ||
) { | ||
} else if (enableBlocksAPI && current.tag === Block) { | ||
// The new Block might not be initialized yet. We need to initialize | ||
// it in case initializing it turns out it would match. | ||
initializeBlockComponentType(element.type); | ||
let type = element.type; | ||
if (type.$$typeof === REACT_LAZY_TYPE) { | ||
type = resolveLazyType(type); | ||
} | ||
if ( | ||
(element.type: BlockComponent<any, any, any>)._fn === | ||
(current.type: BlockComponent<any, any, any>)._fn | ||
type.$$typeof === REACT_BLOCK_TYPE && | ||
((type: any): BlockComponent<any, any>)._render === | ||
(current.type: BlockComponent<any, any>)._render | ||
) { | ||
// Same as above but also update the .type field. | ||
const existing = useFiber(current, element.props); | ||
existing.return = returnFiber; | ||
existing.type = element.type; | ||
existing.type = type; | ||
if (__DEV__) { | ||
existing._debugSource = element._source; | ||
existing._debugOwner = element._owner; | ||
|
@@ -1188,17 +1205,20 @@ function ChildReconciler(shouldTrackSideEffects) { | |
} | ||
case Block: | ||
if (enableBlocksAPI) { | ||
if (element.type.$$typeof === REACT_BLOCK_TYPE) { | ||
let type = element.type; | ||
if (type.$$typeof === REACT_LAZY_TYPE) { | ||
type = resolveLazyType(type); | ||
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. For updates, we attempt to initialize earlier to see if we can reconcile against the inner value. I only do this for Blocks right now to preserve backwards compatibility. However, in non-legacy mode, we could reconcile against the resolved value of any type. E.g. if I have two lazy components that resolve to the same thing, why shouldn't they reconcile against each other? |
||
} | ||
if (type.$$typeof === REACT_BLOCK_TYPE) { | ||
// The new Block might not be initialized yet. We need to initialize | ||
// it in case initializing it turns out it would match. | ||
initializeBlockComponentType(element.type); | ||
if ( | ||
(element.type: BlockComponent<any, any, any>)._fn === | ||
(child.type: BlockComponent<any, any, any>)._fn | ||
((type: any): BlockComponent<any, any>)._render === | ||
(child.type: BlockComponent<any, any>)._render | ||
) { | ||
deleteRemainingChildren(returnFiber, child.sibling); | ||
const existing = useFiber(child, element.props); | ||
existing.type = element.type; | ||
existing.type = type; | ||
existing.return = returnFiber; | ||
if (__DEV__) { | ||
existing._debugSource = element._source; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
|
||
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes'; | ||
import type {BlockComponent} from 'react/src/ReactBlock'; | ||
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy'; | ||
import type {Fiber} from './ReactFiber'; | ||
import type {FiberRoot} from './ReactFiberRoot'; | ||
import type {ExpirationTime} from './ReactFiberExpirationTime'; | ||
|
@@ -73,7 +74,6 @@ import invariant from 'shared/invariant'; | |
import shallowEqual from 'shared/shallowEqual'; | ||
import getComponentName from 'shared/getComponentName'; | ||
import ReactStrictModeWarnings from './ReactStrictModeWarnings'; | ||
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent'; | ||
import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols'; | ||
import { | ||
getCurrentFiberOwnerNameInDevOrNull, | ||
|
@@ -164,11 +164,7 @@ import { | |
resumeMountClassInstance, | ||
updateClassInstance, | ||
} from './ReactFiberClassComponent'; | ||
import { | ||
readLazyComponentType, | ||
resolveDefaultProps, | ||
} from './ReactFiberLazyComponent'; | ||
import {initializeBlockComponentType} from 'shared/ReactLazyComponent'; | ||
import {resolveDefaultProps} from './ReactFiberLazyComponent'; | ||
import { | ||
resolveLazyComponentTag, | ||
createFiberFromTypeAndProps, | ||
|
@@ -184,7 +180,6 @@ import { | |
renderDidSuspendDelayIfPossible, | ||
markUnprocessedUpdateTime, | ||
} from './ReactFiberWorkLoop'; | ||
import {Resolved} from 'shared/ReactLazyStatusTags'; | ||
|
||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; | ||
|
||
|
@@ -492,7 +487,14 @@ function updateSimpleMemoComponent( | |
// We warn when you define propTypes on lazy() | ||
// so let's just skip over it to find memo() outer wrapper. | ||
// Inner props for memo are validated later. | ||
outerMemoType = refineResolvedLazyComponent(outerMemoType); | ||
const lazyComponent: LazyComponentType<any, any> = outerMemoType; | ||
let payload = lazyComponent._payload; | ||
let init = lazyComponent._init; | ||
try { | ||
outerMemoType = init(payload); | ||
} catch (x) { | ||
outerMemoType = null; | ||
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. Why not just let it throw? If we got here, doesn't mean it's resolved anyway? 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. Hm. I think you’re right. I think that we’d only get here if the init is not deterministic. I.e. if it resuspends. That’s a bug in the lazy code but could possibly happen as a bug in a “user provided lazy”. That would be a prod changing behavior so probably best to keep this since it wouldn’t happen in prod (although the call wouldn’t happen neither). |
||
} | ||
} | ||
const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes; | ||
if (outerPropTypes) { | ||
|
@@ -703,23 +705,18 @@ function updateFunctionComponent( | |
return workInProgress.child; | ||
} | ||
|
||
function updateBlock<Props, Payload, Data>( | ||
function updateBlock<Props, Data>( | ||
current: Fiber | null, | ||
workInProgress: Fiber, | ||
block: BlockComponent<Props, Payload, Data>, | ||
block: BlockComponent<Props, Data>, | ||
nextProps: any, | ||
renderExpirationTime: ExpirationTime, | ||
) { | ||
// TODO: current can be non-null here even if the component | ||
// hasn't yet mounted. This happens after the first render suspends. | ||
// We'll need to figure out if this is fine or can cause issues. | ||
|
||
initializeBlockComponentType(block); | ||
if (block._status !== Resolved) { | ||
throw block._data; | ||
} | ||
|
||
const render = block._fn; | ||
const render = block._render; | ||
const data = block._data; | ||
|
||
// The rest is a fork of updateFunctionComponent | ||
|
@@ -1142,7 +1139,10 @@ function mountLazyComponent( | |
// We can't start a User Timing measurement with correct label yet. | ||
// Cancel and resume right after we know the tag. | ||
cancelWorkTimer(workInProgress); | ||
let Component = readLazyComponentType(elementType); | ||
let lazyComponent: LazyComponentType<any, any> = elementType; | ||
let payload = lazyComponent._payload; | ||
let init = lazyComponent._init; | ||
let Component = init(payload); | ||
// Store the unwrapped component in the type. | ||
workInProgress.type = Component; | ||
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component)); | ||
|
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.
This got lost when we dropped Blocks. The benefit of Blocks was that we only needed this check if the tag was Block but now we need to do it for everything.