Remove Reconciler fork (1/2) (#25774)
We've heard from multiple contributors that the Reconciler forking mechanism was confusing and/or annoying to deal with. Since it's currently unused and there's no immediate plans to start using it again, this removes the forking. Fully removing the fork is split into 2 steps to preserve file history: **This PR** - remove `enableNewReconciler` feature flag. - remove `unstable_isNewReconciler` export - remove eslint rules for cross fork imports - remove `*.new.js` files and update imports - merge non-suffixed files into `*.old` files where both exist (sometimes types were defined there) **#25775** - rename `*.old` files
This commit is contained in:
parent
030dae2f4c
commit
420f0b7fa1
|
@ -462,35 +462,6 @@ jobs:
|
|||
cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
scripts/release/publish.js --ci --tags << parameters.dist_tag >>
|
||||
|
||||
# We don't always keep the reconciler forks in sync (otherwise it we wouldn't
|
||||
# have forked it) but during periods when they are meant to be in sync, we
|
||||
# use this job to confirm there are no differences.
|
||||
sync_reconciler_forks:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_node_modules
|
||||
- run:
|
||||
name: Fetch revisions that contain an intentional fork
|
||||
# This will fetch each revision listed in the `forked-revisions` file,
|
||||
# which may be necessary if it's not part of main. For example, it
|
||||
# may have been part of a PR branch that was squashed on merge.
|
||||
command: |
|
||||
cut -d " " -f 1 scripts/merge-fork/forked-revisions | xargs -r git fetch origin
|
||||
- run:
|
||||
name: Revert forked revisions
|
||||
# This will revert the changes without committing. At the end, it's
|
||||
# expected that both forks will be identical.
|
||||
command: |
|
||||
cut -d " " -f 1 scripts/merge-fork/forked-revisions | xargs -r git revert --no-commit
|
||||
- run:
|
||||
name: Confirm reconciler forks are the same
|
||||
command: |
|
||||
yarn replace-fork
|
||||
git diff --quiet || (echo "Reconciler forks are not the same! Run yarn replace-fork. Or, if this was intentional, add the commit SHA to scripts/merge-fork/forked-revisions." && false)
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
|
@ -506,11 +477,6 @@ workflows:
|
|||
- yarn_flow:
|
||||
requires:
|
||||
- setup
|
||||
# NOTE: This job is only enabled when we want the forks to be in sync.
|
||||
# When the forks intentionally diverge, comment out the job to disable it.
|
||||
- sync_reconciler_forks:
|
||||
requires:
|
||||
- setup
|
||||
- check_generated_fizz_runtime:
|
||||
requires:
|
||||
- setup
|
||||
|
|
|
@ -112,14 +112,6 @@ module.exports = {
|
|||
'react-internal/no-to-warn-dev-within-to-throw': ERROR,
|
||||
'react-internal/warning-args': ERROR,
|
||||
'react-internal/no-production-logging': ERROR,
|
||||
'react-internal/no-cross-fork-imports': ERROR,
|
||||
'react-internal/no-cross-fork-types': [
|
||||
ERROR,
|
||||
{
|
||||
old: [],
|
||||
new: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
overrides: [
|
||||
|
|
|
@ -140,8 +140,6 @@
|
|||
"prettier": "node ./scripts/prettier/index.js write-changed",
|
||||
"prettier-all": "node ./scripts/prettier/index.js write",
|
||||
"version-check": "node ./scripts/tasks/version-check.js",
|
||||
"merge-fork": "node ./scripts/merge-fork/merge-fork.js",
|
||||
"replace-fork": "node ./scripts/merge-fork/replace-fork.js",
|
||||
"publish-prereleases": "node ./scripts/release/publish-using-ci-workflow.js",
|
||||
"download-build": "node ./scripts/release/download-experimental-build.js",
|
||||
"download-build-for-head": "node ./scripts/release/download-experimental-build.js --commit=$(git rev-parse HEAD)",
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
createContainer,
|
||||
updateContainer,
|
||||
injectIntoDevTools,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
import FastNoSideEffects from 'art/modes/fast-noSideEffects';
|
||||
|
|
|
@ -10,7 +10,7 @@ import Mode from 'art/modes/current';
|
|||
|
||||
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
|
||||
|
||||
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import {
|
|||
markNodeAsResource,
|
||||
} from './ReactDOMComponentTree';
|
||||
import {HTML_NAMESPACE, SVG_NAMESPACE} from '../shared/DOMNamespaces';
|
||||
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
|
||||
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext.old';
|
||||
|
||||
// The resource types we support. currently they match the form for the as argument.
|
||||
// In the future this may need to change, especially when modules / scripts are supported
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
import type {DOMEventName} from '../events/DOMEventNames';
|
||||
import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {
|
||||
|
@ -81,7 +81,7 @@ import {
|
|||
} from 'react-reconciler/src/ReactWorkTags';
|
||||
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
|
||||
|
||||
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
|
||||
// TODO: Remove this deep import when we delete the legacy root API
|
||||
import {ConcurrentMode, NoMode} from 'react-reconciler/src/ReactTypeOfMode';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
import type {AnyNativeEvent} from '../events/PluginModuleType';
|
||||
import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
|
||||
|
@ -52,7 +52,7 @@ import {
|
|||
IdleEventPriority,
|
||||
getCurrentUpdatePriority,
|
||||
setCurrentUpdatePriority,
|
||||
} from 'react-reconciler/src/ReactEventPriorities';
|
||||
} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
|
|||
import type {DOMEventName} from '../events/DOMEventNames';
|
||||
import type {EventSystemFlags} from './EventSystemFlags';
|
||||
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
|
||||
import {enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
|
@ -35,7 +35,7 @@ import {
|
|||
getClosestInstanceFromNode,
|
||||
} from '../client/ReactDOMComponentTree';
|
||||
import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
|
||||
import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
|
||||
|
||||
let _attemptSynchronousHydration: (fiber: Object) => void;
|
||||
|
|
|
@ -30,7 +30,6 @@ export {
|
|||
unstable_batchedUpdates,
|
||||
unstable_createEventHandle,
|
||||
unstable_flushControlled,
|
||||
unstable_isNewReconciler,
|
||||
unstable_renderSubtreeIntoContainer,
|
||||
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
|
||||
preinit,
|
||||
|
|
|
@ -22,7 +22,6 @@ export {
|
|||
unstable_batchedUpdates,
|
||||
unstable_createEventHandle,
|
||||
unstable_flushControlled,
|
||||
unstable_isNewReconciler,
|
||||
unstable_renderSubtreeIntoContainer,
|
||||
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
|
||||
preinit,
|
||||
|
|
|
@ -16,7 +16,6 @@ export {
|
|||
unstable_batchedUpdates,
|
||||
unstable_createEventHandle,
|
||||
unstable_flushControlled,
|
||||
unstable_isNewReconciler,
|
||||
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
|
||||
preinit,
|
||||
preload,
|
||||
|
|
|
@ -36,7 +36,6 @@ describe('react-dom-server-rendering-stub', () => {
|
|||
expect(ReactDOM.unstable_batchedUpdates).toBe(undefined);
|
||||
expect(ReactDOM.unstable_createEventHandle).toBe(undefined);
|
||||
expect(ReactDOM.unstable_flushControlled).toBe(undefined);
|
||||
expect(ReactDOM.unstable_isNewReconciler).toBe(undefined);
|
||||
expect(ReactDOM.unstable_renderSubtreeIntoContainer).toBe(undefined);
|
||||
expect(ReactDOM.unstable_runWithPriority).toBe(undefined);
|
||||
});
|
||||
|
|
|
@ -43,15 +43,14 @@ import {
|
|||
attemptDiscreteHydration,
|
||||
attemptContinuousHydration,
|
||||
attemptHydrationAtCurrentPriority,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
import {
|
||||
runWithPriority,
|
||||
getCurrentUpdatePriority,
|
||||
} from 'react-reconciler/src/ReactEventPriorities';
|
||||
} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
|
||||
import {canUseDOM} from 'shared/ExecutionEnvironment';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
getClosestInstanceFromNode,
|
||||
|
@ -256,5 +255,3 @@ if (__DEV__) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const unstable_isNewReconciler = enableNewReconciler;
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
getPublicRootInstance,
|
||||
findHostInstance,
|
||||
findHostInstanceWithWarning,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
|
||||
import getComponentNameFromType from 'shared/getComponentNameFromType';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
|
|
|
@ -76,7 +76,7 @@ import {
|
|||
registerMutableSourceForHydration,
|
||||
flushSync,
|
||||
isAlreadyRendering,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
|
||||
|
||||
/* global reportError */
|
||||
|
|
|
@ -19,4 +19,4 @@ export {
|
|||
findBoundingRects,
|
||||
focusWithin,
|
||||
observeVisibleRects,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
|
|
|
@ -19,4 +19,4 @@ export {
|
|||
findBoundingRects,
|
||||
focusWithin,
|
||||
observeVisibleRects,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
|
|
|
@ -19,4 +19,4 @@ export {
|
|||
findBoundingRects,
|
||||
focusWithin,
|
||||
observeVisibleRects,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
|
|
|
@ -19,4 +19,4 @@ export {
|
|||
findBoundingRects,
|
||||
focusWithin,
|
||||
observeVisibleRects,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
updateContainer,
|
||||
injectIntoDevTools,
|
||||
getPublicRootInstance,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
|
||||
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
|
||||
import {setBatchingImplementation} from './legacy-events/ReactGenericBatching';
|
||||
|
|
|
@ -26,7 +26,7 @@ import {dispatchEvent} from './ReactFabricEventEmitter';
|
|||
import {
|
||||
DefaultEventPriority,
|
||||
DiscreteEventPriority,
|
||||
} from 'react-reconciler/src/ReactEventPriorities';
|
||||
} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
|
||||
// Modules provided by RN:
|
||||
import {
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
} from './ReactNativeComponentTree';
|
||||
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
|
||||
|
||||
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
|
||||
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
updateContainer,
|
||||
injectIntoDevTools,
|
||||
getPublicRootInstance,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
// TODO: direct imports like some-package/src/* are bad. Fix me.
|
||||
import {getStackByFiberInDevAndProd} from 'react-reconciler/src/ReactFiberComponentStack';
|
||||
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
|
||||
|
|
|
@ -18,7 +18,7 @@ import type {
|
|||
Fiber,
|
||||
TransitionTracingCallbacks,
|
||||
} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue.new';
|
||||
import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
import type {RootTag} from 'react-reconciler/src/ReactRootTags';
|
||||
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
export * from './src/ReactFiberReconciler';
|
||||
export * from './src/ReactFiberReconciler.old';
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {enableNewReconciler} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
DiscreteEventPriority as DiscreteEventPriority_old,
|
||||
ContinuousEventPriority as ContinuousEventPriority_old,
|
||||
DefaultEventPriority as DefaultEventPriority_old,
|
||||
IdleEventPriority as IdleEventPriority_old,
|
||||
getCurrentUpdatePriority as getCurrentUpdatePriority_old,
|
||||
setCurrentUpdatePriority as setCurrentUpdatePriority_old,
|
||||
runWithPriority as runWithPriority_old,
|
||||
isHigherEventPriority as isHigherEventPriority_old,
|
||||
} from './ReactEventPriorities.old';
|
||||
|
||||
import {
|
||||
DiscreteEventPriority as DiscreteEventPriority_new,
|
||||
ContinuousEventPriority as ContinuousEventPriority_new,
|
||||
DefaultEventPriority as DefaultEventPriority_new,
|
||||
IdleEventPriority as IdleEventPriority_new,
|
||||
getCurrentUpdatePriority as getCurrentUpdatePriority_new,
|
||||
setCurrentUpdatePriority as setCurrentUpdatePriority_new,
|
||||
runWithPriority as runWithPriority_new,
|
||||
isHigherEventPriority as isHigherEventPriority_new,
|
||||
} from './ReactEventPriorities.new';
|
||||
|
||||
export opaque type EventPriority = number;
|
||||
|
||||
export const DiscreteEventPriority: EventPriority = enableNewReconciler
|
||||
? (DiscreteEventPriority_new: any)
|
||||
: (DiscreteEventPriority_old: any);
|
||||
export const ContinuousEventPriority: EventPriority = enableNewReconciler
|
||||
? (ContinuousEventPriority_new: any)
|
||||
: (ContinuousEventPriority_old: any);
|
||||
export const DefaultEventPriority: EventPriority = enableNewReconciler
|
||||
? (DefaultEventPriority_new: any)
|
||||
: (DefaultEventPriority_old: any);
|
||||
export const IdleEventPriority: EventPriority = enableNewReconciler
|
||||
? (IdleEventPriority_new: any)
|
||||
: (IdleEventPriority_old: any);
|
||||
|
||||
export function runWithPriority<T>(priority: EventPriority, fn: () => T): T {
|
||||
return enableNewReconciler
|
||||
? runWithPriority_new((priority: any), fn)
|
||||
: runWithPriority_old((priority: any), fn);
|
||||
}
|
||||
|
||||
export function getCurrentUpdatePriority(): EventPriority {
|
||||
return enableNewReconciler
|
||||
? (getCurrentUpdatePriority_new(): any)
|
||||
: (getCurrentUpdatePriority_old(): any);
|
||||
}
|
||||
|
||||
export function setCurrentUpdatePriority(priority: EventPriority): void {
|
||||
return enableNewReconciler
|
||||
? setCurrentUpdatePriority_new((priority: any))
|
||||
: setCurrentUpdatePriority_old((priority: any));
|
||||
}
|
||||
|
||||
export function isHigherEventPriority(
|
||||
a: EventPriority,
|
||||
b: EventPriority,
|
||||
): boolean {
|
||||
return enableNewReconciler
|
||||
? isHigherEventPriority_new((a: any), (b: any))
|
||||
: isHigherEventPriority_old((a: any), (b: any));
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {Lane, Lanes} from './ReactFiberLane.new';
|
||||
|
||||
import {
|
||||
NoLane,
|
||||
SyncLane,
|
||||
InputContinuousLane,
|
||||
DefaultLane,
|
||||
IdleLane,
|
||||
getHighestPriorityLane,
|
||||
includesNonIdleWork,
|
||||
} from './ReactFiberLane.new';
|
||||
|
||||
export opaque type EventPriority = Lane;
|
||||
|
||||
export const DiscreteEventPriority: EventPriority = SyncLane;
|
||||
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
|
||||
export const DefaultEventPriority: EventPriority = DefaultLane;
|
||||
export const IdleEventPriority: EventPriority = IdleLane;
|
||||
|
||||
let currentUpdatePriority: EventPriority = NoLane;
|
||||
|
||||
export function getCurrentUpdatePriority(): EventPriority {
|
||||
return currentUpdatePriority;
|
||||
}
|
||||
|
||||
export function setCurrentUpdatePriority(newPriority: EventPriority) {
|
||||
currentUpdatePriority = newPriority;
|
||||
}
|
||||
|
||||
export function runWithPriority<T>(priority: EventPriority, fn: () => T): T {
|
||||
const previousPriority = currentUpdatePriority;
|
||||
try {
|
||||
currentUpdatePriority = priority;
|
||||
return fn();
|
||||
} finally {
|
||||
currentUpdatePriority = previousPriority;
|
||||
}
|
||||
}
|
||||
|
||||
export function higherEventPriority(
|
||||
a: EventPriority,
|
||||
b: EventPriority,
|
||||
): EventPriority {
|
||||
return a !== 0 && a < b ? a : b;
|
||||
}
|
||||
|
||||
export function lowerEventPriority(
|
||||
a: EventPriority,
|
||||
b: EventPriority,
|
||||
): EventPriority {
|
||||
return a === 0 || a > b ? a : b;
|
||||
}
|
||||
|
||||
export function isHigherEventPriority(
|
||||
a: EventPriority,
|
||||
b: EventPriority,
|
||||
): boolean {
|
||||
return a !== 0 && a < b;
|
||||
}
|
||||
|
||||
export function lanesToEventPriority(lanes: Lanes): EventPriority {
|
||||
const lane = getHighestPriorityLane(lanes);
|
||||
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
|
||||
return DiscreteEventPriority;
|
||||
}
|
||||
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
|
||||
return ContinuousEventPriority;
|
||||
}
|
||||
if (includesNonIdleWork(lane)) {
|
||||
return DefaultEventPriority;
|
||||
}
|
||||
return IdleEventPriority;
|
||||
}
|
|
@ -1,910 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {ReactElement} from 'shared/ReactElementType';
|
||||
import type {ReactFragment, ReactPortal, ReactScope} from 'shared/ReactTypes';
|
||||
import type {Fiber} from './ReactInternalTypes';
|
||||
import type {RootTag} from './ReactRootTags';
|
||||
import type {WorkTag} from './ReactWorkTags';
|
||||
import type {TypeOfMode} from './ReactTypeOfMode';
|
||||
import type {Lanes} from './ReactFiberLane.new';
|
||||
import type {SuspenseInstance} from './ReactFiberHostConfig';
|
||||
import type {
|
||||
OffscreenProps,
|
||||
OffscreenInstance,
|
||||
} from './ReactFiberOffscreenComponent';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
|
||||
|
||||
import {
|
||||
supportsResources,
|
||||
supportsSingletons,
|
||||
isHostResourceType,
|
||||
isHostSingletonType,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {
|
||||
createRootStrictEffectsByDefault,
|
||||
enableCache,
|
||||
enableProfilerTimer,
|
||||
enableScopeAPI,
|
||||
enableLegacyHidden,
|
||||
enableSyncDefaultUpdates,
|
||||
allowConcurrentByDefault,
|
||||
enableTransitionTracing,
|
||||
enableDebugTracing,
|
||||
enableFloat,
|
||||
enableHostSingletons,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
|
||||
import {ConcurrentRoot} from './ReactRootTags';
|
||||
import {
|
||||
IndeterminateComponent,
|
||||
ClassComponent,
|
||||
HostRoot,
|
||||
HostComponent,
|
||||
HostText,
|
||||
HostPortal,
|
||||
HostResource,
|
||||
HostSingleton,
|
||||
ForwardRef,
|
||||
Fragment,
|
||||
Mode,
|
||||
ContextProvider,
|
||||
ContextConsumer,
|
||||
Profiler,
|
||||
SuspenseComponent,
|
||||
SuspenseListComponent,
|
||||
DehydratedFragment,
|
||||
FunctionComponent,
|
||||
MemoComponent,
|
||||
SimpleMemoComponent,
|
||||
LazyComponent,
|
||||
ScopeComponent,
|
||||
OffscreenComponent,
|
||||
LegacyHiddenComponent,
|
||||
CacheComponent,
|
||||
TracingMarkerComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
|
||||
import {
|
||||
resolveClassForHotReloading,
|
||||
resolveFunctionForHotReloading,
|
||||
resolveForwardRefForHotReloading,
|
||||
} from './ReactFiberHotReloading.new';
|
||||
import {NoLanes} from './ReactFiberLane.new';
|
||||
import {
|
||||
NoMode,
|
||||
ConcurrentMode,
|
||||
DebugTracingMode,
|
||||
ProfileMode,
|
||||
StrictLegacyMode,
|
||||
StrictEffectsMode,
|
||||
ConcurrentUpdatesByDefaultMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
REACT_FRAGMENT_TYPE,
|
||||
REACT_DEBUG_TRACING_MODE_TYPE,
|
||||
REACT_STRICT_MODE_TYPE,
|
||||
REACT_PROFILER_TYPE,
|
||||
REACT_PROVIDER_TYPE,
|
||||
REACT_CONTEXT_TYPE,
|
||||
REACT_SUSPENSE_TYPE,
|
||||
REACT_SUSPENSE_LIST_TYPE,
|
||||
REACT_MEMO_TYPE,
|
||||
REACT_LAZY_TYPE,
|
||||
REACT_SCOPE_TYPE,
|
||||
REACT_OFFSCREEN_TYPE,
|
||||
REACT_LEGACY_HIDDEN_TYPE,
|
||||
REACT_CACHE_TYPE,
|
||||
REACT_TRACING_MARKER_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.new';
|
||||
import {detachOffscreenInstance} from './ReactFiberCommitWork.new';
|
||||
import {getHostContext} from './ReactFiberHostContext.new';
|
||||
|
||||
export type {Fiber};
|
||||
|
||||
let hasBadMapPolyfill;
|
||||
|
||||
if (__DEV__) {
|
||||
hasBadMapPolyfill = false;
|
||||
try {
|
||||
const nonExtensibleObject = Object.preventExtensions({});
|
||||
/* eslint-disable no-new */
|
||||
new Map([[nonExtensibleObject, null]]);
|
||||
new Set([nonExtensibleObject]);
|
||||
/* eslint-enable no-new */
|
||||
} catch (e) {
|
||||
// TODO: Consider warning about bad polyfills
|
||||
hasBadMapPolyfill = true;
|
||||
}
|
||||
}
|
||||
|
||||
function FiberNode(
|
||||
tag: WorkTag,
|
||||
pendingProps: mixed,
|
||||
key: null | string,
|
||||
mode: TypeOfMode,
|
||||
) {
|
||||
// Instance
|
||||
this.tag = tag;
|
||||
this.key = key;
|
||||
this.elementType = null;
|
||||
this.type = null;
|
||||
this.stateNode = null;
|
||||
|
||||
// Fiber
|
||||
this.return = null;
|
||||
this.child = null;
|
||||
this.sibling = null;
|
||||
this.index = 0;
|
||||
|
||||
this.ref = null;
|
||||
this.refCleanup = null;
|
||||
|
||||
this.pendingProps = pendingProps;
|
||||
this.memoizedProps = null;
|
||||
this.updateQueue = null;
|
||||
this.memoizedState = null;
|
||||
this.dependencies = null;
|
||||
|
||||
this.mode = mode;
|
||||
|
||||
// Effects
|
||||
this.flags = NoFlags;
|
||||
this.subtreeFlags = NoFlags;
|
||||
this.deletions = null;
|
||||
|
||||
this.lanes = NoLanes;
|
||||
this.childLanes = NoLanes;
|
||||
|
||||
this.alternate = null;
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Note: The following is done to avoid a v8 performance cliff.
|
||||
//
|
||||
// Initializing the fields below to smis and later updating them with
|
||||
// double values will cause Fibers to end up having separate shapes.
|
||||
// This behavior/bug has something to do with Object.preventExtension().
|
||||
// Fortunately this only impacts DEV builds.
|
||||
// Unfortunately it makes React unusably slow for some applications.
|
||||
// To work around this, initialize the fields below with doubles.
|
||||
//
|
||||
// Learn more about this here:
|
||||
// https://github.com/facebook/react/issues/14365
|
||||
// https://bugs.chromium.org/p/v8/issues/detail?id=8538
|
||||
this.actualDuration = Number.NaN;
|
||||
this.actualStartTime = Number.NaN;
|
||||
this.selfBaseDuration = Number.NaN;
|
||||
this.treeBaseDuration = Number.NaN;
|
||||
|
||||
// It's okay to replace the initial doubles with smis after initialization.
|
||||
// This won't trigger the performance cliff mentioned above,
|
||||
// and it simplifies other profiler code (including DevTools).
|
||||
this.actualDuration = 0;
|
||||
this.actualStartTime = -1;
|
||||
this.selfBaseDuration = 0;
|
||||
this.treeBaseDuration = 0;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
// This isn't directly used but is handy for debugging internals:
|
||||
|
||||
this._debugSource = null;
|
||||
this._debugOwner = null;
|
||||
this._debugNeedsRemount = false;
|
||||
this._debugHookTypes = null;
|
||||
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
|
||||
Object.preventExtensions(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a constructor function, rather than a POJO constructor, still
|
||||
// please ensure we do the following:
|
||||
// 1) Nobody should add any instance methods on this. Instance methods can be
|
||||
// more difficult to predict when they get optimized and they are almost
|
||||
// never inlined properly in static compilers.
|
||||
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
|
||||
// always know when it is a fiber.
|
||||
// 3) We might want to experiment with using numeric keys since they are easier
|
||||
// to optimize in a non-JIT environment.
|
||||
// 4) We can easily go from a constructor to a createFiber object literal if that
|
||||
// is faster.
|
||||
// 5) It should be easy to port this to a C struct and keep a C implementation
|
||||
// compatible.
|
||||
const createFiber = function(
|
||||
tag: WorkTag,
|
||||
pendingProps: mixed,
|
||||
key: null | string,
|
||||
mode: TypeOfMode,
|
||||
): Fiber {
|
||||
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
|
||||
return new FiberNode(tag, pendingProps, key, mode);
|
||||
};
|
||||
|
||||
function shouldConstruct(Component: Function) {
|
||||
const prototype = Component.prototype;
|
||||
return !!(prototype && prototype.isReactComponent);
|
||||
}
|
||||
|
||||
export function isSimpleFunctionComponent(type: any): boolean {
|
||||
return (
|
||||
typeof type === 'function' &&
|
||||
!shouldConstruct(type) &&
|
||||
type.defaultProps === undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveLazyComponentTag(Component: Function): WorkTag {
|
||||
if (typeof Component === 'function') {
|
||||
return shouldConstruct(Component) ? ClassComponent : FunctionComponent;
|
||||
} else if (Component !== undefined && Component !== null) {
|
||||
const $$typeof = Component.$$typeof;
|
||||
if ($$typeof === REACT_FORWARD_REF_TYPE) {
|
||||
return ForwardRef;
|
||||
}
|
||||
if ($$typeof === REACT_MEMO_TYPE) {
|
||||
return MemoComponent;
|
||||
}
|
||||
}
|
||||
return IndeterminateComponent;
|
||||
}
|
||||
|
||||
// This is used to create an alternate fiber to do work on.
|
||||
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
|
||||
let workInProgress = current.alternate;
|
||||
if (workInProgress === null) {
|
||||
// We use a double buffering pooling technique because we know that we'll
|
||||
// only ever need at most two versions of a tree. We pool the "other" unused
|
||||
// node that we're free to reuse. This is lazily created to avoid allocating
|
||||
// extra objects for things that are never updated. It also allow us to
|
||||
// reclaim the extra memory if needed.
|
||||
workInProgress = createFiber(
|
||||
current.tag,
|
||||
pendingProps,
|
||||
current.key,
|
||||
current.mode,
|
||||
);
|
||||
workInProgress.elementType = current.elementType;
|
||||
workInProgress.type = current.type;
|
||||
workInProgress.stateNode = current.stateNode;
|
||||
|
||||
if (__DEV__) {
|
||||
// DEV-only fields
|
||||
|
||||
workInProgress._debugSource = current._debugSource;
|
||||
workInProgress._debugOwner = current._debugOwner;
|
||||
workInProgress._debugHookTypes = current._debugHookTypes;
|
||||
}
|
||||
|
||||
workInProgress.alternate = current;
|
||||
current.alternate = workInProgress;
|
||||
} else {
|
||||
workInProgress.pendingProps = pendingProps;
|
||||
// Needed because Blocks store data on type.
|
||||
workInProgress.type = current.type;
|
||||
|
||||
// We already have an alternate.
|
||||
// Reset the effect tag.
|
||||
workInProgress.flags = NoFlags;
|
||||
|
||||
// The effects are no longer valid.
|
||||
workInProgress.subtreeFlags = NoFlags;
|
||||
workInProgress.deletions = null;
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
|
||||
// This prevents time from endlessly accumulating in new commits.
|
||||
// This has the downside of resetting values for different priority renders,
|
||||
// But works for yielding (the common case) and should support resuming.
|
||||
workInProgress.actualDuration = 0;
|
||||
workInProgress.actualStartTime = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset all effects except static ones.
|
||||
// Static effects are not specific to a render.
|
||||
workInProgress.flags = current.flags & StaticMask;
|
||||
workInProgress.childLanes = current.childLanes;
|
||||
workInProgress.lanes = current.lanes;
|
||||
|
||||
workInProgress.child = current.child;
|
||||
workInProgress.memoizedProps = current.memoizedProps;
|
||||
workInProgress.memoizedState = current.memoizedState;
|
||||
workInProgress.updateQueue = current.updateQueue;
|
||||
|
||||
// Clone the dependencies object. This is mutated during the render phase, so
|
||||
// it cannot be shared with the current fiber.
|
||||
const currentDependencies = current.dependencies;
|
||||
workInProgress.dependencies =
|
||||
currentDependencies === null
|
||||
? null
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
|
||||
// These will be overridden during the parent's reconciliation
|
||||
workInProgress.sibling = current.sibling;
|
||||
workInProgress.index = current.index;
|
||||
workInProgress.ref = current.ref;
|
||||
workInProgress.refCleanup = current.refCleanup;
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
workInProgress.selfBaseDuration = current.selfBaseDuration;
|
||||
workInProgress.treeBaseDuration = current.treeBaseDuration;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
workInProgress._debugNeedsRemount = current._debugNeedsRemount;
|
||||
switch (workInProgress.tag) {
|
||||
case IndeterminateComponent:
|
||||
case FunctionComponent:
|
||||
case SimpleMemoComponent:
|
||||
workInProgress.type = resolveFunctionForHotReloading(current.type);
|
||||
break;
|
||||
case ClassComponent:
|
||||
workInProgress.type = resolveClassForHotReloading(current.type);
|
||||
break;
|
||||
case ForwardRef:
|
||||
workInProgress.type = resolveForwardRefForHotReloading(current.type);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return workInProgress;
|
||||
}
|
||||
|
||||
// Used to reuse a Fiber for a second pass.
|
||||
export function resetWorkInProgress(
|
||||
workInProgress: Fiber,
|
||||
renderLanes: Lanes,
|
||||
): Fiber {
|
||||
// This resets the Fiber to what createFiber or createWorkInProgress would
|
||||
// have set the values to before during the first pass. Ideally this wouldn't
|
||||
// be necessary but unfortunately many code paths reads from the workInProgress
|
||||
// when they should be reading from current and writing to workInProgress.
|
||||
|
||||
// We assume pendingProps, index, key, ref, return are still untouched to
|
||||
// avoid doing another reconciliation.
|
||||
|
||||
// Reset the effect flags but keep any Placement tags, since that's something
|
||||
// that child fiber is setting, not the reconciliation.
|
||||
workInProgress.flags &= StaticMask | Placement;
|
||||
|
||||
// The effects are no longer valid.
|
||||
|
||||
const current = workInProgress.alternate;
|
||||
if (current === null) {
|
||||
// Reset to createFiber's initial values.
|
||||
workInProgress.childLanes = NoLanes;
|
||||
workInProgress.lanes = renderLanes;
|
||||
|
||||
workInProgress.child = null;
|
||||
workInProgress.subtreeFlags = NoFlags;
|
||||
workInProgress.memoizedProps = null;
|
||||
workInProgress.memoizedState = null;
|
||||
workInProgress.updateQueue = null;
|
||||
|
||||
workInProgress.dependencies = null;
|
||||
|
||||
workInProgress.stateNode = null;
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Note: We don't reset the actualTime counts. It's useful to accumulate
|
||||
// actual time across multiple render passes.
|
||||
workInProgress.selfBaseDuration = 0;
|
||||
workInProgress.treeBaseDuration = 0;
|
||||
}
|
||||
} else {
|
||||
// Reset to the cloned values that createWorkInProgress would've.
|
||||
workInProgress.childLanes = current.childLanes;
|
||||
workInProgress.lanes = current.lanes;
|
||||
|
||||
workInProgress.child = current.child;
|
||||
workInProgress.subtreeFlags = NoFlags;
|
||||
workInProgress.deletions = null;
|
||||
workInProgress.memoizedProps = current.memoizedProps;
|
||||
workInProgress.memoizedState = current.memoizedState;
|
||||
workInProgress.updateQueue = current.updateQueue;
|
||||
// Needed because Blocks store data on type.
|
||||
workInProgress.type = current.type;
|
||||
|
||||
// Clone the dependencies object. This is mutated during the render phase, so
|
||||
// it cannot be shared with the current fiber.
|
||||
const currentDependencies = current.dependencies;
|
||||
workInProgress.dependencies =
|
||||
currentDependencies === null
|
||||
? null
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Note: We don't reset the actualTime counts. It's useful to accumulate
|
||||
// actual time across multiple render passes.
|
||||
workInProgress.selfBaseDuration = current.selfBaseDuration;
|
||||
workInProgress.treeBaseDuration = current.treeBaseDuration;
|
||||
}
|
||||
}
|
||||
|
||||
return workInProgress;
|
||||
}
|
||||
|
||||
export function createHostRootFiber(
|
||||
tag: RootTag,
|
||||
isStrictMode: boolean,
|
||||
concurrentUpdatesByDefaultOverride: null | boolean,
|
||||
): Fiber {
|
||||
let mode;
|
||||
if (tag === ConcurrentRoot) {
|
||||
mode = ConcurrentMode;
|
||||
if (isStrictMode === true || createRootStrictEffectsByDefault) {
|
||||
mode |= StrictLegacyMode | StrictEffectsMode;
|
||||
}
|
||||
if (
|
||||
// We only use this flag for our repo tests to check both behaviors.
|
||||
// TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
|
||||
!enableSyncDefaultUpdates ||
|
||||
// Only for internal experiments.
|
||||
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
|
||||
) {
|
||||
mode |= ConcurrentUpdatesByDefaultMode;
|
||||
}
|
||||
} else {
|
||||
mode = NoMode;
|
||||
}
|
||||
|
||||
if (enableProfilerTimer && isDevToolsPresent) {
|
||||
// Always collect profile timings when DevTools are present.
|
||||
// This enables DevTools to start capturing timing at any point–
|
||||
// Without some nodes in the tree having empty base times.
|
||||
mode |= ProfileMode;
|
||||
}
|
||||
|
||||
return createFiber(HostRoot, null, null, mode);
|
||||
}
|
||||
|
||||
export function createFiberFromTypeAndProps(
|
||||
type: any, // React$ElementType
|
||||
key: null | string,
|
||||
pendingProps: any,
|
||||
owner: null | Fiber,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
): Fiber {
|
||||
let fiberTag = IndeterminateComponent;
|
||||
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
|
||||
let resolvedType = type;
|
||||
if (typeof type === 'function') {
|
||||
if (shouldConstruct(type)) {
|
||||
fiberTag = ClassComponent;
|
||||
if (__DEV__) {
|
||||
resolvedType = resolveClassForHotReloading(resolvedType);
|
||||
}
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
resolvedType = resolveFunctionForHotReloading(resolvedType);
|
||||
}
|
||||
}
|
||||
} else if (typeof type === 'string') {
|
||||
if (
|
||||
enableFloat &&
|
||||
supportsResources &&
|
||||
enableHostSingletons &&
|
||||
supportsSingletons
|
||||
) {
|
||||
const hostContext = getHostContext();
|
||||
fiberTag = isHostResourceType(type, pendingProps, hostContext)
|
||||
? HostResource
|
||||
: isHostSingletonType(type)
|
||||
? HostSingleton
|
||||
: HostComponent;
|
||||
} else if (enableFloat && supportsResources) {
|
||||
const hostContext = getHostContext();
|
||||
fiberTag = isHostResourceType(type, pendingProps, hostContext)
|
||||
? HostResource
|
||||
: HostComponent;
|
||||
} else if (enableHostSingletons && supportsSingletons) {
|
||||
fiberTag = isHostSingletonType(type) ? HostSingleton : HostComponent;
|
||||
} else {
|
||||
fiberTag = HostComponent;
|
||||
}
|
||||
} else {
|
||||
getTag: switch (type) {
|
||||
case REACT_FRAGMENT_TYPE:
|
||||
return createFiberFromFragment(pendingProps.children, mode, lanes, key);
|
||||
case REACT_STRICT_MODE_TYPE:
|
||||
fiberTag = Mode;
|
||||
mode |= StrictLegacyMode;
|
||||
if ((mode & ConcurrentMode) !== NoMode) {
|
||||
// Strict effects should never run on legacy roots
|
||||
mode |= StrictEffectsMode;
|
||||
}
|
||||
break;
|
||||
case REACT_PROFILER_TYPE:
|
||||
return createFiberFromProfiler(pendingProps, mode, lanes, key);
|
||||
case REACT_SUSPENSE_TYPE:
|
||||
return createFiberFromSuspense(pendingProps, mode, lanes, key);
|
||||
case REACT_SUSPENSE_LIST_TYPE:
|
||||
return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
|
||||
case REACT_OFFSCREEN_TYPE:
|
||||
return createFiberFromOffscreen(pendingProps, mode, lanes, key);
|
||||
case REACT_LEGACY_HIDDEN_TYPE:
|
||||
if (enableLegacyHidden) {
|
||||
return createFiberFromLegacyHidden(pendingProps, mode, lanes, key);
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case REACT_SCOPE_TYPE:
|
||||
if (enableScopeAPI) {
|
||||
return createFiberFromScope(type, pendingProps, mode, lanes, key);
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case REACT_CACHE_TYPE:
|
||||
if (enableCache) {
|
||||
return createFiberFromCache(pendingProps, mode, lanes, key);
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case REACT_TRACING_MARKER_TYPE:
|
||||
if (enableTransitionTracing) {
|
||||
return createFiberFromTracingMarker(pendingProps, mode, lanes, key);
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case REACT_DEBUG_TRACING_MODE_TYPE:
|
||||
if (enableDebugTracing) {
|
||||
fiberTag = Mode;
|
||||
mode |= DebugTracingMode;
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
default: {
|
||||
if (typeof type === 'object' && type !== null) {
|
||||
switch (type.$$typeof) {
|
||||
case REACT_PROVIDER_TYPE:
|
||||
fiberTag = ContextProvider;
|
||||
break getTag;
|
||||
case REACT_CONTEXT_TYPE:
|
||||
// This is a consumer
|
||||
fiberTag = ContextConsumer;
|
||||
break getTag;
|
||||
case REACT_FORWARD_REF_TYPE:
|
||||
fiberTag = ForwardRef;
|
||||
if (__DEV__) {
|
||||
resolvedType = resolveForwardRefForHotReloading(resolvedType);
|
||||
}
|
||||
break getTag;
|
||||
case REACT_MEMO_TYPE:
|
||||
fiberTag = MemoComponent;
|
||||
break getTag;
|
||||
case REACT_LAZY_TYPE:
|
||||
fiberTag = LazyComponent;
|
||||
resolvedType = null;
|
||||
break getTag;
|
||||
}
|
||||
}
|
||||
let info = '';
|
||||
if (__DEV__) {
|
||||
if (
|
||||
type === undefined ||
|
||||
(typeof type === 'object' &&
|
||||
type !== null &&
|
||||
Object.keys(type).length === 0)
|
||||
) {
|
||||
info +=
|
||||
' You likely forgot to export your component from the file ' +
|
||||
"it's defined in, or you might have mixed up default and " +
|
||||
'named imports.';
|
||||
}
|
||||
const ownerName = owner ? getComponentNameFromFiber(owner) : null;
|
||||
if (ownerName) {
|
||||
info += '\n\nCheck the render method of `' + ownerName + '`.';
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Element type is invalid: expected a string (for built-in ' +
|
||||
'components) or a class/function (for composite components) ' +
|
||||
`but got: ${type == null ? type : typeof type}.${info}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fiber = createFiber(fiberTag, pendingProps, key, mode);
|
||||
fiber.elementType = type;
|
||||
fiber.type = resolvedType;
|
||||
fiber.lanes = lanes;
|
||||
|
||||
if (__DEV__) {
|
||||
fiber._debugOwner = owner;
|
||||
}
|
||||
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromElement(
|
||||
element: ReactElement,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
): Fiber {
|
||||
let owner = null;
|
||||
if (__DEV__) {
|
||||
owner = element._owner;
|
||||
}
|
||||
const type = element.type;
|
||||
const key = element.key;
|
||||
const pendingProps = element.props;
|
||||
const fiber = createFiberFromTypeAndProps(
|
||||
type,
|
||||
key,
|
||||
pendingProps,
|
||||
owner,
|
||||
mode,
|
||||
lanes,
|
||||
);
|
||||
if (__DEV__) {
|
||||
fiber._debugSource = element._source;
|
||||
fiber._debugOwner = element._owner;
|
||||
}
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromFragment(
|
||||
elements: ReactFragment,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
): Fiber {
|
||||
const fiber = createFiber(Fragment, elements, key, mode);
|
||||
fiber.lanes = lanes;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
function createFiberFromScope(
|
||||
scope: ReactScope,
|
||||
pendingProps: any,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
) {
|
||||
const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
|
||||
fiber.type = scope;
|
||||
fiber.elementType = scope;
|
||||
fiber.lanes = lanes;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
function createFiberFromProfiler(
|
||||
pendingProps: any,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
): Fiber {
|
||||
if (__DEV__) {
|
||||
if (typeof pendingProps.id !== 'string') {
|
||||
console.error(
|
||||
'Profiler must specify an "id" of type `string` as a prop. Received the type `%s` instead.',
|
||||
typeof pendingProps.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
|
||||
fiber.elementType = REACT_PROFILER_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
fiber.stateNode = {
|
||||
effectDuration: 0,
|
||||
passiveEffectDuration: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromSuspense(
|
||||
pendingProps: any,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
): Fiber {
|
||||
const fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
|
||||
fiber.elementType = REACT_SUSPENSE_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromSuspenseList(
|
||||
pendingProps: any,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
): Fiber {
|
||||
const fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
|
||||
fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromOffscreen(
|
||||
pendingProps: OffscreenProps,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
): Fiber {
|
||||
const fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
|
||||
fiber.elementType = REACT_OFFSCREEN_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
const primaryChildInstance: OffscreenInstance = {
|
||||
_visibility: OffscreenVisible,
|
||||
_pendingMarkers: null,
|
||||
_retryCache: null,
|
||||
_transitions: null,
|
||||
_current: null,
|
||||
detach: () => detachOffscreenInstance(primaryChildInstance),
|
||||
};
|
||||
fiber.stateNode = primaryChildInstance;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromLegacyHidden(
|
||||
pendingProps: OffscreenProps,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
): Fiber {
|
||||
const fiber = createFiber(LegacyHiddenComponent, pendingProps, key, mode);
|
||||
fiber.elementType = REACT_LEGACY_HIDDEN_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
// Adding a stateNode for legacy hidden because it's currently using
|
||||
// the offscreen implementation, which depends on a state node
|
||||
const instance: OffscreenInstance = {
|
||||
_visibility: OffscreenVisible,
|
||||
_pendingMarkers: null,
|
||||
_transitions: null,
|
||||
_retryCache: null,
|
||||
_current: null,
|
||||
detach: () => detachOffscreenInstance(instance),
|
||||
};
|
||||
fiber.stateNode = instance;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromCache(
|
||||
pendingProps: any,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
): Fiber {
|
||||
const fiber = createFiber(CacheComponent, pendingProps, key, mode);
|
||||
fiber.elementType = REACT_CACHE_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromTracingMarker(
|
||||
pendingProps: any,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
): Fiber {
|
||||
const fiber = createFiber(TracingMarkerComponent, pendingProps, key, mode);
|
||||
fiber.elementType = REACT_TRACING_MARKER_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
const tracingMarkerInstance: TracingMarkerInstance = {
|
||||
tag: TransitionTracingMarker,
|
||||
transitions: null,
|
||||
pendingBoundaries: null,
|
||||
aborts: null,
|
||||
name: pendingProps.name,
|
||||
};
|
||||
fiber.stateNode = tracingMarkerInstance;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromText(
|
||||
content: string,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
): Fiber {
|
||||
const fiber = createFiber(HostText, content, null, mode);
|
||||
fiber.lanes = lanes;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromHostInstanceForDeletion(): Fiber {
|
||||
const fiber = createFiber(HostComponent, null, null, NoMode);
|
||||
fiber.elementType = 'DELETED';
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromDehydratedFragment(
|
||||
dehydratedNode: SuspenseInstance,
|
||||
): Fiber {
|
||||
const fiber = createFiber(DehydratedFragment, null, null, NoMode);
|
||||
fiber.stateNode = dehydratedNode;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromPortal(
|
||||
portal: ReactPortal,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
): Fiber {
|
||||
const pendingProps = portal.children !== null ? portal.children : [];
|
||||
const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
|
||||
fiber.lanes = lanes;
|
||||
fiber.stateNode = {
|
||||
containerInfo: portal.containerInfo,
|
||||
pendingChildren: null, // Used by persistent updates
|
||||
implementation: portal.implementation,
|
||||
};
|
||||
return fiber;
|
||||
}
|
||||
|
||||
// Used for stashing WIP properties to replay failed work in DEV.
|
||||
export function assignFiberPropertiesInDEV(
|
||||
target: Fiber | null,
|
||||
source: Fiber,
|
||||
): Fiber {
|
||||
if (target === null) {
|
||||
// This Fiber's initial properties will always be overwritten.
|
||||
// We only use a Fiber to ensure the same hidden class so DEV isn't slow.
|
||||
target = createFiber(IndeterminateComponent, null, null, NoMode);
|
||||
}
|
||||
|
||||
// This is intentionally written as a list of all properties.
|
||||
// We tried to use Object.assign() instead but this is called in
|
||||
// the hottest path, and Object.assign() was too slow:
|
||||
// https://github.com/facebook/react/issues/12502
|
||||
// This code is DEV-only so size is not a concern.
|
||||
|
||||
target.tag = source.tag;
|
||||
target.key = source.key;
|
||||
target.elementType = source.elementType;
|
||||
target.type = source.type;
|
||||
target.stateNode = source.stateNode;
|
||||
target.return = source.return;
|
||||
target.child = source.child;
|
||||
target.sibling = source.sibling;
|
||||
target.index = source.index;
|
||||
target.ref = source.ref;
|
||||
target.refCleanup = source.refCleanup;
|
||||
target.pendingProps = source.pendingProps;
|
||||
target.memoizedProps = source.memoizedProps;
|
||||
target.updateQueue = source.updateQueue;
|
||||
target.memoizedState = source.memoizedState;
|
||||
target.dependencies = source.dependencies;
|
||||
target.mode = source.mode;
|
||||
target.flags = source.flags;
|
||||
target.subtreeFlags = source.subtreeFlags;
|
||||
target.deletions = source.deletions;
|
||||
target.lanes = source.lanes;
|
||||
target.childLanes = source.childLanes;
|
||||
target.alternate = source.alternate;
|
||||
if (enableProfilerTimer) {
|
||||
target.actualDuration = source.actualDuration;
|
||||
target.actualStartTime = source.actualStartTime;
|
||||
target.selfBaseDuration = source.selfBaseDuration;
|
||||
target.treeBaseDuration = source.treeBaseDuration;
|
||||
}
|
||||
|
||||
target._debugSource = source._debugSource;
|
||||
target._debugOwner = source._debugOwner;
|
||||
target._debugNeedsRemount = source._debugNeedsRemount;
|
||||
target._debugHookTypes = source._debugHookTypes;
|
||||
return target;
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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.new';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
|
||||
import {warnsIfNotActing} from './ReactFiberHostConfig';
|
||||
|
||||
const {ReactCurrentActQueue} = ReactSharedInternals;
|
||||
|
||||
export function isLegacyActEnvironment(fiber: Fiber): boolean {
|
||||
if (__DEV__) {
|
||||
// Legacy mode. We preserve the behavior of React 17's act. It assumes an
|
||||
// act environment whenever `jest` is defined, but you can still turn off
|
||||
// spurious warnings by setting IS_REACT_ACT_ENVIRONMENT explicitly
|
||||
// to false.
|
||||
|
||||
const isReactActEnvironmentGlobal =
|
||||
// $FlowFixMe – Flow doesn't know about IS_REACT_ACT_ENVIRONMENT global
|
||||
typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined'
|
||||
? IS_REACT_ACT_ENVIRONMENT
|
||||
: undefined;
|
||||
|
||||
// $FlowFixMe - Flow doesn't know about jest
|
||||
const jestIsDefined = typeof jest !== 'undefined';
|
||||
return (
|
||||
warnsIfNotActing && jestIsDefined && isReactActEnvironmentGlobal !== false
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isConcurrentActEnvironment(): void | boolean {
|
||||
if (__DEV__) {
|
||||
const isReactActEnvironmentGlobal =
|
||||
typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined'
|
||||
? IS_REACT_ACT_ENVIRONMENT
|
||||
: undefined;
|
||||
|
||||
if (!isReactActEnvironmentGlobal && ReactCurrentActQueue.current !== null) {
|
||||
// TODO: Include link to relevant documentation page.
|
||||
console.error(
|
||||
'The current testing environment is not configured to support ' +
|
||||
'act(...)',
|
||||
);
|
||||
}
|
||||
return isReactActEnvironmentGlobal;
|
||||
}
|
||||
return false;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -170,7 +170,7 @@ import {
|
|||
getResource,
|
||||
} from './ReactFiberHostConfig';
|
||||
import type {SuspenseInstance} from './ReactFiberHostConfig';
|
||||
import {shouldError, shouldSuspend} from './ReactFiberReconciler';
|
||||
import {shouldError, shouldSuspend} from './ReactFiberReconciler.old';
|
||||
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.old';
|
||||
import {
|
||||
suspenseStackCursor,
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {CacheDispatcher} from './ReactInternalTypes';
|
||||
import type {Cache} from './ReactFiberCacheComponent.new';
|
||||
|
||||
import {enableCache} from 'shared/ReactFeatureFlags';
|
||||
import {readContext} from './ReactFiberNewContext.new';
|
||||
import {CacheContext} from './ReactFiberCacheComponent.new';
|
||||
|
||||
function getCacheSignal(): AbortSignal {
|
||||
if (!enableCache) {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
const cache: Cache = readContext(CacheContext);
|
||||
return cache.controller.signal;
|
||||
}
|
||||
|
||||
function getCacheForType<T>(resourceType: () => T): T {
|
||||
if (!enableCache) {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
const cache: Cache = readContext(CacheContext);
|
||||
let cacheForType: T | void = (cache.data.get(resourceType): any);
|
||||
if (cacheForType === undefined) {
|
||||
cacheForType = resourceType();
|
||||
cache.data.set(resourceType, cacheForType);
|
||||
}
|
||||
return cacheForType;
|
||||
}
|
||||
|
||||
export const DefaultCacheDispatcher: CacheDispatcher = {
|
||||
getCacheSignal,
|
||||
getCacheForType,
|
||||
};
|
|
@ -1,147 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {ReactContext} from 'shared/ReactTypes';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
|
||||
import {enableCache} from 'shared/ReactFeatureFlags';
|
||||
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import {pushProvider, popProvider} from './ReactFiberNewContext.new';
|
||||
import * as Scheduler from 'scheduler';
|
||||
|
||||
// In environments without AbortController (e.g. tests)
|
||||
// replace it with a lightweight shim that only has the features we use.
|
||||
const AbortControllerLocal: typeof AbortController = enableCache
|
||||
? typeof AbortController !== 'undefined'
|
||||
? AbortController
|
||||
: (function AbortControllerShim() {
|
||||
const listeners = [];
|
||||
const signal = (this.signal = {
|
||||
aborted: false,
|
||||
addEventListener: (type, listener) => {
|
||||
listeners.push(listener);
|
||||
},
|
||||
});
|
||||
|
||||
this.abort = () => {
|
||||
signal.aborted = true;
|
||||
listeners.forEach(listener => listener());
|
||||
};
|
||||
}: AbortController)
|
||||
: (null: any);
|
||||
|
||||
export type Cache = {
|
||||
controller: AbortControllerLocal,
|
||||
data: Map<() => mixed, mixed>,
|
||||
refCount: number,
|
||||
};
|
||||
|
||||
export type CacheComponentState = {
|
||||
+parent: Cache,
|
||||
+cache: Cache,
|
||||
};
|
||||
|
||||
export type SpawnedCachePool = {
|
||||
+parent: Cache,
|
||||
+pool: Cache,
|
||||
};
|
||||
|
||||
// Intentionally not named imports because Rollup would
|
||||
// use dynamic dispatch for CommonJS interop named imports.
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
|
||||
export const CacheContext: ReactContext<Cache> = enableCache
|
||||
? {
|
||||
$$typeof: REACT_CONTEXT_TYPE,
|
||||
// We don't use Consumer/Provider for Cache components. So we'll cheat.
|
||||
Consumer: (null: any),
|
||||
Provider: (null: any),
|
||||
// We'll initialize these at the root.
|
||||
_currentValue: (null: any),
|
||||
_currentValue2: (null: any),
|
||||
_threadCount: 0,
|
||||
_defaultValue: (null: any),
|
||||
_globalName: (null: any),
|
||||
}
|
||||
: (null: any);
|
||||
|
||||
if (__DEV__ && enableCache) {
|
||||
CacheContext._currentRenderer = null;
|
||||
CacheContext._currentRenderer2 = null;
|
||||
}
|
||||
|
||||
// Creates a new empty Cache instance with a ref-count of 0. The caller is responsible
|
||||
// for retaining the cache once it is in use (retainCache), and releasing the cache
|
||||
// once it is no longer needed (releaseCache).
|
||||
export function createCache(): Cache {
|
||||
if (!enableCache) {
|
||||
return (null: any);
|
||||
}
|
||||
const cache: Cache = {
|
||||
controller: new AbortControllerLocal(),
|
||||
data: new Map(),
|
||||
refCount: 0,
|
||||
};
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
export function retainCache(cache: Cache) {
|
||||
if (!enableCache) {
|
||||
return;
|
||||
}
|
||||
if (__DEV__) {
|
||||
if (cache.controller.signal.aborted) {
|
||||
console.warn(
|
||||
'A cache instance was retained after it was already freed. ' +
|
||||
'This likely indicates a bug in React.',
|
||||
);
|
||||
}
|
||||
}
|
||||
cache.refCount++;
|
||||
}
|
||||
|
||||
// Cleanup a cache instance, potentially freeing it if there are no more references
|
||||
export function releaseCache(cache: Cache) {
|
||||
if (!enableCache) {
|
||||
return;
|
||||
}
|
||||
cache.refCount--;
|
||||
if (__DEV__) {
|
||||
if (cache.refCount < 0) {
|
||||
console.warn(
|
||||
'A cache instance was released after it was already freed. ' +
|
||||
'This likely indicates a bug in React.',
|
||||
);
|
||||
}
|
||||
}
|
||||
if (cache.refCount === 0) {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
cache.controller.abort();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function pushCacheProvider(workInProgress: Fiber, cache: Cache) {
|
||||
if (!enableCache) {
|
||||
return;
|
||||
}
|
||||
pushProvider(workInProgress, CacheContext, cache);
|
||||
}
|
||||
|
||||
export function popCacheProvider(workInProgress: Fiber, cache: Cache) {
|
||||
if (!enableCache) {
|
||||
return;
|
||||
}
|
||||
popProvider(CacheContext, workInProgress);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,742 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// UpdateQueue is a linked list of prioritized updates.
|
||||
//
|
||||
// Like fibers, update queues come in pairs: a current queue, which represents
|
||||
// the visible state of the screen, and a work-in-progress queue, which can be
|
||||
// mutated and processed asynchronously before it is committed — a form of
|
||||
// double buffering. If a work-in-progress render is discarded before finishing,
|
||||
// we create a new work-in-progress by cloning the current queue.
|
||||
//
|
||||
// Both queues share a persistent, singly-linked list structure. To schedule an
|
||||
// update, we append it to the end of both queues. Each queue maintains a
|
||||
// pointer to first update in the persistent list that hasn't been processed.
|
||||
// The work-in-progress pointer always has a position equal to or greater than
|
||||
// the current queue, since we always work on that one. The current queue's
|
||||
// pointer is only updated during the commit phase, when we swap in the
|
||||
// work-in-progress.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// Current pointer: A - B - C - D - E - F
|
||||
// Work-in-progress pointer: D - E - F
|
||||
// ^
|
||||
// The work-in-progress queue has
|
||||
// processed more updates than current.
|
||||
//
|
||||
// The reason we append to both queues is because otherwise we might drop
|
||||
// updates without ever processing them. For example, if we only add updates to
|
||||
// the work-in-progress queue, some updates could be lost whenever a work-in
|
||||
// -progress render restarts by cloning from current. Similarly, if we only add
|
||||
// updates to the current queue, the updates will be lost whenever an already
|
||||
// in-progress queue commits and swaps with the current queue. However, by
|
||||
// adding to both queues, we guarantee that the update will be part of the next
|
||||
// work-in-progress. (And because the work-in-progress queue becomes the
|
||||
// current queue once it commits, there's no danger of applying the same
|
||||
// update twice.)
|
||||
//
|
||||
// Prioritization
|
||||
// --------------
|
||||
//
|
||||
// Updates are not sorted by priority, but by insertion; new updates are always
|
||||
// appended to the end of the list.
|
||||
//
|
||||
// The priority is still important, though. When processing the update queue
|
||||
// during the render phase, only the updates with sufficient priority are
|
||||
// included in the result. If we skip an update because it has insufficient
|
||||
// priority, it remains in the queue to be processed later, during a lower
|
||||
// priority render. Crucially, all updates subsequent to a skipped update also
|
||||
// remain in the queue *regardless of their priority*. That means high priority
|
||||
// updates are sometimes processed twice, at two separate priorities. We also
|
||||
// keep track of a base state, that represents the state before the first
|
||||
// update in the queue is applied.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// Given a base state of '', and the following queue of updates
|
||||
//
|
||||
// A1 - B2 - C1 - D2
|
||||
//
|
||||
// where the number indicates the priority, and the update is applied to the
|
||||
// previous state by appending a letter, React will process these updates as
|
||||
// two separate renders, one per distinct priority level:
|
||||
//
|
||||
// First render, at priority 1:
|
||||
// Base state: ''
|
||||
// Updates: [A1, C1]
|
||||
// Result state: 'AC'
|
||||
//
|
||||
// Second render, at priority 2:
|
||||
// Base state: 'A' <- The base state does not include C1,
|
||||
// because B2 was skipped.
|
||||
// Updates: [B2, C1, D2] <- C1 was rebased on top of B2
|
||||
// Result state: 'ABCD'
|
||||
//
|
||||
// Because we process updates in insertion order, and rebase high priority
|
||||
// updates when preceding updates are skipped, the final result is deterministic
|
||||
// regardless of priority. Intermediate state may vary according to system
|
||||
// resources, but the final state is always the same.
|
||||
|
||||
import type {Fiber, FiberRoot} from './ReactInternalTypes';
|
||||
import type {Lanes, Lane} from './ReactFiberLane.new';
|
||||
|
||||
import {
|
||||
NoLane,
|
||||
NoLanes,
|
||||
OffscreenLane,
|
||||
isSubsetOfLanes,
|
||||
mergeLanes,
|
||||
removeLanes,
|
||||
isTransitionLane,
|
||||
intersectLanes,
|
||||
markRootEntangled,
|
||||
} from './ReactFiberLane.new';
|
||||
import {
|
||||
enterDisallowedContextReadInDEV,
|
||||
exitDisallowedContextReadInDEV,
|
||||
} from './ReactFiberNewContext.new';
|
||||
import {
|
||||
Callback,
|
||||
Visibility,
|
||||
ShouldCapture,
|
||||
DidCapture,
|
||||
} from './ReactFiberFlags';
|
||||
|
||||
import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {StrictLegacyMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
markSkippedUpdateLanes,
|
||||
isUnsafeClassRenderPhaseUpdate,
|
||||
getWorkInProgressRootRenderLanes,
|
||||
} from './ReactFiberWorkLoop.new';
|
||||
import {
|
||||
enqueueConcurrentClassUpdate,
|
||||
unsafe_markUpdateLaneFromFiberToRoot,
|
||||
} from './ReactFiberConcurrentUpdates.new';
|
||||
import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
|
||||
|
||||
import assign from 'shared/assign';
|
||||
|
||||
export type Update<State> = {
|
||||
// TODO: Temporary field. Will remove this by storing a map of
|
||||
// transition -> event time on the root.
|
||||
eventTime: number,
|
||||
lane: Lane,
|
||||
|
||||
tag: 0 | 1 | 2 | 3,
|
||||
payload: any,
|
||||
callback: (() => mixed) | null,
|
||||
|
||||
next: Update<State> | null,
|
||||
};
|
||||
|
||||
export type SharedQueue<State> = {
|
||||
pending: Update<State> | null,
|
||||
lanes: Lanes,
|
||||
hiddenCallbacks: Array<() => mixed> | null,
|
||||
};
|
||||
|
||||
export type UpdateQueue<State> = {
|
||||
baseState: State,
|
||||
firstBaseUpdate: Update<State> | null,
|
||||
lastBaseUpdate: Update<State> | null,
|
||||
shared: SharedQueue<State>,
|
||||
callbacks: Array<() => mixed> | null,
|
||||
};
|
||||
|
||||
export const UpdateState = 0;
|
||||
export const ReplaceState = 1;
|
||||
export const ForceUpdate = 2;
|
||||
export const CaptureUpdate = 3;
|
||||
|
||||
// Global state that is reset at the beginning of calling `processUpdateQueue`.
|
||||
// It should only be read right after calling `processUpdateQueue`, via
|
||||
// `checkHasForceUpdateAfterProcessing`.
|
||||
let hasForceUpdate = false;
|
||||
|
||||
let didWarnUpdateInsideUpdate;
|
||||
let currentlyProcessingQueue;
|
||||
export let resetCurrentlyProcessingQueue: () => void;
|
||||
if (__DEV__) {
|
||||
didWarnUpdateInsideUpdate = false;
|
||||
currentlyProcessingQueue = null;
|
||||
resetCurrentlyProcessingQueue = () => {
|
||||
currentlyProcessingQueue = null;
|
||||
};
|
||||
}
|
||||
|
||||
export function initializeUpdateQueue<State>(fiber: Fiber): void {
|
||||
const queue: UpdateQueue<State> = {
|
||||
baseState: fiber.memoizedState,
|
||||
firstBaseUpdate: null,
|
||||
lastBaseUpdate: null,
|
||||
shared: {
|
||||
pending: null,
|
||||
lanes: NoLanes,
|
||||
hiddenCallbacks: null,
|
||||
},
|
||||
callbacks: null,
|
||||
};
|
||||
fiber.updateQueue = queue;
|
||||
}
|
||||
|
||||
export function cloneUpdateQueue<State>(
|
||||
current: Fiber,
|
||||
workInProgress: Fiber,
|
||||
): void {
|
||||
// Clone the update queue from current. Unless it's already a clone.
|
||||
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
|
||||
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
|
||||
if (queue === currentQueue) {
|
||||
const clone: UpdateQueue<State> = {
|
||||
baseState: currentQueue.baseState,
|
||||
firstBaseUpdate: currentQueue.firstBaseUpdate,
|
||||
lastBaseUpdate: currentQueue.lastBaseUpdate,
|
||||
shared: currentQueue.shared,
|
||||
callbacks: null,
|
||||
};
|
||||
workInProgress.updateQueue = clone;
|
||||
}
|
||||
}
|
||||
|
||||
export function createUpdate(eventTime: number, lane: Lane): Update<mixed> {
|
||||
const update: Update<mixed> = {
|
||||
eventTime,
|
||||
lane,
|
||||
|
||||
tag: UpdateState,
|
||||
payload: null,
|
||||
callback: null,
|
||||
|
||||
next: null,
|
||||
};
|
||||
return update;
|
||||
}
|
||||
|
||||
export function enqueueUpdate<State>(
|
||||
fiber: Fiber,
|
||||
update: Update<State>,
|
||||
lane: Lane,
|
||||
): FiberRoot | null {
|
||||
const updateQueue = fiber.updateQueue;
|
||||
if (updateQueue === null) {
|
||||
// Only occurs if the fiber has been unmounted.
|
||||
return null;
|
||||
}
|
||||
|
||||
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
|
||||
|
||||
if (__DEV__) {
|
||||
if (
|
||||
currentlyProcessingQueue === sharedQueue &&
|
||||
!didWarnUpdateInsideUpdate
|
||||
) {
|
||||
console.error(
|
||||
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
|
||||
'from inside an update function. Update functions should be pure, ' +
|
||||
'with zero side-effects. Consider using componentDidUpdate or a ' +
|
||||
'callback.',
|
||||
);
|
||||
didWarnUpdateInsideUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUnsafeClassRenderPhaseUpdate(fiber)) {
|
||||
// This is an unsafe render phase update. Add directly to the update
|
||||
// queue so we can process it immediately during the current render.
|
||||
const pending = sharedQueue.pending;
|
||||
if (pending === null) {
|
||||
// This is the first update. Create a circular list.
|
||||
update.next = update;
|
||||
} else {
|
||||
update.next = pending.next;
|
||||
pending.next = update;
|
||||
}
|
||||
sharedQueue.pending = update;
|
||||
|
||||
// Update the childLanes even though we're most likely already rendering
|
||||
// this fiber. This is for backwards compatibility in the case where you
|
||||
// update a different component during render phase than the one that is
|
||||
// currently renderings (a pattern that is accompanied by a warning).
|
||||
return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
|
||||
} else {
|
||||
return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
|
||||
}
|
||||
}
|
||||
|
||||
export function entangleTransitions(root: FiberRoot, fiber: Fiber, lane: Lane) {
|
||||
const updateQueue = fiber.updateQueue;
|
||||
if (updateQueue === null) {
|
||||
// Only occurs if the fiber has been unmounted.
|
||||
return;
|
||||
}
|
||||
|
||||
const sharedQueue: SharedQueue<mixed> = (updateQueue: any).shared;
|
||||
if (isTransitionLane(lane)) {
|
||||
let queueLanes = sharedQueue.lanes;
|
||||
|
||||
// If any entangled lanes are no longer pending on the root, then they must
|
||||
// have finished. We can remove them from the shared queue, which represents
|
||||
// a superset of the actually pending lanes. In some cases we may entangle
|
||||
// more than we need to, but that's OK. In fact it's worse if we *don't*
|
||||
// entangle when we should.
|
||||
queueLanes = intersectLanes(queueLanes, root.pendingLanes);
|
||||
|
||||
// Entangle the new transition lane with the other transition lanes.
|
||||
const newQueueLanes = mergeLanes(queueLanes, lane);
|
||||
sharedQueue.lanes = newQueueLanes;
|
||||
// Even if queue.lanes already include lane, we don't know for certain if
|
||||
// the lane finished since the last time we entangled it. So we need to
|
||||
// entangle it again, just to be sure.
|
||||
markRootEntangled(root, newQueueLanes);
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueCapturedUpdate<State>(
|
||||
workInProgress: Fiber,
|
||||
capturedUpdate: Update<State>,
|
||||
) {
|
||||
// Captured updates are updates that are thrown by a child during the render
|
||||
// phase. They should be discarded if the render is aborted. Therefore,
|
||||
// we should only put them on the work-in-progress queue, not the current one.
|
||||
let queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
|
||||
|
||||
// Check if the work-in-progress queue is a clone.
|
||||
const current = workInProgress.alternate;
|
||||
if (current !== null) {
|
||||
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
|
||||
if (queue === currentQueue) {
|
||||
// The work-in-progress queue is the same as current. This happens when
|
||||
// we bail out on a parent fiber that then captures an error thrown by
|
||||
// a child. Since we want to append the update only to the work-in
|
||||
// -progress queue, we need to clone the updates. We usually clone during
|
||||
// processUpdateQueue, but that didn't happen in this case because we
|
||||
// skipped over the parent when we bailed out.
|
||||
let newFirst = null;
|
||||
let newLast = null;
|
||||
const firstBaseUpdate = queue.firstBaseUpdate;
|
||||
if (firstBaseUpdate !== null) {
|
||||
// Loop through the updates and clone them.
|
||||
let update: Update<State> = firstBaseUpdate;
|
||||
do {
|
||||
const clone: Update<State> = {
|
||||
eventTime: update.eventTime,
|
||||
lane: update.lane,
|
||||
|
||||
tag: update.tag,
|
||||
payload: update.payload,
|
||||
// When this update is rebased, we should not fire its
|
||||
// callback again.
|
||||
callback: null,
|
||||
|
||||
next: null,
|
||||
};
|
||||
if (newLast === null) {
|
||||
newFirst = newLast = clone;
|
||||
} else {
|
||||
newLast.next = clone;
|
||||
newLast = clone;
|
||||
}
|
||||
// $FlowFixMe[incompatible-type] we bail out when we get a null
|
||||
update = update.next;
|
||||
} while (update !== null);
|
||||
|
||||
// Append the captured update the end of the cloned list.
|
||||
if (newLast === null) {
|
||||
newFirst = newLast = capturedUpdate;
|
||||
} else {
|
||||
newLast.next = capturedUpdate;
|
||||
newLast = capturedUpdate;
|
||||
}
|
||||
} else {
|
||||
// There are no base updates.
|
||||
newFirst = newLast = capturedUpdate;
|
||||
}
|
||||
queue = {
|
||||
baseState: currentQueue.baseState,
|
||||
firstBaseUpdate: newFirst,
|
||||
lastBaseUpdate: newLast,
|
||||
shared: currentQueue.shared,
|
||||
callbacks: currentQueue.callbacks,
|
||||
};
|
||||
workInProgress.updateQueue = queue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Append the update to the end of the list.
|
||||
const lastBaseUpdate = queue.lastBaseUpdate;
|
||||
if (lastBaseUpdate === null) {
|
||||
queue.firstBaseUpdate = capturedUpdate;
|
||||
} else {
|
||||
lastBaseUpdate.next = capturedUpdate;
|
||||
}
|
||||
queue.lastBaseUpdate = capturedUpdate;
|
||||
}
|
||||
|
||||
function getStateFromUpdate<State>(
|
||||
workInProgress: Fiber,
|
||||
queue: UpdateQueue<State>,
|
||||
update: Update<State>,
|
||||
prevState: State,
|
||||
nextProps: any,
|
||||
instance: any,
|
||||
): any {
|
||||
switch (update.tag) {
|
||||
case ReplaceState: {
|
||||
const payload = update.payload;
|
||||
if (typeof payload === 'function') {
|
||||
// Updater function
|
||||
if (__DEV__) {
|
||||
enterDisallowedContextReadInDEV();
|
||||
}
|
||||
const nextState = payload.call(instance, prevState, nextProps);
|
||||
if (__DEV__) {
|
||||
if (
|
||||
debugRenderPhaseSideEffectsForStrictMode &&
|
||||
workInProgress.mode & StrictLegacyMode
|
||||
) {
|
||||
setIsStrictModeForDevtools(true);
|
||||
try {
|
||||
payload.call(instance, prevState, nextProps);
|
||||
} finally {
|
||||
setIsStrictModeForDevtools(false);
|
||||
}
|
||||
}
|
||||
exitDisallowedContextReadInDEV();
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
// State object
|
||||
return payload;
|
||||
}
|
||||
case CaptureUpdate: {
|
||||
workInProgress.flags =
|
||||
(workInProgress.flags & ~ShouldCapture) | DidCapture;
|
||||
}
|
||||
// Intentional fallthrough
|
||||
case UpdateState: {
|
||||
const payload = update.payload;
|
||||
let partialState;
|
||||
if (typeof payload === 'function') {
|
||||
// Updater function
|
||||
if (__DEV__) {
|
||||
enterDisallowedContextReadInDEV();
|
||||
}
|
||||
partialState = payload.call(instance, prevState, nextProps);
|
||||
if (__DEV__) {
|
||||
if (
|
||||
debugRenderPhaseSideEffectsForStrictMode &&
|
||||
workInProgress.mode & StrictLegacyMode
|
||||
) {
|
||||
setIsStrictModeForDevtools(true);
|
||||
try {
|
||||
payload.call(instance, prevState, nextProps);
|
||||
} finally {
|
||||
setIsStrictModeForDevtools(false);
|
||||
}
|
||||
}
|
||||
exitDisallowedContextReadInDEV();
|
||||
}
|
||||
} else {
|
||||
// Partial state object
|
||||
partialState = payload;
|
||||
}
|
||||
if (partialState === null || partialState === undefined) {
|
||||
// Null and undefined are treated as no-ops.
|
||||
return prevState;
|
||||
}
|
||||
// Merge the partial state and the previous state.
|
||||
return assign({}, prevState, partialState);
|
||||
}
|
||||
case ForceUpdate: {
|
||||
hasForceUpdate = true;
|
||||
return prevState;
|
||||
}
|
||||
}
|
||||
return prevState;
|
||||
}
|
||||
|
||||
export function processUpdateQueue<State>(
|
||||
workInProgress: Fiber,
|
||||
props: any,
|
||||
instance: any,
|
||||
renderLanes: Lanes,
|
||||
): void {
|
||||
// This is always non-null on a ClassComponent or HostRoot
|
||||
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
|
||||
|
||||
hasForceUpdate = false;
|
||||
|
||||
if (__DEV__) {
|
||||
// $FlowFixMe[escaped-generic] discovered when updating Flow
|
||||
currentlyProcessingQueue = queue.shared;
|
||||
}
|
||||
|
||||
let firstBaseUpdate = queue.firstBaseUpdate;
|
||||
let lastBaseUpdate = queue.lastBaseUpdate;
|
||||
|
||||
// Check if there are pending updates. If so, transfer them to the base queue.
|
||||
let pendingQueue = queue.shared.pending;
|
||||
if (pendingQueue !== null) {
|
||||
queue.shared.pending = null;
|
||||
|
||||
// The pending queue is circular. Disconnect the pointer between first
|
||||
// and last so that it's non-circular.
|
||||
const lastPendingUpdate = pendingQueue;
|
||||
const firstPendingUpdate = lastPendingUpdate.next;
|
||||
lastPendingUpdate.next = null;
|
||||
// Append pending updates to base queue
|
||||
if (lastBaseUpdate === null) {
|
||||
firstBaseUpdate = firstPendingUpdate;
|
||||
} else {
|
||||
lastBaseUpdate.next = firstPendingUpdate;
|
||||
}
|
||||
lastBaseUpdate = lastPendingUpdate;
|
||||
|
||||
// If there's a current queue, and it's different from the base queue, then
|
||||
// we need to transfer the updates to that queue, too. Because the base
|
||||
// queue is a singly-linked list with no cycles, we can append to both
|
||||
// lists and take advantage of structural sharing.
|
||||
// TODO: Pass `current` as argument
|
||||
const current = workInProgress.alternate;
|
||||
if (current !== null) {
|
||||
// This is always non-null on a ClassComponent or HostRoot
|
||||
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
|
||||
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
|
||||
if (currentLastBaseUpdate !== lastBaseUpdate) {
|
||||
if (currentLastBaseUpdate === null) {
|
||||
currentQueue.firstBaseUpdate = firstPendingUpdate;
|
||||
} else {
|
||||
currentLastBaseUpdate.next = firstPendingUpdate;
|
||||
}
|
||||
currentQueue.lastBaseUpdate = lastPendingUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These values may change as we process the queue.
|
||||
if (firstBaseUpdate !== null) {
|
||||
// Iterate through the list of updates to compute the result.
|
||||
let newState = queue.baseState;
|
||||
// TODO: Don't need to accumulate this. Instead, we can remove renderLanes
|
||||
// from the original lanes.
|
||||
let newLanes = NoLanes;
|
||||
|
||||
let newBaseState = null;
|
||||
let newFirstBaseUpdate = null;
|
||||
let newLastBaseUpdate = null;
|
||||
|
||||
let update: Update<State> = firstBaseUpdate;
|
||||
do {
|
||||
// TODO: Don't need this field anymore
|
||||
const updateEventTime = update.eventTime;
|
||||
|
||||
// An extra OffscreenLane bit is added to updates that were made to
|
||||
// a hidden tree, so that we can distinguish them from updates that were
|
||||
// already there when the tree was hidden.
|
||||
const updateLane = removeLanes(update.lane, OffscreenLane);
|
||||
const isHiddenUpdate = updateLane !== update.lane;
|
||||
|
||||
// Check if this update was made while the tree was hidden. If so, then
|
||||
// it's not a "base" update and we should disregard the extra base lanes
|
||||
// that were added to renderLanes when we entered the Offscreen tree.
|
||||
const shouldSkipUpdate = isHiddenUpdate
|
||||
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
|
||||
: !isSubsetOfLanes(renderLanes, updateLane);
|
||||
|
||||
if (shouldSkipUpdate) {
|
||||
// Priority is insufficient. Skip this update. If this is the first
|
||||
// skipped update, the previous update/state is the new base
|
||||
// update/state.
|
||||
const clone: Update<State> = {
|
||||
eventTime: updateEventTime,
|
||||
lane: updateLane,
|
||||
|
||||
tag: update.tag,
|
||||
payload: update.payload,
|
||||
callback: update.callback,
|
||||
|
||||
next: null,
|
||||
};
|
||||
if (newLastBaseUpdate === null) {
|
||||
newFirstBaseUpdate = newLastBaseUpdate = clone;
|
||||
newBaseState = newState;
|
||||
} else {
|
||||
newLastBaseUpdate = newLastBaseUpdate.next = clone;
|
||||
}
|
||||
// Update the remaining priority in the queue.
|
||||
newLanes = mergeLanes(newLanes, updateLane);
|
||||
} else {
|
||||
// This update does have sufficient priority.
|
||||
|
||||
if (newLastBaseUpdate !== null) {
|
||||
const clone: Update<State> = {
|
||||
eventTime: updateEventTime,
|
||||
// This update is going to be committed so we never want uncommit
|
||||
// it. Using NoLane works because 0 is a subset of all bitmasks, so
|
||||
// this will never be skipped by the check above.
|
||||
lane: NoLane,
|
||||
|
||||
tag: update.tag,
|
||||
payload: update.payload,
|
||||
|
||||
// When this update is rebased, we should not fire its
|
||||
// callback again.
|
||||
callback: null,
|
||||
|
||||
next: null,
|
||||
};
|
||||
newLastBaseUpdate = newLastBaseUpdate.next = clone;
|
||||
}
|
||||
|
||||
// Process this update.
|
||||
newState = getStateFromUpdate(
|
||||
workInProgress,
|
||||
queue,
|
||||
update,
|
||||
newState,
|
||||
props,
|
||||
instance,
|
||||
);
|
||||
const callback = update.callback;
|
||||
if (callback !== null) {
|
||||
workInProgress.flags |= Callback;
|
||||
if (isHiddenUpdate) {
|
||||
workInProgress.flags |= Visibility;
|
||||
}
|
||||
const callbacks = queue.callbacks;
|
||||
if (callbacks === null) {
|
||||
queue.callbacks = [callback];
|
||||
} else {
|
||||
callbacks.push(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
// $FlowFixMe[incompatible-type] we bail out when we get a null
|
||||
update = update.next;
|
||||
if (update === null) {
|
||||
pendingQueue = queue.shared.pending;
|
||||
if (pendingQueue === null) {
|
||||
break;
|
||||
} else {
|
||||
// An update was scheduled from inside a reducer. Add the new
|
||||
// pending updates to the end of the list and keep processing.
|
||||
const lastPendingUpdate = pendingQueue;
|
||||
// Intentionally unsound. Pending updates form a circular list, but we
|
||||
// unravel them when transferring them to the base queue.
|
||||
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
|
||||
lastPendingUpdate.next = null;
|
||||
update = firstPendingUpdate;
|
||||
queue.lastBaseUpdate = lastPendingUpdate;
|
||||
queue.shared.pending = null;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
|
||||
if (newLastBaseUpdate === null) {
|
||||
newBaseState = newState;
|
||||
}
|
||||
|
||||
queue.baseState = ((newBaseState: any): State);
|
||||
queue.firstBaseUpdate = newFirstBaseUpdate;
|
||||
queue.lastBaseUpdate = newLastBaseUpdate;
|
||||
|
||||
if (firstBaseUpdate === null) {
|
||||
// `queue.lanes` is used for entangling transitions. We can set it back to
|
||||
// zero once the queue is empty.
|
||||
queue.shared.lanes = NoLanes;
|
||||
}
|
||||
|
||||
// Set the remaining expiration time to be whatever is remaining in the queue.
|
||||
// This should be fine because the only two other things that contribute to
|
||||
// expiration time are props and context. We're already in the middle of the
|
||||
// begin phase by the time we start processing the queue, so we've already
|
||||
// dealt with the props. Context in components that specify
|
||||
// shouldComponentUpdate is tricky; but we'll have to account for
|
||||
// that regardless.
|
||||
markSkippedUpdateLanes(newLanes);
|
||||
workInProgress.lanes = newLanes;
|
||||
workInProgress.memoizedState = newState;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
currentlyProcessingQueue = null;
|
||||
}
|
||||
}
|
||||
|
||||
function callCallback(callback, context) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new Error(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
`received: ${callback}`,
|
||||
);
|
||||
}
|
||||
|
||||
callback.call(context);
|
||||
}
|
||||
|
||||
export function resetHasForceUpdateBeforeProcessing() {
|
||||
hasForceUpdate = false;
|
||||
}
|
||||
|
||||
export function checkHasForceUpdateAfterProcessing(): boolean {
|
||||
return hasForceUpdate;
|
||||
}
|
||||
|
||||
export function deferHiddenCallbacks<State>(
|
||||
updateQueue: UpdateQueue<State>,
|
||||
): void {
|
||||
// When an update finishes on a hidden component, its callback should not
|
||||
// be fired until/unless the component is made visible again. Stash the
|
||||
// callback on the shared queue object so it can be fired later.
|
||||
const newHiddenCallbacks = updateQueue.callbacks;
|
||||
if (newHiddenCallbacks !== null) {
|
||||
const existingHiddenCallbacks = updateQueue.shared.hiddenCallbacks;
|
||||
if (existingHiddenCallbacks === null) {
|
||||
updateQueue.shared.hiddenCallbacks = newHiddenCallbacks;
|
||||
} else {
|
||||
updateQueue.shared.hiddenCallbacks = existingHiddenCallbacks.concat(
|
||||
newHiddenCallbacks,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function commitHiddenCallbacks<State>(
|
||||
updateQueue: UpdateQueue<State>,
|
||||
context: any,
|
||||
): void {
|
||||
// This component is switching from hidden -> visible. Commit any callbacks
|
||||
// that were previously deferred.
|
||||
const hiddenCallbacks = updateQueue.shared.hiddenCallbacks;
|
||||
if (hiddenCallbacks !== null) {
|
||||
updateQueue.shared.hiddenCallbacks = null;
|
||||
for (let i = 0; i < hiddenCallbacks.length; i++) {
|
||||
const callback = hiddenCallbacks[i];
|
||||
callCallback(callback, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function commitCallbacks<State>(
|
||||
updateQueue: UpdateQueue<State>,
|
||||
context: any,
|
||||
): void {
|
||||
const callbacks = updateQueue.callbacks;
|
||||
if (callbacks !== null) {
|
||||
updateQueue.callbacks = null;
|
||||
for (let i = 0; i < callbacks.length; i++) {
|
||||
const callback = callbacks[i];
|
||||
callCallback(callback, context);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,288 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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, FiberRoot} from './ReactInternalTypes';
|
||||
import type {
|
||||
UpdateQueue as HookQueue,
|
||||
Update as HookUpdate,
|
||||
} from './ReactFiberHooks.new';
|
||||
import type {
|
||||
SharedQueue as ClassQueue,
|
||||
Update as ClassUpdate,
|
||||
} from './ReactFiberClassUpdateQueue.new';
|
||||
import type {Lane, Lanes} from './ReactFiberLane.new';
|
||||
import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
|
||||
|
||||
import {
|
||||
warnAboutUpdateOnNotYetMountedFiberInDEV,
|
||||
throwIfInfiniteUpdateLoopDetected,
|
||||
getWorkInProgressRoot,
|
||||
} from './ReactFiberWorkLoop.new';
|
||||
import {
|
||||
NoLane,
|
||||
NoLanes,
|
||||
mergeLanes,
|
||||
markHiddenUpdate,
|
||||
} from './ReactFiberLane.new';
|
||||
import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
|
||||
import {HostRoot, OffscreenComponent} from './ReactWorkTags';
|
||||
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
|
||||
|
||||
export type ConcurrentUpdate = {
|
||||
next: ConcurrentUpdate,
|
||||
lane: Lane,
|
||||
};
|
||||
|
||||
type ConcurrentQueue = {
|
||||
pending: ConcurrentUpdate | null,
|
||||
};
|
||||
|
||||
// If a render is in progress, and we receive an update from a concurrent event,
|
||||
// we wait until the current render is over (either finished or interrupted)
|
||||
// before adding it to the fiber/hook queue. Push to this array so we can
|
||||
// access the queue, fiber, update, et al later.
|
||||
const concurrentQueues: Array<any> = [];
|
||||
let concurrentQueuesIndex = 0;
|
||||
|
||||
let concurrentlyUpdatedLanes: Lanes = NoLanes;
|
||||
|
||||
export function finishQueueingConcurrentUpdates(): void {
|
||||
const endIndex = concurrentQueuesIndex;
|
||||
concurrentQueuesIndex = 0;
|
||||
|
||||
concurrentlyUpdatedLanes = NoLanes;
|
||||
|
||||
let i = 0;
|
||||
while (i < endIndex) {
|
||||
const fiber: Fiber = concurrentQueues[i];
|
||||
concurrentQueues[i++] = null;
|
||||
const queue: ConcurrentQueue = concurrentQueues[i];
|
||||
concurrentQueues[i++] = null;
|
||||
const update: ConcurrentUpdate = concurrentQueues[i];
|
||||
concurrentQueues[i++] = null;
|
||||
const lane: Lane = concurrentQueues[i];
|
||||
concurrentQueues[i++] = null;
|
||||
|
||||
if (queue !== null && update !== null) {
|
||||
const pending = queue.pending;
|
||||
if (pending === null) {
|
||||
// This is the first update. Create a circular list.
|
||||
update.next = update;
|
||||
} else {
|
||||
update.next = pending.next;
|
||||
pending.next = update;
|
||||
}
|
||||
queue.pending = update;
|
||||
}
|
||||
|
||||
if (lane !== NoLane) {
|
||||
markUpdateLaneFromFiberToRoot(fiber, update, lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getConcurrentlyUpdatedLanes(): Lanes {
|
||||
return concurrentlyUpdatedLanes;
|
||||
}
|
||||
|
||||
function enqueueUpdate(
|
||||
fiber: Fiber,
|
||||
queue: ConcurrentQueue | null,
|
||||
update: ConcurrentUpdate | null,
|
||||
lane: Lane,
|
||||
) {
|
||||
// Don't update the `childLanes` on the return path yet. If we already in
|
||||
// the middle of rendering, wait until after it has completed.
|
||||
concurrentQueues[concurrentQueuesIndex++] = fiber;
|
||||
concurrentQueues[concurrentQueuesIndex++] = queue;
|
||||
concurrentQueues[concurrentQueuesIndex++] = update;
|
||||
concurrentQueues[concurrentQueuesIndex++] = lane;
|
||||
|
||||
concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
|
||||
|
||||
// The fiber's `lane` field is used in some places to check if any work is
|
||||
// scheduled, to perform an eager bailout, so we need to update it immediately.
|
||||
// TODO: We should probably move this to the "shared" queue instead.
|
||||
fiber.lanes = mergeLanes(fiber.lanes, lane);
|
||||
const alternate = fiber.alternate;
|
||||
if (alternate !== null) {
|
||||
alternate.lanes = mergeLanes(alternate.lanes, lane);
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueConcurrentHookUpdate<S, A>(
|
||||
fiber: Fiber,
|
||||
queue: HookQueue<S, A>,
|
||||
update: HookUpdate<S, A>,
|
||||
lane: Lane,
|
||||
): FiberRoot | null {
|
||||
const concurrentQueue: ConcurrentQueue = (queue: any);
|
||||
const concurrentUpdate: ConcurrentUpdate = (update: any);
|
||||
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
|
||||
return getRootForUpdatedFiber(fiber);
|
||||
}
|
||||
|
||||
export function enqueueConcurrentHookUpdateAndEagerlyBailout<S, A>(
|
||||
fiber: Fiber,
|
||||
queue: HookQueue<S, A>,
|
||||
update: HookUpdate<S, A>,
|
||||
): void {
|
||||
// This function is used to queue an update that doesn't need a rerender. The
|
||||
// only reason we queue it is in case there's a subsequent higher priority
|
||||
// update that causes it to be rebased.
|
||||
const lane = NoLane;
|
||||
const concurrentQueue: ConcurrentQueue = (queue: any);
|
||||
const concurrentUpdate: ConcurrentUpdate = (update: any);
|
||||
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
|
||||
|
||||
// Usually we can rely on the upcoming render phase to process the concurrent
|
||||
// queue. However, since this is a bail out, we're not scheduling any work
|
||||
// here. So the update we just queued will leak until something else happens
|
||||
// to schedule work (if ever).
|
||||
//
|
||||
// Check if we're currently in the middle of rendering a tree, and if not,
|
||||
// process the queue immediately to prevent a leak.
|
||||
const isConcurrentlyRendering = getWorkInProgressRoot() !== null;
|
||||
if (!isConcurrentlyRendering) {
|
||||
finishQueueingConcurrentUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueConcurrentClassUpdate<State>(
|
||||
fiber: Fiber,
|
||||
queue: ClassQueue<State>,
|
||||
update: ClassUpdate<State>,
|
||||
lane: Lane,
|
||||
): FiberRoot | null {
|
||||
const concurrentQueue: ConcurrentQueue = (queue: any);
|
||||
const concurrentUpdate: ConcurrentUpdate = (update: any);
|
||||
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
|
||||
return getRootForUpdatedFiber(fiber);
|
||||
}
|
||||
|
||||
export function enqueueConcurrentRenderForLane(
|
||||
fiber: Fiber,
|
||||
lane: Lane,
|
||||
): FiberRoot | null {
|
||||
enqueueUpdate(fiber, null, null, lane);
|
||||
return getRootForUpdatedFiber(fiber);
|
||||
}
|
||||
|
||||
// Calling this function outside this module should only be done for backwards
|
||||
// compatibility and should always be accompanied by a warning.
|
||||
export function unsafe_markUpdateLaneFromFiberToRoot(
|
||||
sourceFiber: Fiber,
|
||||
lane: Lane,
|
||||
): FiberRoot | null {
|
||||
// NOTE: For Hyrum's Law reasons, if an infinite update loop is detected, it
|
||||
// should throw before `markUpdateLaneFromFiberToRoot` is called. But this is
|
||||
// undefined behavior and we can change it if we need to; it just so happens
|
||||
// that, at the time of this writing, there's an internal product test that
|
||||
// happens to rely on this.
|
||||
const root = getRootForUpdatedFiber(sourceFiber);
|
||||
markUpdateLaneFromFiberToRoot(sourceFiber, null, lane);
|
||||
return root;
|
||||
}
|
||||
|
||||
function markUpdateLaneFromFiberToRoot(
|
||||
sourceFiber: Fiber,
|
||||
update: ConcurrentUpdate | null,
|
||||
lane: Lane,
|
||||
): void {
|
||||
// Update the source fiber's lanes
|
||||
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
|
||||
let alternate = sourceFiber.alternate;
|
||||
if (alternate !== null) {
|
||||
alternate.lanes = mergeLanes(alternate.lanes, lane);
|
||||
}
|
||||
// Walk the parent path to the root and update the child lanes.
|
||||
let isHidden = false;
|
||||
let parent = sourceFiber.return;
|
||||
let node = sourceFiber;
|
||||
while (parent !== null) {
|
||||
parent.childLanes = mergeLanes(parent.childLanes, lane);
|
||||
alternate = parent.alternate;
|
||||
if (alternate !== null) {
|
||||
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
|
||||
}
|
||||
|
||||
if (parent.tag === OffscreenComponent) {
|
||||
// Check if this offscreen boundary is currently hidden.
|
||||
//
|
||||
// The instance may be null if the Offscreen parent was unmounted. Usually
|
||||
// the parent wouldn't be reachable in that case because we disconnect
|
||||
// fibers from the tree when they are deleted. However, there's a weird
|
||||
// edge case where setState is called on a fiber that was interrupted
|
||||
// before it ever mounted. Because it never mounts, it also never gets
|
||||
// deleted. Because it never gets deleted, its return pointer never gets
|
||||
// disconnected. Which means it may be attached to a deleted Offscreen
|
||||
// parent node. (This discovery suggests it may be better for memory usage
|
||||
// if we don't attach the `return` pointer until the commit phase, though
|
||||
// in order to do that we'd need some other way to track the return
|
||||
// pointer during the initial render, like on the stack.)
|
||||
//
|
||||
// This case is always accompanied by a warning, but we still need to
|
||||
// account for it. (There may be other cases that we haven't discovered,
|
||||
// too.)
|
||||
const offscreenInstance: OffscreenInstance | null = parent.stateNode;
|
||||
if (
|
||||
offscreenInstance !== null &&
|
||||
!(offscreenInstance._visibility & OffscreenVisible)
|
||||
) {
|
||||
isHidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
node = parent;
|
||||
parent = parent.return;
|
||||
}
|
||||
|
||||
if (isHidden && update !== null && node.tag === HostRoot) {
|
||||
const root: FiberRoot = node.stateNode;
|
||||
markHiddenUpdate(root, update, lane);
|
||||
}
|
||||
}
|
||||
|
||||
function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
|
||||
// TODO: We will detect and infinite update loop and throw even if this fiber
|
||||
// has already unmounted. This isn't really necessary but it happens to be the
|
||||
// current behavior we've used for several release cycles. Consider not
|
||||
// performing this check if the updated fiber already unmounted, since it's
|
||||
// not possible for that to cause an infinite update loop.
|
||||
throwIfInfiniteUpdateLoopDetected();
|
||||
|
||||
// When a setState happens, we must ensure the root is scheduled. Because
|
||||
// update queues do not have a backpointer to the root, the only way to do
|
||||
// this currently is to walk up the return path. This used to not be a big
|
||||
// deal because we would have to walk up the return path to set
|
||||
// the `childLanes`, anyway, but now those two traversals happen at
|
||||
// different times.
|
||||
// TODO: Consider adding a `root` backpointer on the update queue.
|
||||
detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
|
||||
let node = sourceFiber;
|
||||
let parent = node.return;
|
||||
while (parent !== null) {
|
||||
detectUpdateOnUnmountedFiber(sourceFiber, node);
|
||||
node = parent;
|
||||
parent = node.return;
|
||||
}
|
||||
return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
|
||||
}
|
||||
|
||||
function detectUpdateOnUnmountedFiber(sourceFiber: Fiber, parent: Fiber) {
|
||||
if (__DEV__) {
|
||||
const alternate = parent.alternate;
|
||||
if (
|
||||
alternate === null &&
|
||||
(parent.flags & (Placement | Hydrating)) !== NoFlags
|
||||
) {
|
||||
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,343 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 './ReactInternalTypes';
|
||||
import type {StackCursor} from './ReactFiberStack.new';
|
||||
|
||||
import {isFiberMounted} from './ReactFiberTreeReflection';
|
||||
import {disableLegacyContext} from 'shared/ReactFeatureFlags';
|
||||
import {ClassComponent, HostRoot} from './ReactWorkTags';
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
import checkPropTypes from 'shared/checkPropTypes';
|
||||
|
||||
import {createCursor, push, pop} from './ReactFiberStack.new';
|
||||
|
||||
let warnedAboutMissingGetChildContext;
|
||||
|
||||
if (__DEV__) {
|
||||
warnedAboutMissingGetChildContext = {};
|
||||
}
|
||||
|
||||
// $FlowFixMe[incompatible-exact]
|
||||
export const emptyContextObject: {} = {};
|
||||
if (__DEV__) {
|
||||
Object.freeze(emptyContextObject);
|
||||
}
|
||||
|
||||
// A cursor to the current merged context object on the stack.
|
||||
const contextStackCursor: StackCursor<Object> = createCursor(
|
||||
emptyContextObject,
|
||||
);
|
||||
// A cursor to a boolean indicating whether the context has changed.
|
||||
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
|
||||
// Keep track of the previous context object that was on the stack.
|
||||
// We use this to get access to the parent context after we have already
|
||||
// pushed the next context provider, and now need to merge their contexts.
|
||||
let previousContext: Object = emptyContextObject;
|
||||
|
||||
function getUnmaskedContext(
|
||||
workInProgress: Fiber,
|
||||
Component: Function,
|
||||
didPushOwnContextIfProvider: boolean,
|
||||
): Object {
|
||||
if (disableLegacyContext) {
|
||||
return emptyContextObject;
|
||||
} else {
|
||||
if (didPushOwnContextIfProvider && isContextProvider(Component)) {
|
||||
// If the fiber is a context provider itself, when we read its context
|
||||
// we may have already pushed its own child context on the stack. A context
|
||||
// provider should not "see" its own child context. Therefore we read the
|
||||
// previous (parent) context instead for a context provider.
|
||||
return previousContext;
|
||||
}
|
||||
return contextStackCursor.current;
|
||||
}
|
||||
}
|
||||
|
||||
function cacheContext(
|
||||
workInProgress: Fiber,
|
||||
unmaskedContext: Object,
|
||||
maskedContext: Object,
|
||||
): void {
|
||||
if (disableLegacyContext) {
|
||||
return;
|
||||
} else {
|
||||
const instance = workInProgress.stateNode;
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
|
||||
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
|
||||
}
|
||||
}
|
||||
|
||||
function getMaskedContext(
|
||||
workInProgress: Fiber,
|
||||
unmaskedContext: Object,
|
||||
): Object {
|
||||
if (disableLegacyContext) {
|
||||
return emptyContextObject;
|
||||
} else {
|
||||
const type = workInProgress.type;
|
||||
const contextTypes = type.contextTypes;
|
||||
if (!contextTypes) {
|
||||
return emptyContextObject;
|
||||
}
|
||||
|
||||
// Avoid recreating masked context unless unmasked context has changed.
|
||||
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
|
||||
// This may trigger infinite loops if componentWillReceiveProps calls setState.
|
||||
const instance = workInProgress.stateNode;
|
||||
if (
|
||||
instance &&
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
|
||||
) {
|
||||
return instance.__reactInternalMemoizedMaskedChildContext;
|
||||
}
|
||||
|
||||
const context = {};
|
||||
for (const key in contextTypes) {
|
||||
context[key] = unmaskedContext[key];
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const name = getComponentNameFromFiber(workInProgress) || 'Unknown';
|
||||
checkPropTypes(contextTypes, context, 'context', name);
|
||||
}
|
||||
|
||||
// Cache unmasked context so we can avoid recreating masked context unless necessary.
|
||||
// Context is created before the class component is instantiated so check for instance.
|
||||
if (instance) {
|
||||
cacheContext(workInProgress, unmaskedContext, context);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
function hasContextChanged(): boolean {
|
||||
if (disableLegacyContext) {
|
||||
return false;
|
||||
} else {
|
||||
return didPerformWorkStackCursor.current;
|
||||
}
|
||||
}
|
||||
|
||||
function isContextProvider(type: Function): boolean {
|
||||
if (disableLegacyContext) {
|
||||
return false;
|
||||
} else {
|
||||
const childContextTypes = type.childContextTypes;
|
||||
return childContextTypes !== null && childContextTypes !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function popContext(fiber: Fiber): void {
|
||||
if (disableLegacyContext) {
|
||||
return;
|
||||
} else {
|
||||
pop(didPerformWorkStackCursor, fiber);
|
||||
pop(contextStackCursor, fiber);
|
||||
}
|
||||
}
|
||||
|
||||
function popTopLevelContextObject(fiber: Fiber): void {
|
||||
if (disableLegacyContext) {
|
||||
return;
|
||||
} else {
|
||||
pop(didPerformWorkStackCursor, fiber);
|
||||
pop(contextStackCursor, fiber);
|
||||
}
|
||||
}
|
||||
|
||||
function pushTopLevelContextObject(
|
||||
fiber: Fiber,
|
||||
context: Object,
|
||||
didChange: boolean,
|
||||
): void {
|
||||
if (disableLegacyContext) {
|
||||
return;
|
||||
} else {
|
||||
if (contextStackCursor.current !== emptyContextObject) {
|
||||
throw new Error(
|
||||
'Unexpected context found on stack. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
push(contextStackCursor, context, fiber);
|
||||
push(didPerformWorkStackCursor, didChange, fiber);
|
||||
}
|
||||
}
|
||||
|
||||
function processChildContext(
|
||||
fiber: Fiber,
|
||||
type: any,
|
||||
parentContext: Object,
|
||||
): Object {
|
||||
if (disableLegacyContext) {
|
||||
return parentContext;
|
||||
} else {
|
||||
const instance = fiber.stateNode;
|
||||
const childContextTypes = type.childContextTypes;
|
||||
|
||||
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
|
||||
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
|
||||
if (typeof instance.getChildContext !== 'function') {
|
||||
if (__DEV__) {
|
||||
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
|
||||
|
||||
if (!warnedAboutMissingGetChildContext[componentName]) {
|
||||
warnedAboutMissingGetChildContext[componentName] = true;
|
||||
console.error(
|
||||
'%s.childContextTypes is specified but there is no getChildContext() method ' +
|
||||
'on the instance. You can either define getChildContext() on %s or remove ' +
|
||||
'childContextTypes from it.',
|
||||
componentName,
|
||||
componentName,
|
||||
);
|
||||
}
|
||||
}
|
||||
return parentContext;
|
||||
}
|
||||
|
||||
const childContext = instance.getChildContext();
|
||||
for (const contextKey in childContext) {
|
||||
if (!(contextKey in childContextTypes)) {
|
||||
throw new Error(
|
||||
`${getComponentNameFromFiber(fiber) ||
|
||||
'Unknown'}.getChildContext(): key "${contextKey}" is not defined in childContextTypes.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
const name = getComponentNameFromFiber(fiber) || 'Unknown';
|
||||
checkPropTypes(childContextTypes, childContext, 'child context', name);
|
||||
}
|
||||
|
||||
return {...parentContext, ...childContext};
|
||||
}
|
||||
}
|
||||
|
||||
function pushContextProvider(workInProgress: Fiber): boolean {
|
||||
if (disableLegacyContext) {
|
||||
return false;
|
||||
} else {
|
||||
const instance = workInProgress.stateNode;
|
||||
// We push the context as early as possible to ensure stack integrity.
|
||||
// If the instance does not exist yet, we will push null at first,
|
||||
// and replace it on the stack later when invalidating the context.
|
||||
const memoizedMergedChildContext =
|
||||
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
|
||||
emptyContextObject;
|
||||
|
||||
// Remember the parent context so we can merge with it later.
|
||||
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
|
||||
previousContext = contextStackCursor.current;
|
||||
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
|
||||
push(
|
||||
didPerformWorkStackCursor,
|
||||
didPerformWorkStackCursor.current,
|
||||
workInProgress,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function invalidateContextProvider(
|
||||
workInProgress: Fiber,
|
||||
type: any,
|
||||
didChange: boolean,
|
||||
): void {
|
||||
if (disableLegacyContext) {
|
||||
return;
|
||||
} else {
|
||||
const instance = workInProgress.stateNode;
|
||||
|
||||
if (!instance) {
|
||||
throw new Error(
|
||||
'Expected to have an instance by this point. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
if (didChange) {
|
||||
// Merge parent and own context.
|
||||
// Skip this if we're not updating due to sCU.
|
||||
// This avoids unnecessarily recomputing memoized values.
|
||||
const mergedContext = processChildContext(
|
||||
workInProgress,
|
||||
type,
|
||||
previousContext,
|
||||
);
|
||||
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
|
||||
|
||||
// Replace the old (or empty) context with the new one.
|
||||
// It is important to unwind the context in the reverse order.
|
||||
pop(didPerformWorkStackCursor, workInProgress);
|
||||
pop(contextStackCursor, workInProgress);
|
||||
// Now push the new context and mark that it has changed.
|
||||
push(contextStackCursor, mergedContext, workInProgress);
|
||||
push(didPerformWorkStackCursor, didChange, workInProgress);
|
||||
} else {
|
||||
pop(didPerformWorkStackCursor, workInProgress);
|
||||
push(didPerformWorkStackCursor, didChange, workInProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findCurrentUnmaskedContext(fiber: Fiber): Object {
|
||||
if (disableLegacyContext) {
|
||||
return emptyContextObject;
|
||||
} else {
|
||||
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
|
||||
// makes sense elsewhere
|
||||
if (!isFiberMounted(fiber) || fiber.tag !== ClassComponent) {
|
||||
throw new Error(
|
||||
'Expected subtree parent to be a mounted class component. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
let node: Fiber = fiber;
|
||||
do {
|
||||
switch (node.tag) {
|
||||
case HostRoot:
|
||||
return node.stateNode.context;
|
||||
case ClassComponent: {
|
||||
const Component = node.type;
|
||||
if (isContextProvider(Component)) {
|
||||
return node.stateNode.__reactInternalMemoizedMergedChildContext;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// $FlowFixMe[incompatible-type] we bail out when we get a null
|
||||
node = node.return;
|
||||
} while (node !== null);
|
||||
|
||||
throw new Error(
|
||||
'Found unexpected detached subtree parent. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getUnmaskedContext,
|
||||
cacheContext,
|
||||
getMaskedContext,
|
||||
hasContextChanged,
|
||||
popContext,
|
||||
popTopLevelContextObject,
|
||||
pushTopLevelContextObject,
|
||||
processChildContext,
|
||||
isContextProvider,
|
||||
pushContextProvider,
|
||||
invalidateContextProvider,
|
||||
findCurrentUnmaskedContext,
|
||||
};
|
|
@ -1,540 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {Lane, Lanes} from './ReactFiberLane.new';
|
||||
import type {Fiber, FiberRoot} from './ReactInternalTypes';
|
||||
import type {ReactNodeList, Wakeable} from 'shared/ReactTypes';
|
||||
import type {EventPriority} from './ReactEventPriorities.new';
|
||||
// import type {DevToolsProfilingHooks} from 'react-devtools-shared/src/backend/types';
|
||||
// TODO: This import doesn't work because the DevTools depend on the DOM version of React
|
||||
// and to properly type check against DOM React we can't also type check again non-DOM
|
||||
// React which this hook might be in.
|
||||
type DevToolsProfilingHooks = any;
|
||||
|
||||
import {
|
||||
getLabelForLane,
|
||||
TotalLanes,
|
||||
} from 'react-reconciler/src/ReactFiberLane.new';
|
||||
import {DidCapture} from './ReactFiberFlags';
|
||||
import {
|
||||
consoleManagedByDevToolsDuringStrictMode,
|
||||
enableProfilerTimer,
|
||||
enableSchedulingProfiler,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
DiscreteEventPriority,
|
||||
ContinuousEventPriority,
|
||||
DefaultEventPriority,
|
||||
IdleEventPriority,
|
||||
} from './ReactEventPriorities.new';
|
||||
import {
|
||||
ImmediatePriority as ImmediateSchedulerPriority,
|
||||
UserBlockingPriority as UserBlockingSchedulerPriority,
|
||||
NormalPriority as NormalSchedulerPriority,
|
||||
IdlePriority as IdleSchedulerPriority,
|
||||
unstable_yieldValue,
|
||||
unstable_setDisableYieldValue,
|
||||
} from './Scheduler';
|
||||
import {setSuppressWarning} from 'shared/consoleWithStackDev';
|
||||
import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
|
||||
|
||||
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
|
||||
|
||||
let rendererID = null;
|
||||
let injectedHook = null;
|
||||
let injectedProfilingHooks: DevToolsProfilingHooks | null = null;
|
||||
let hasLoggedError = false;
|
||||
|
||||
export const isDevToolsPresent =
|
||||
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined';
|
||||
|
||||
export function injectInternals(internals: Object): boolean {
|
||||
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
|
||||
// No DevTools
|
||||
return false;
|
||||
}
|
||||
const hook = __REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
if (hook.isDisabled) {
|
||||
// This isn't a real property on the hook, but it can be set to opt out
|
||||
// of DevTools integration and associated warnings and logs.
|
||||
// https://github.com/facebook/react/issues/3877
|
||||
return true;
|
||||
}
|
||||
if (!hook.supportsFiber) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'The installed version of React DevTools is too old and will not work ' +
|
||||
'with the current version of React. Please update React DevTools. ' +
|
||||
'https://reactjs.org/link/react-devtools',
|
||||
);
|
||||
}
|
||||
// DevTools exists, even though it doesn't support Fiber.
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if (enableSchedulingProfiler) {
|
||||
// Conditionally inject these hooks only if Timeline profiler is supported by this build.
|
||||
// This gives DevTools a way to feature detect that isn't tied to version number
|
||||
// (since profiling and timeline are controlled by different feature flags).
|
||||
internals = {
|
||||
...internals,
|
||||
getLaneLabelMap,
|
||||
injectProfilingHooks,
|
||||
};
|
||||
}
|
||||
|
||||
rendererID = hook.inject(internals);
|
||||
|
||||
// We have successfully injected, so now it is safe to set up hooks.
|
||||
injectedHook = hook;
|
||||
} catch (err) {
|
||||
// Catch all errors because it is unsafe to throw during initialization.
|
||||
if (__DEV__) {
|
||||
console.error('React instrumentation encountered an error: %s.', err);
|
||||
}
|
||||
}
|
||||
if (hook.checkDCE) {
|
||||
// This is the real DevTools.
|
||||
return true;
|
||||
} else {
|
||||
// This is likely a hook installed by Fast Refresh runtime.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function onScheduleRoot(root: FiberRoot, children: ReactNodeList) {
|
||||
if (__DEV__) {
|
||||
if (
|
||||
injectedHook &&
|
||||
typeof injectedHook.onScheduleFiberRoot === 'function'
|
||||
) {
|
||||
try {
|
||||
injectedHook.onScheduleFiberRoot(rendererID, root, children);
|
||||
} catch (err) {
|
||||
if (__DEV__ && !hasLoggedError) {
|
||||
hasLoggedError = true;
|
||||
console.error('React instrumentation encountered an error: %s', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function onCommitRoot(root: FiberRoot, eventPriority: EventPriority) {
|
||||
if (injectedHook && typeof injectedHook.onCommitFiberRoot === 'function') {
|
||||
try {
|
||||
const didError = (root.current.flags & DidCapture) === DidCapture;
|
||||
if (enableProfilerTimer) {
|
||||
let schedulerPriority;
|
||||
switch (eventPriority) {
|
||||
case DiscreteEventPriority:
|
||||
schedulerPriority = ImmediateSchedulerPriority;
|
||||
break;
|
||||
case ContinuousEventPriority:
|
||||
schedulerPriority = UserBlockingSchedulerPriority;
|
||||
break;
|
||||
case DefaultEventPriority:
|
||||
schedulerPriority = NormalSchedulerPriority;
|
||||
break;
|
||||
case IdleEventPriority:
|
||||
schedulerPriority = IdleSchedulerPriority;
|
||||
break;
|
||||
default:
|
||||
schedulerPriority = NormalSchedulerPriority;
|
||||
break;
|
||||
}
|
||||
injectedHook.onCommitFiberRoot(
|
||||
rendererID,
|
||||
root,
|
||||
schedulerPriority,
|
||||
didError,
|
||||
);
|
||||
} else {
|
||||
injectedHook.onCommitFiberRoot(rendererID, root, undefined, didError);
|
||||
}
|
||||
} catch (err) {
|
||||
if (__DEV__) {
|
||||
if (!hasLoggedError) {
|
||||
hasLoggedError = true;
|
||||
console.error('React instrumentation encountered an error: %s', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function onPostCommitRoot(root: FiberRoot) {
|
||||
if (
|
||||
injectedHook &&
|
||||
typeof injectedHook.onPostCommitFiberRoot === 'function'
|
||||
) {
|
||||
try {
|
||||
injectedHook.onPostCommitFiberRoot(rendererID, root);
|
||||
} catch (err) {
|
||||
if (__DEV__) {
|
||||
if (!hasLoggedError) {
|
||||
hasLoggedError = true;
|
||||
console.error('React instrumentation encountered an error: %s', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function onCommitUnmount(fiber: Fiber) {
|
||||
if (injectedHook && typeof injectedHook.onCommitFiberUnmount === 'function') {
|
||||
try {
|
||||
injectedHook.onCommitFiberUnmount(rendererID, fiber);
|
||||
} catch (err) {
|
||||
if (__DEV__) {
|
||||
if (!hasLoggedError) {
|
||||
hasLoggedError = true;
|
||||
console.error('React instrumentation encountered an error: %s', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
|
||||
if (consoleManagedByDevToolsDuringStrictMode) {
|
||||
if (typeof unstable_yieldValue === 'function') {
|
||||
// We're in a test because Scheduler.unstable_yieldValue only exists
|
||||
// in SchedulerMock. To reduce the noise in strict mode tests,
|
||||
// suppress warnings and disable scheduler yielding during the double render
|
||||
unstable_setDisableYieldValue(newIsStrictMode);
|
||||
setSuppressWarning(newIsStrictMode);
|
||||
}
|
||||
|
||||
if (injectedHook && typeof injectedHook.setStrictMode === 'function') {
|
||||
try {
|
||||
injectedHook.setStrictMode(rendererID, newIsStrictMode);
|
||||
} catch (err) {
|
||||
if (__DEV__) {
|
||||
if (!hasLoggedError) {
|
||||
hasLoggedError = true;
|
||||
console.error(
|
||||
'React instrumentation encountered an error: %s',
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (newIsStrictMode) {
|
||||
disableLogs();
|
||||
} else {
|
||||
reenableLogs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Profiler API hooks
|
||||
|
||||
function injectProfilingHooks(profilingHooks: DevToolsProfilingHooks): void {
|
||||
injectedProfilingHooks = profilingHooks;
|
||||
}
|
||||
|
||||
function getLaneLabelMap(): Map<Lane, string> | null {
|
||||
if (enableSchedulingProfiler) {
|
||||
const map: Map<Lane, string> = new Map();
|
||||
|
||||
let lane = 1;
|
||||
for (let index = 0; index < TotalLanes; index++) {
|
||||
const label = ((getLabelForLane(lane): any): string);
|
||||
map.set(lane, label);
|
||||
lane *= 2;
|
||||
}
|
||||
|
||||
return map;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function markCommitStarted(lanes: Lanes): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markCommitStarted === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markCommitStarted(lanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markCommitStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markCommitStopped === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markCommitStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentRenderStarted(fiber: Fiber): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentRenderStarted === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentRenderStarted(fiber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentRenderStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentRenderStopped === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentRenderStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentPassiveEffectMountStarted ===
|
||||
'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentPassiveEffectMountStarted(fiber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentPassiveEffectMountStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentPassiveEffectMountStopped ===
|
||||
'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentPassiveEffectMountStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStarted ===
|
||||
'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentPassiveEffectUnmountStarted(fiber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentPassiveEffectUnmountStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStopped ===
|
||||
'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentPassiveEffectUnmountStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentLayoutEffectMountStarted ===
|
||||
'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentLayoutEffectMountStarted(fiber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentLayoutEffectMountStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentLayoutEffectMountStopped ===
|
||||
'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentLayoutEffectMountStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStarted ===
|
||||
'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentLayoutEffectUnmountStarted(fiber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentLayoutEffectUnmountStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStopped ===
|
||||
'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentLayoutEffectUnmountStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentErrored(
|
||||
fiber: Fiber,
|
||||
thrownValue: mixed,
|
||||
lanes: Lanes,
|
||||
): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentErrored === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentErrored(fiber, thrownValue, lanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentSuspended(
|
||||
fiber: Fiber,
|
||||
wakeable: Wakeable,
|
||||
lanes: Lanes,
|
||||
): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markComponentSuspended === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markComponentSuspended(fiber, wakeable, lanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markLayoutEffectsStarted(lanes: Lanes): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markLayoutEffectsStarted === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markLayoutEffectsStarted(lanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markLayoutEffectsStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markLayoutEffectsStopped === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markLayoutEffectsStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markPassiveEffectsStarted(lanes: Lanes): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markPassiveEffectsStarted === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markPassiveEffectsStarted(lanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markPassiveEffectsStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markPassiveEffectsStopped === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markPassiveEffectsStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markRenderStarted(lanes: Lanes): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markRenderStarted === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markRenderStarted(lanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markRenderYielded(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markRenderYielded === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markRenderYielded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markRenderStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markRenderStopped === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markRenderStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markRenderScheduled(lane: Lane): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markRenderScheduled === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markRenderScheduled(lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markForceUpdateScheduled === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markForceUpdateScheduled(fiber, lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (
|
||||
injectedProfilingHooks !== null &&
|
||||
typeof injectedProfilingHooks.markStateUpdateScheduled === 'function'
|
||||
) {
|
||||
injectedProfilingHooks.markStateUpdateScheduled(fiber, lane);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 './ReactInternalTypes';
|
||||
import type {StackCursor} from './ReactFiberStack.new';
|
||||
import type {Lanes} from './ReactFiberLane.new';
|
||||
|
||||
import {createCursor, push, pop} from './ReactFiberStack.new';
|
||||
|
||||
import {getRenderLanes, setRenderLanes} from './ReactFiberWorkLoop.new';
|
||||
import {NoLanes, mergeLanes} from './ReactFiberLane.new';
|
||||
|
||||
// TODO: Remove `renderLanes` context in favor of hidden context
|
||||
type HiddenContext = {
|
||||
// Represents the lanes that must be included when processing updates in
|
||||
// order to reveal the hidden content.
|
||||
// TODO: Remove `subtreeLanes` context from work loop in favor of this one.
|
||||
baseLanes: number,
|
||||
...
|
||||
};
|
||||
|
||||
// TODO: This isn't being used yet, but it's intended to replace the
|
||||
// InvisibleParentContext that is currently managed by SuspenseContext.
|
||||
export const currentTreeHiddenStackCursor: StackCursor<HiddenContext | null> = createCursor(
|
||||
null,
|
||||
);
|
||||
export const prevRenderLanesStackCursor: StackCursor<Lanes> = createCursor(
|
||||
NoLanes,
|
||||
);
|
||||
|
||||
export function pushHiddenContext(fiber: Fiber, context: HiddenContext): void {
|
||||
const prevRenderLanes = getRenderLanes();
|
||||
push(prevRenderLanesStackCursor, prevRenderLanes, fiber);
|
||||
push(currentTreeHiddenStackCursor, context, fiber);
|
||||
|
||||
// When rendering a subtree that's currently hidden, we must include all
|
||||
// lanes that would have rendered if the hidden subtree hadn't been deferred.
|
||||
// That is, in order to reveal content from hidden -> visible, we must commit
|
||||
// all the updates that we skipped when we originally hid the tree.
|
||||
setRenderLanes(mergeLanes(prevRenderLanes, context.baseLanes));
|
||||
}
|
||||
|
||||
export function reuseHiddenContextOnStack(fiber: Fiber): void {
|
||||
// This subtree is not currently hidden, so we don't need to add any lanes
|
||||
// to the render lanes. But we still need to push something to avoid a
|
||||
// context mismatch. Reuse the existing context on the stack.
|
||||
push(prevRenderLanesStackCursor, getRenderLanes(), fiber);
|
||||
push(
|
||||
currentTreeHiddenStackCursor,
|
||||
currentTreeHiddenStackCursor.current,
|
||||
fiber,
|
||||
);
|
||||
}
|
||||
|
||||
export function popHiddenContext(fiber: Fiber): void {
|
||||
// Restore the previous render lanes from the stack
|
||||
setRenderLanes(prevRenderLanesStackCursor.current);
|
||||
|
||||
pop(currentTreeHiddenStackCursor, fiber);
|
||||
pop(prevRenderLanesStackCursor, fiber);
|
||||
}
|
||||
|
||||
export function isCurrentTreeHidden(): boolean {
|
||||
return currentTreeHiddenStackCursor.current !== null;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -31,7 +31,6 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
|
|||
import {
|
||||
enableDebugTracing,
|
||||
enableSchedulingProfiler,
|
||||
enableNewReconciler,
|
||||
enableCache,
|
||||
enableUseRefAccessWarning,
|
||||
enableLazyContextPropagation,
|
||||
|
@ -2758,8 +2757,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
|
|||
useMutableSource: throwInvalidHookError,
|
||||
useSyncExternalStore: throwInvalidHookError,
|
||||
useId: throwInvalidHookError,
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(ContextOnlyDispatcher: Dispatcher).useCacheRefresh = throwInvalidHookError;
|
||||
|
@ -2793,8 +2790,6 @@ const HooksDispatcherOnMount: Dispatcher = {
|
|||
useMutableSource: mountMutableSource,
|
||||
useSyncExternalStore: mountSyncExternalStore,
|
||||
useId: mountId,
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
// $FlowFixMe[escaped-generic] discovered when updating Flow
|
||||
|
@ -2828,8 +2823,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
|
|||
useMutableSource: updateMutableSource,
|
||||
useSyncExternalStore: updateSyncExternalStore,
|
||||
useId: updateId,
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).useCacheRefresh = updateRefresh;
|
||||
|
@ -2863,8 +2856,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
|
|||
useMutableSource: updateMutableSource,
|
||||
useSyncExternalStore: updateSyncExternalStore,
|
||||
useId: updateId,
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).useCacheRefresh = updateRefresh;
|
||||
|
@ -3041,8 +3032,6 @@ if (__DEV__) {
|
|||
mountHookTypesDev();
|
||||
return mountId();
|
||||
},
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
|
||||
|
@ -3198,8 +3187,6 @@ if (__DEV__) {
|
|||
updateHookTypesDev();
|
||||
return mountId();
|
||||
},
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
|
||||
|
@ -3355,8 +3342,6 @@ if (__DEV__) {
|
|||
updateHookTypesDev();
|
||||
return updateId();
|
||||
},
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
|
||||
|
@ -3513,8 +3498,6 @@ if (__DEV__) {
|
|||
updateHookTypesDev();
|
||||
return updateId();
|
||||
},
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(HooksDispatcherOnRerenderInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
|
||||
|
@ -3687,8 +3670,6 @@ if (__DEV__) {
|
|||
mountHookTypesDev();
|
||||
return mountId();
|
||||
},
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
|
||||
|
@ -3872,8 +3853,6 @@ if (__DEV__) {
|
|||
updateHookTypesDev();
|
||||
return updateId();
|
||||
},
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
|
||||
|
@ -4058,8 +4037,6 @@ if (__DEV__) {
|
|||
updateHookTypesDev();
|
||||
return updateId();
|
||||
},
|
||||
|
||||
unstable_isNewReconciler: enableNewReconciler,
|
||||
};
|
||||
if (enableCache) {
|
||||
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {Container, HostContext} from './ReactFiberHostConfig';
|
||||
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
getCurrentRootHostContainer as getCurrentRootHostContainer_old,
|
||||
getHostContext as getHostContext_old,
|
||||
} from './ReactFiberHostContext.old';
|
||||
|
||||
import {
|
||||
getCurrentRootHostContainer as getCurrentRootHostContainer_new,
|
||||
getHostContext as getHostContext_new,
|
||||
} from './ReactFiberHostContext.new';
|
||||
|
||||
export function getCurrentRootHostContainer(): null | Container {
|
||||
return enableNewReconciler
|
||||
? getCurrentRootHostContainer_new()
|
||||
: getCurrentRootHostContainer_old();
|
||||
}
|
||||
|
||||
export function getHostContext(): HostContext {
|
||||
return enableNewReconciler ? getHostContext_new() : getHostContext_old();
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 './ReactInternalTypes';
|
||||
import type {StackCursor} from './ReactFiberStack.new';
|
||||
import type {Container, HostContext} from './ReactFiberHostConfig';
|
||||
|
||||
import {getChildHostContext, getRootHostContext} from './ReactFiberHostConfig';
|
||||
import {createCursor, push, pop} from './ReactFiberStack.new';
|
||||
|
||||
const contextStackCursor: StackCursor<HostContext | null> = createCursor(null);
|
||||
const contextFiberStackCursor: StackCursor<Fiber | null> = createCursor(null);
|
||||
const rootInstanceStackCursor: StackCursor<Container | null> = createCursor(
|
||||
null,
|
||||
);
|
||||
|
||||
function requiredContext<Value>(c: Value | null): Value {
|
||||
if (__DEV__) {
|
||||
if (c === null) {
|
||||
console.error(
|
||||
'Expected host context to exist. This error is likely caused by a bug ' +
|
||||
'in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
}
|
||||
return (c: any);
|
||||
}
|
||||
|
||||
function getCurrentRootHostContainer(): null | Container {
|
||||
return rootInstanceStackCursor.current;
|
||||
}
|
||||
|
||||
function getRootHostContainer(): Container {
|
||||
const rootInstance = requiredContext(rootInstanceStackCursor.current);
|
||||
return rootInstance;
|
||||
}
|
||||
|
||||
function pushHostContainer(fiber: Fiber, nextRootInstance: Container) {
|
||||
// Push current root instance onto the stack;
|
||||
// This allows us to reset root when portals are popped.
|
||||
push(rootInstanceStackCursor, nextRootInstance, fiber);
|
||||
// Track the context and the Fiber that provided it.
|
||||
// This enables us to pop only Fibers that provide unique contexts.
|
||||
push(contextFiberStackCursor, fiber, fiber);
|
||||
|
||||
// Finally, we need to push the host context to the stack.
|
||||
// However, we can't just call getRootHostContext() and push it because
|
||||
// we'd have a different number of entries on the stack depending on
|
||||
// whether getRootHostContext() throws somewhere in renderer code or not.
|
||||
// So we push an empty value first. This lets us safely unwind on errors.
|
||||
push(contextStackCursor, null, fiber);
|
||||
const nextRootContext = getRootHostContext(nextRootInstance);
|
||||
// Now that we know this function doesn't throw, replace it.
|
||||
pop(contextStackCursor, fiber);
|
||||
push(contextStackCursor, nextRootContext, fiber);
|
||||
}
|
||||
|
||||
function popHostContainer(fiber: Fiber) {
|
||||
pop(contextStackCursor, fiber);
|
||||
pop(contextFiberStackCursor, fiber);
|
||||
pop(rootInstanceStackCursor, fiber);
|
||||
}
|
||||
|
||||
function getHostContext(): HostContext {
|
||||
const context = requiredContext(contextStackCursor.current);
|
||||
return context;
|
||||
}
|
||||
|
||||
function pushHostContext(fiber: Fiber): void {
|
||||
const context: HostContext = requiredContext(contextStackCursor.current);
|
||||
const nextContext = getChildHostContext(context, fiber.type);
|
||||
|
||||
// Don't push this Fiber's context unless it's unique.
|
||||
if (context === nextContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track the context and the Fiber that provided it.
|
||||
// This enables us to pop only Fibers that provide unique contexts.
|
||||
push(contextFiberStackCursor, fiber, fiber);
|
||||
push(contextStackCursor, nextContext, fiber);
|
||||
}
|
||||
|
||||
function popHostContext(fiber: Fiber): void {
|
||||
// Do not pop unless this Fiber provided the current context.
|
||||
// pushHostContext() only pushes Fibers that provide unique contexts.
|
||||
if (contextFiberStackCursor.current !== fiber) {
|
||||
return;
|
||||
}
|
||||
|
||||
pop(contextStackCursor, fiber);
|
||||
pop(contextFiberStackCursor, fiber);
|
||||
}
|
||||
|
||||
export {
|
||||
getHostContext,
|
||||
getCurrentRootHostContainer,
|
||||
getRootHostContainer,
|
||||
popHostContainer,
|
||||
popHostContext,
|
||||
pushHostContainer,
|
||||
pushHostContext,
|
||||
};
|
|
@ -1,93 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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, FiberRoot} from './ReactInternalTypes';
|
||||
import type {ReactElement} from '../../shared/ReactElementType';
|
||||
import type {Instance} from './ReactFiberHostConfig';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
|
||||
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
setRefreshHandler as setRefreshHandler_old,
|
||||
resolveFunctionForHotReloading as resolveFunctionForHotReloading_old,
|
||||
resolveClassForHotReloading as resolveClassForHotReloading_old,
|
||||
resolveForwardRefForHotReloading as resolveForwardRefForHotReloading_old,
|
||||
isCompatibleFamilyForHotReloading as isCompatibleFamilyForHotReloading_old,
|
||||
markFailedErrorBoundaryForHotReloading as markFailedErrorBoundaryForHotReloading_old,
|
||||
scheduleRefresh as scheduleRefresh_old,
|
||||
scheduleRoot as scheduleRoot_old,
|
||||
findHostInstancesForRefresh as findHostInstancesForRefresh_old,
|
||||
} from './ReactFiberHotReloading.old';
|
||||
|
||||
import {
|
||||
setRefreshHandler as setRefreshHandler_new,
|
||||
resolveFunctionForHotReloading as resolveFunctionForHotReloading_new,
|
||||
resolveClassForHotReloading as resolveClassForHotReloading_new,
|
||||
resolveForwardRefForHotReloading as resolveForwardRefForHotReloading_new,
|
||||
isCompatibleFamilyForHotReloading as isCompatibleFamilyForHotReloading_new,
|
||||
markFailedErrorBoundaryForHotReloading as markFailedErrorBoundaryForHotReloading_new,
|
||||
scheduleRefresh as scheduleRefresh_new,
|
||||
scheduleRoot as scheduleRoot_new,
|
||||
findHostInstancesForRefresh as findHostInstancesForRefresh_new,
|
||||
} from './ReactFiberHotReloading.new';
|
||||
|
||||
export type Family = {
|
||||
current: any,
|
||||
};
|
||||
|
||||
export type RefreshUpdate = {
|
||||
staleFamilies: Set<Family>,
|
||||
updatedFamilies: Set<Family>,
|
||||
};
|
||||
|
||||
// Resolves type to a family.
|
||||
export type RefreshHandler = any => Family | void;
|
||||
|
||||
// Used by React Refresh runtime through DevTools Global Hook.
|
||||
export type SetRefreshHandler = (handler: RefreshHandler | null) => void;
|
||||
export type ScheduleRefresh = (root: FiberRoot, update: RefreshUpdate) => void;
|
||||
export type ScheduleRoot = (root: FiberRoot, element: ReactNodeList) => void;
|
||||
export type FindHostInstancesForRefresh = (
|
||||
root: FiberRoot,
|
||||
families: Array<Family>,
|
||||
) => Set<Instance>;
|
||||
|
||||
export const setRefreshHandler: (
|
||||
handler: RefreshHandler | null,
|
||||
) => void = enableNewReconciler ? setRefreshHandler_new : setRefreshHandler_old;
|
||||
export const resolveFunctionForHotReloading: typeof resolveFunctionForHotReloading_new = enableNewReconciler
|
||||
? resolveFunctionForHotReloading_new
|
||||
: resolveFunctionForHotReloading_old;
|
||||
export const resolveClassForHotReloading: typeof resolveClassForHotReloading_new = enableNewReconciler
|
||||
? resolveClassForHotReloading_new
|
||||
: resolveClassForHotReloading_old;
|
||||
export const resolveForwardRefForHotReloading: typeof resolveForwardRefForHotReloading_new = enableNewReconciler
|
||||
? resolveForwardRefForHotReloading_new
|
||||
: resolveForwardRefForHotReloading_old;
|
||||
export const isCompatibleFamilyForHotReloading: (
|
||||
fiber: Fiber,
|
||||
element: ReactElement,
|
||||
) => boolean = enableNewReconciler
|
||||
? isCompatibleFamilyForHotReloading_new
|
||||
: isCompatibleFamilyForHotReloading_old;
|
||||
export const markFailedErrorBoundaryForHotReloading: (
|
||||
fiber: Fiber,
|
||||
) => void = enableNewReconciler
|
||||
? markFailedErrorBoundaryForHotReloading_new
|
||||
: markFailedErrorBoundaryForHotReloading_old;
|
||||
export const scheduleRefresh: ScheduleRefresh = enableNewReconciler
|
||||
? scheduleRefresh_new
|
||||
: scheduleRefresh_old;
|
||||
export const scheduleRoot: ScheduleRoot = enableNewReconciler
|
||||
? scheduleRoot_new
|
||||
: scheduleRoot_old;
|
||||
export const findHostInstancesForRefresh: FindHostInstancesForRefresh = enableNewReconciler
|
||||
? findHostInstancesForRefresh_new
|
||||
: findHostInstancesForRefresh_old;
|
|
@ -1,487 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/* eslint-disable react-internal/prod-error-codes */
|
||||
|
||||
import type {ReactElement} from 'shared/ReactElementType';
|
||||
import type {Fiber, FiberRoot} from './ReactInternalTypes';
|
||||
import type {Instance} from './ReactFiberHostConfig';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
|
||||
import type {
|
||||
Family,
|
||||
FindHostInstancesForRefresh,
|
||||
RefreshHandler,
|
||||
RefreshUpdate,
|
||||
ScheduleRefresh,
|
||||
ScheduleRoot,
|
||||
} from './ReactFiberHotReloading';
|
||||
|
||||
import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
flushSync,
|
||||
scheduleUpdateOnFiber,
|
||||
flushPassiveEffects,
|
||||
} from './ReactFiberWorkLoop.new';
|
||||
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new';
|
||||
import {updateContainer} from './ReactFiberReconciler.new';
|
||||
import {emptyContextObject} from './ReactFiberContext.new';
|
||||
import {SyncLane, NoTimestamp} from './ReactFiberLane.new';
|
||||
import {
|
||||
ClassComponent,
|
||||
FunctionComponent,
|
||||
ForwardRef,
|
||||
HostComponent,
|
||||
HostResource,
|
||||
HostSingleton,
|
||||
HostPortal,
|
||||
HostRoot,
|
||||
MemoComponent,
|
||||
SimpleMemoComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
REACT_MEMO_TYPE,
|
||||
REACT_LAZY_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {supportsSingletons} from './ReactFiberHostConfig';
|
||||
|
||||
let resolveFamily: RefreshHandler | null = null;
|
||||
let failedBoundaries: WeakSet<Fiber> | null = null;
|
||||
|
||||
export const setRefreshHandler = (handler: RefreshHandler | null): void => {
|
||||
if (__DEV__) {
|
||||
resolveFamily = handler;
|
||||
}
|
||||
};
|
||||
|
||||
export function resolveFunctionForHotReloading(type: any): any {
|
||||
if (__DEV__) {
|
||||
if (resolveFamily === null) {
|
||||
// Hot reloading is disabled.
|
||||
return type;
|
||||
}
|
||||
const family = resolveFamily(type);
|
||||
if (family === undefined) {
|
||||
return type;
|
||||
}
|
||||
// Use the latest known implementation.
|
||||
return family.current;
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveClassForHotReloading(type: any): any {
|
||||
// No implementation differences.
|
||||
return resolveFunctionForHotReloading(type);
|
||||
}
|
||||
|
||||
export function resolveForwardRefForHotReloading(type: any): any {
|
||||
if (__DEV__) {
|
||||
if (resolveFamily === null) {
|
||||
// Hot reloading is disabled.
|
||||
return type;
|
||||
}
|
||||
const family = resolveFamily(type);
|
||||
if (family === undefined) {
|
||||
// Check if we're dealing with a real forwardRef. Don't want to crash early.
|
||||
if (
|
||||
type !== null &&
|
||||
type !== undefined &&
|
||||
typeof type.render === 'function'
|
||||
) {
|
||||
// ForwardRef is special because its resolved .type is an object,
|
||||
// but it's possible that we only have its inner render function in the map.
|
||||
// If that inner render function is different, we'll build a new forwardRef type.
|
||||
const currentRender = resolveFunctionForHotReloading(type.render);
|
||||
if (type.render !== currentRender) {
|
||||
const syntheticType = {
|
||||
$$typeof: REACT_FORWARD_REF_TYPE,
|
||||
render: currentRender,
|
||||
};
|
||||
if (type.displayName !== undefined) {
|
||||
(syntheticType: any).displayName = type.displayName;
|
||||
}
|
||||
return syntheticType;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
// Use the latest known implementation.
|
||||
return family.current;
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
export function isCompatibleFamilyForHotReloading(
|
||||
fiber: Fiber,
|
||||
element: ReactElement,
|
||||
): boolean {
|
||||
if (__DEV__) {
|
||||
if (resolveFamily === null) {
|
||||
// Hot reloading is disabled.
|
||||
return false;
|
||||
}
|
||||
|
||||
const prevType = fiber.elementType;
|
||||
const nextType = element.type;
|
||||
|
||||
// If we got here, we know types aren't === equal.
|
||||
let needsCompareFamilies = false;
|
||||
|
||||
const $$typeofNextType =
|
||||
typeof nextType === 'object' && nextType !== null
|
||||
? nextType.$$typeof
|
||||
: null;
|
||||
|
||||
switch (fiber.tag) {
|
||||
case ClassComponent: {
|
||||
if (typeof nextType === 'function') {
|
||||
needsCompareFamilies = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FunctionComponent: {
|
||||
if (typeof nextType === 'function') {
|
||||
needsCompareFamilies = true;
|
||||
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
|
||||
// We don't know the inner type yet.
|
||||
// We're going to assume that the lazy inner type is stable,
|
||||
// and so it is sufficient to avoid reconciling it away.
|
||||
// We're not going to unwrap or actually use the new lazy type.
|
||||
needsCompareFamilies = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ForwardRef: {
|
||||
if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
|
||||
needsCompareFamilies = true;
|
||||
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
|
||||
needsCompareFamilies = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent: {
|
||||
if ($$typeofNextType === REACT_MEMO_TYPE) {
|
||||
// TODO: if it was but can no longer be simple,
|
||||
// we shouldn't set this.
|
||||
needsCompareFamilies = true;
|
||||
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
|
||||
needsCompareFamilies = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if both types have a family and it's the same one.
|
||||
if (needsCompareFamilies) {
|
||||
// Note: memo() and forwardRef() we'll compare outer rather than inner type.
|
||||
// This means both of them need to be registered to preserve state.
|
||||
// If we unwrapped and compared the inner types for wrappers instead,
|
||||
// then we would risk falsely saying two separate memo(Foo)
|
||||
// calls are equivalent because they wrap the same Foo function.
|
||||
const prevFamily = resolveFamily(prevType);
|
||||
// $FlowFixMe[not-a-function] found when upgrading Flow
|
||||
if (prevFamily !== undefined && prevFamily === resolveFamily(nextType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
|
||||
if (__DEV__) {
|
||||
if (resolveFamily === null) {
|
||||
// Hot reloading is disabled.
|
||||
return;
|
||||
}
|
||||
if (typeof WeakSet !== 'function') {
|
||||
return;
|
||||
}
|
||||
if (failedBoundaries === null) {
|
||||
failedBoundaries = new WeakSet();
|
||||
}
|
||||
failedBoundaries.add(fiber);
|
||||
}
|
||||
}
|
||||
|
||||
export const scheduleRefresh: ScheduleRefresh = (
|
||||
root: FiberRoot,
|
||||
update: RefreshUpdate,
|
||||
): void => {
|
||||
if (__DEV__) {
|
||||
if (resolveFamily === null) {
|
||||
// Hot reloading is disabled.
|
||||
return;
|
||||
}
|
||||
const {staleFamilies, updatedFamilies} = update;
|
||||
flushPassiveEffects();
|
||||
flushSync(() => {
|
||||
scheduleFibersWithFamiliesRecursively(
|
||||
root.current,
|
||||
updatedFamilies,
|
||||
staleFamilies,
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const scheduleRoot: ScheduleRoot = (
|
||||
root: FiberRoot,
|
||||
element: ReactNodeList,
|
||||
): void => {
|
||||
if (__DEV__) {
|
||||
if (root.context !== emptyContextObject) {
|
||||
// Super edge case: root has a legacy _renderSubtree context
|
||||
// but we don't know the parentComponent so we can't pass it.
|
||||
// Just ignore. We'll delete this with _renderSubtree code path later.
|
||||
return;
|
||||
}
|
||||
flushPassiveEffects();
|
||||
flushSync(() => {
|
||||
updateContainer(element, root, null, null);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function scheduleFibersWithFamiliesRecursively(
|
||||
fiber: Fiber,
|
||||
updatedFamilies: Set<Family>,
|
||||
staleFamilies: Set<Family>,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
const {alternate, child, sibling, tag, type} = fiber;
|
||||
|
||||
let candidateType = null;
|
||||
switch (tag) {
|
||||
case FunctionComponent:
|
||||
case SimpleMemoComponent:
|
||||
case ClassComponent:
|
||||
candidateType = type;
|
||||
break;
|
||||
case ForwardRef:
|
||||
candidateType = type.render;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (resolveFamily === null) {
|
||||
throw new Error('Expected resolveFamily to be set during hot reload.');
|
||||
}
|
||||
|
||||
let needsRender = false;
|
||||
let needsRemount = false;
|
||||
if (candidateType !== null) {
|
||||
const family = resolveFamily(candidateType);
|
||||
if (family !== undefined) {
|
||||
if (staleFamilies.has(family)) {
|
||||
needsRemount = true;
|
||||
} else if (updatedFamilies.has(family)) {
|
||||
if (tag === ClassComponent) {
|
||||
needsRemount = true;
|
||||
} else {
|
||||
needsRender = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failedBoundaries !== null) {
|
||||
if (
|
||||
failedBoundaries.has(fiber) ||
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
(alternate !== null && failedBoundaries.has(alternate))
|
||||
) {
|
||||
needsRemount = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsRemount) {
|
||||
fiber._debugNeedsRemount = true;
|
||||
}
|
||||
if (needsRemount || needsRender) {
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
||||
}
|
||||
}
|
||||
if (child !== null && !needsRemount) {
|
||||
scheduleFibersWithFamiliesRecursively(
|
||||
child,
|
||||
updatedFamilies,
|
||||
staleFamilies,
|
||||
);
|
||||
}
|
||||
if (sibling !== null) {
|
||||
scheduleFibersWithFamiliesRecursively(
|
||||
sibling,
|
||||
updatedFamilies,
|
||||
staleFamilies,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const findHostInstancesForRefresh: FindHostInstancesForRefresh = (
|
||||
root: FiberRoot,
|
||||
families: Array<Family>,
|
||||
): Set<Instance> => {
|
||||
if (__DEV__) {
|
||||
const hostInstances = new Set();
|
||||
const types = new Set(families.map(family => family.current));
|
||||
findHostInstancesForMatchingFibersRecursively(
|
||||
root.current,
|
||||
types,
|
||||
hostInstances,
|
||||
);
|
||||
return hostInstances;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Did not expect findHostInstancesForRefresh to be called in production.',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function findHostInstancesForMatchingFibersRecursively(
|
||||
fiber: Fiber,
|
||||
types: Set<any>,
|
||||
hostInstances: Set<Instance>,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
const {child, sibling, tag, type} = fiber;
|
||||
|
||||
let candidateType = null;
|
||||
switch (tag) {
|
||||
case FunctionComponent:
|
||||
case SimpleMemoComponent:
|
||||
case ClassComponent:
|
||||
candidateType = type;
|
||||
break;
|
||||
case ForwardRef:
|
||||
candidateType = type.render;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
let didMatch = false;
|
||||
if (candidateType !== null) {
|
||||
if (types.has(candidateType)) {
|
||||
didMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (didMatch) {
|
||||
// We have a match. This only drills down to the closest host components.
|
||||
// There's no need to search deeper because for the purpose of giving
|
||||
// visual feedback, "flashing" outermost parent rectangles is sufficient.
|
||||
findHostInstancesForFiberShallowly(fiber, hostInstances);
|
||||
} else {
|
||||
// If there's no match, maybe there will be one further down in the child tree.
|
||||
if (child !== null) {
|
||||
findHostInstancesForMatchingFibersRecursively(
|
||||
child,
|
||||
types,
|
||||
hostInstances,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (sibling !== null) {
|
||||
findHostInstancesForMatchingFibersRecursively(
|
||||
sibling,
|
||||
types,
|
||||
hostInstances,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findHostInstancesForFiberShallowly(
|
||||
fiber: Fiber,
|
||||
hostInstances: Set<Instance>,
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
const foundHostInstances = findChildHostInstancesForFiberShallowly(
|
||||
fiber,
|
||||
hostInstances,
|
||||
);
|
||||
if (foundHostInstances) {
|
||||
return;
|
||||
}
|
||||
// If we didn't find any host children, fallback to closest host parent.
|
||||
let node = fiber;
|
||||
while (true) {
|
||||
switch (node.tag) {
|
||||
case HostSingleton:
|
||||
case HostComponent:
|
||||
hostInstances.add(node.stateNode);
|
||||
return;
|
||||
case HostPortal:
|
||||
hostInstances.add(node.stateNode.containerInfo);
|
||||
return;
|
||||
case HostRoot:
|
||||
hostInstances.add(node.stateNode.containerInfo);
|
||||
return;
|
||||
}
|
||||
if (node.return === null) {
|
||||
throw new Error('Expected to reach root first.');
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findChildHostInstancesForFiberShallowly(
|
||||
fiber: Fiber,
|
||||
hostInstances: Set<Instance>,
|
||||
): boolean {
|
||||
if (__DEV__) {
|
||||
let node: Fiber = fiber;
|
||||
let foundHostInstances = false;
|
||||
while (true) {
|
||||
if (
|
||||
node.tag === HostComponent ||
|
||||
(enableFloat ? node.tag === HostResource : false) ||
|
||||
(enableHostSingletons && supportsSingletons
|
||||
? node.tag === HostSingleton
|
||||
: false)
|
||||
) {
|
||||
// We got a match.
|
||||
foundHostInstances = true;
|
||||
hostInstances.add(node.stateNode);
|
||||
// There may still be more, so keep searching.
|
||||
} else if (node.child !== null) {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
continue;
|
||||
}
|
||||
if (node === fiber) {
|
||||
return foundHostInstances;
|
||||
}
|
||||
while (node.sibling === null) {
|
||||
if (node.return === null || node.return === fiber) {
|
||||
return foundHostInstances;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -14,15 +14,6 @@ import type {Fiber, FiberRoot} from './ReactInternalTypes';
|
|||
import type {Instance} from './ReactFiberHostConfig';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
|
||||
import type {
|
||||
Family,
|
||||
FindHostInstancesForRefresh,
|
||||
RefreshHandler,
|
||||
RefreshUpdate,
|
||||
ScheduleRefresh,
|
||||
ScheduleRoot,
|
||||
} from './ReactFiberHotReloading';
|
||||
|
||||
import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
flushSync,
|
||||
|
@ -52,6 +43,27 @@ import {
|
|||
} from 'shared/ReactSymbols';
|
||||
import {supportsSingletons} from './ReactFiberHostConfig';
|
||||
|
||||
export type Family = {
|
||||
current: any,
|
||||
};
|
||||
|
||||
export type RefreshUpdate = {
|
||||
staleFamilies: Set<Family>,
|
||||
updatedFamilies: Set<Family>,
|
||||
};
|
||||
|
||||
// Resolves type to a family.
|
||||
type RefreshHandler = any => Family | void;
|
||||
|
||||
// Used by React Refresh runtime through DevTools Global Hook.
|
||||
export type SetRefreshHandler = (handler: RefreshHandler | null) => void;
|
||||
export type ScheduleRefresh = (root: FiberRoot, update: RefreshUpdate) => void;
|
||||
export type ScheduleRoot = (root: FiberRoot, element: ReactNodeList) => void;
|
||||
export type FindHostInstancesForRefresh = (
|
||||
root: FiberRoot,
|
||||
families: Array<Family>,
|
||||
) => Set<Instance>;
|
||||
|
||||
let resolveFamily: RefreshHandler | null = null;
|
||||
let failedBoundaries: WeakSet<Fiber> | null = null;
|
||||
|
||||
|
|
|
@ -1,771 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 './ReactInternalTypes';
|
||||
import {NoMode, ConcurrentMode} from './ReactTypeOfMode';
|
||||
import type {
|
||||
Instance,
|
||||
TextInstance,
|
||||
HydratableInstance,
|
||||
SuspenseInstance,
|
||||
Container,
|
||||
HostContext,
|
||||
} from './ReactFiberHostConfig';
|
||||
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
|
||||
import type {TreeContext} from './ReactFiberTreeContext.new';
|
||||
import type {CapturedValue} from './ReactCapturedValue';
|
||||
|
||||
import {
|
||||
HostComponent,
|
||||
HostSingleton,
|
||||
HostText,
|
||||
HostRoot,
|
||||
SuspenseComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {
|
||||
ChildDeletion,
|
||||
Placement,
|
||||
Hydrating,
|
||||
NoFlags,
|
||||
DidCapture,
|
||||
} from './ReactFiberFlags';
|
||||
import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
createFiberFromHostInstanceForDeletion,
|
||||
createFiberFromDehydratedFragment,
|
||||
} from './ReactFiber.new';
|
||||
import {
|
||||
shouldSetTextContent,
|
||||
supportsHydration,
|
||||
supportsSingletons,
|
||||
isHydratable,
|
||||
canHydrateInstance,
|
||||
canHydrateTextInstance,
|
||||
canHydrateSuspenseInstance,
|
||||
getNextHydratableSibling,
|
||||
getFirstHydratableChild,
|
||||
getFirstHydratableChildWithinContainer,
|
||||
getFirstHydratableChildWithinSuspenseInstance,
|
||||
hydrateInstance,
|
||||
hydrateTextInstance,
|
||||
hydrateSuspenseInstance,
|
||||
getNextHydratableInstanceAfterSuspenseInstance,
|
||||
shouldDeleteUnhydratedTailInstances,
|
||||
didNotMatchHydratedContainerTextInstance,
|
||||
didNotMatchHydratedTextInstance,
|
||||
didNotHydrateInstanceWithinContainer,
|
||||
didNotHydrateInstanceWithinSuspenseInstance,
|
||||
didNotHydrateInstance,
|
||||
didNotFindHydratableInstanceWithinContainer,
|
||||
didNotFindHydratableTextInstanceWithinContainer,
|
||||
didNotFindHydratableSuspenseInstanceWithinContainer,
|
||||
didNotFindHydratableInstanceWithinSuspenseInstance,
|
||||
didNotFindHydratableTextInstanceWithinSuspenseInstance,
|
||||
didNotFindHydratableSuspenseInstanceWithinSuspenseInstance,
|
||||
didNotFindHydratableInstance,
|
||||
didNotFindHydratableTextInstance,
|
||||
didNotFindHydratableSuspenseInstance,
|
||||
resolveSingletonInstance,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {OffscreenLane} from './ReactFiberLane.new';
|
||||
import {
|
||||
getSuspendedTreeContext,
|
||||
restoreSuspendedTreeContext,
|
||||
} from './ReactFiberTreeContext.new';
|
||||
import {queueRecoverableErrors} from './ReactFiberWorkLoop.new';
|
||||
import {
|
||||
getRootHostContainer,
|
||||
getHostContext,
|
||||
} from './ReactFiberHostContext.new';
|
||||
|
||||
// The deepest Fiber on the stack involved in a hydration context.
|
||||
// This may have been an insertion or a hydration.
|
||||
let hydrationParentFiber: null | Fiber = null;
|
||||
let nextHydratableInstance: null | HydratableInstance = null;
|
||||
let isHydrating: boolean = false;
|
||||
|
||||
// This flag allows for warning supression when we expect there to be mismatches
|
||||
// due to earlier mismatches or a suspended fiber.
|
||||
let didSuspendOrErrorDEV: boolean = false;
|
||||
|
||||
// Hydration errors that were thrown inside this boundary
|
||||
let hydrationErrors: Array<CapturedValue<mixed>> | null = null;
|
||||
|
||||
function warnIfHydrating() {
|
||||
if (__DEV__) {
|
||||
if (isHydrating) {
|
||||
console.error(
|
||||
'We should not be hydrating here. This is a bug in React. Please file a bug.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markDidThrowWhileHydratingDEV() {
|
||||
if (__DEV__) {
|
||||
didSuspendOrErrorDEV = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function didSuspendOrErrorWhileHydratingDEV(): boolean {
|
||||
if (__DEV__) {
|
||||
return didSuspendOrErrorDEV;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function enterHydrationState(fiber: Fiber): boolean {
|
||||
if (!supportsHydration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parentInstance: Container = fiber.stateNode.containerInfo;
|
||||
nextHydratableInstance = getFirstHydratableChildWithinContainer(
|
||||
parentInstance,
|
||||
);
|
||||
hydrationParentFiber = fiber;
|
||||
isHydrating = true;
|
||||
hydrationErrors = null;
|
||||
didSuspendOrErrorDEV = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function reenterHydrationStateFromDehydratedSuspenseInstance(
|
||||
fiber: Fiber,
|
||||
suspenseInstance: SuspenseInstance,
|
||||
treeContext: TreeContext | null,
|
||||
): boolean {
|
||||
if (!supportsHydration) {
|
||||
return false;
|
||||
}
|
||||
nextHydratableInstance = getFirstHydratableChildWithinSuspenseInstance(
|
||||
suspenseInstance,
|
||||
);
|
||||
hydrationParentFiber = fiber;
|
||||
isHydrating = true;
|
||||
hydrationErrors = null;
|
||||
didSuspendOrErrorDEV = false;
|
||||
if (treeContext !== null) {
|
||||
restoreSuspendedTreeContext(fiber, treeContext);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function warnUnhydratedInstance(
|
||||
returnFiber: Fiber,
|
||||
instance: HydratableInstance,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
switch (returnFiber.tag) {
|
||||
case HostRoot: {
|
||||
didNotHydrateInstanceWithinContainer(
|
||||
returnFiber.stateNode.containerInfo,
|
||||
instance,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case HostSingleton:
|
||||
case HostComponent: {
|
||||
const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode;
|
||||
didNotHydrateInstance(
|
||||
returnFiber.type,
|
||||
returnFiber.memoizedProps,
|
||||
returnFiber.stateNode,
|
||||
instance,
|
||||
// TODO: Delete this argument when we remove the legacy root API.
|
||||
isConcurrentMode,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
const suspenseState: SuspenseState = returnFiber.memoizedState;
|
||||
if (suspenseState.dehydrated !== null)
|
||||
didNotHydrateInstanceWithinSuspenseInstance(
|
||||
suspenseState.dehydrated,
|
||||
instance,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteHydratableInstance(
|
||||
returnFiber: Fiber,
|
||||
instance: HydratableInstance,
|
||||
) {
|
||||
warnUnhydratedInstance(returnFiber, instance);
|
||||
const childToDelete = createFiberFromHostInstanceForDeletion();
|
||||
childToDelete.stateNode = instance;
|
||||
childToDelete.return = returnFiber;
|
||||
|
||||
const deletions = returnFiber.deletions;
|
||||
if (deletions === null) {
|
||||
returnFiber.deletions = [childToDelete];
|
||||
returnFiber.flags |= ChildDeletion;
|
||||
} else {
|
||||
deletions.push(childToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
function warnNonhydratedInstance(returnFiber: Fiber, fiber: Fiber) {
|
||||
if (__DEV__) {
|
||||
if (didSuspendOrErrorDEV) {
|
||||
// Inside a boundary that already suspended. We're currently rendering the
|
||||
// siblings of a suspended node. The mismatch may be due to the missing
|
||||
// data, so it's probably a false positive.
|
||||
return;
|
||||
}
|
||||
|
||||
switch (returnFiber.tag) {
|
||||
case HostRoot: {
|
||||
const parentContainer = returnFiber.stateNode.containerInfo;
|
||||
switch (fiber.tag) {
|
||||
case HostSingleton:
|
||||
case HostComponent:
|
||||
const type = fiber.type;
|
||||
const props = fiber.pendingProps;
|
||||
didNotFindHydratableInstanceWithinContainer(
|
||||
parentContainer,
|
||||
type,
|
||||
props,
|
||||
);
|
||||
break;
|
||||
case HostText:
|
||||
const text = fiber.pendingProps;
|
||||
didNotFindHydratableTextInstanceWithinContainer(
|
||||
parentContainer,
|
||||
text,
|
||||
);
|
||||
break;
|
||||
case SuspenseComponent:
|
||||
didNotFindHydratableSuspenseInstanceWithinContainer(
|
||||
parentContainer,
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HostSingleton:
|
||||
case HostComponent: {
|
||||
const parentType = returnFiber.type;
|
||||
const parentProps = returnFiber.memoizedProps;
|
||||
const parentInstance = returnFiber.stateNode;
|
||||
switch (fiber.tag) {
|
||||
case HostSingleton:
|
||||
case HostComponent: {
|
||||
const type = fiber.type;
|
||||
const props = fiber.pendingProps;
|
||||
const isConcurrentMode =
|
||||
(returnFiber.mode & ConcurrentMode) !== NoMode;
|
||||
didNotFindHydratableInstance(
|
||||
parentType,
|
||||
parentProps,
|
||||
parentInstance,
|
||||
type,
|
||||
props,
|
||||
// TODO: Delete this argument when we remove the legacy root API.
|
||||
isConcurrentMode,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case HostText: {
|
||||
const text = fiber.pendingProps;
|
||||
const isConcurrentMode =
|
||||
(returnFiber.mode & ConcurrentMode) !== NoMode;
|
||||
didNotFindHydratableTextInstance(
|
||||
parentType,
|
||||
parentProps,
|
||||
parentInstance,
|
||||
text,
|
||||
// TODO: Delete this argument when we remove the legacy root API.
|
||||
isConcurrentMode,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
didNotFindHydratableSuspenseInstance(
|
||||
parentType,
|
||||
parentProps,
|
||||
parentInstance,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
const suspenseState: SuspenseState = returnFiber.memoizedState;
|
||||
const parentInstance = suspenseState.dehydrated;
|
||||
if (parentInstance !== null)
|
||||
switch (fiber.tag) {
|
||||
case HostSingleton:
|
||||
case HostComponent:
|
||||
const type = fiber.type;
|
||||
const props = fiber.pendingProps;
|
||||
didNotFindHydratableInstanceWithinSuspenseInstance(
|
||||
parentInstance,
|
||||
type,
|
||||
props,
|
||||
);
|
||||
break;
|
||||
case HostText:
|
||||
const text = fiber.pendingProps;
|
||||
didNotFindHydratableTextInstanceWithinSuspenseInstance(
|
||||
parentInstance,
|
||||
text,
|
||||
);
|
||||
break;
|
||||
case SuspenseComponent:
|
||||
didNotFindHydratableSuspenseInstanceWithinSuspenseInstance(
|
||||
parentInstance,
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
|
||||
fiber.flags = (fiber.flags & ~Hydrating) | Placement;
|
||||
warnNonhydratedInstance(returnFiber, fiber);
|
||||
}
|
||||
|
||||
function tryHydrate(fiber, nextInstance) {
|
||||
switch (fiber.tag) {
|
||||
// HostSingleton is intentionally omitted. the hydration pathway for singletons is non-fallible
|
||||
// you can find it inlined in claimHydratableSingleton
|
||||
case HostComponent: {
|
||||
const type = fiber.type;
|
||||
const props = fiber.pendingProps;
|
||||
const instance = canHydrateInstance(nextInstance, type, props);
|
||||
if (instance !== null) {
|
||||
fiber.stateNode = (instance: Instance);
|
||||
hydrationParentFiber = fiber;
|
||||
nextHydratableInstance = getFirstHydratableChild(instance);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case HostText: {
|
||||
const text = fiber.pendingProps;
|
||||
const textInstance = canHydrateTextInstance(nextInstance, text);
|
||||
if (textInstance !== null) {
|
||||
fiber.stateNode = (textInstance: TextInstance);
|
||||
hydrationParentFiber = fiber;
|
||||
// Text Instances don't have children so there's nothing to hydrate.
|
||||
nextHydratableInstance = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
const suspenseInstance: null | SuspenseInstance = canHydrateSuspenseInstance(
|
||||
nextInstance,
|
||||
);
|
||||
if (suspenseInstance !== null) {
|
||||
const suspenseState: SuspenseState = {
|
||||
dehydrated: suspenseInstance,
|
||||
treeContext: getSuspendedTreeContext(),
|
||||
retryLane: OffscreenLane,
|
||||
};
|
||||
fiber.memoizedState = suspenseState;
|
||||
// Store the dehydrated fragment as a child fiber.
|
||||
// This simplifies the code for getHostSibling and deleting nodes,
|
||||
// since it doesn't have to consider all Suspense boundaries and
|
||||
// check if they're dehydrated ones or not.
|
||||
const dehydratedFragment = createFiberFromDehydratedFragment(
|
||||
suspenseInstance,
|
||||
);
|
||||
dehydratedFragment.return = fiber;
|
||||
fiber.child = dehydratedFragment;
|
||||
hydrationParentFiber = fiber;
|
||||
// While a Suspense Instance does have children, we won't step into
|
||||
// it during the first pass. Instead, we'll reenter it later.
|
||||
nextHydratableInstance = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldClientRenderOnMismatch(fiber: Fiber) {
|
||||
return (
|
||||
(fiber.mode & ConcurrentMode) !== NoMode &&
|
||||
(fiber.flags & DidCapture) === NoFlags
|
||||
);
|
||||
}
|
||||
|
||||
function throwOnHydrationMismatch(fiber: Fiber) {
|
||||
throw new Error(
|
||||
'Hydration failed because the initial UI does not match what was ' +
|
||||
'rendered on the server.',
|
||||
);
|
||||
}
|
||||
|
||||
function claimHydratableSingleton(fiber: Fiber): void {
|
||||
if (enableHostSingletons && supportsSingletons) {
|
||||
if (!isHydrating) {
|
||||
return;
|
||||
}
|
||||
const currentRootContainer = getRootHostContainer();
|
||||
const currentHostContext = getHostContext();
|
||||
const instance = (fiber.stateNode = resolveSingletonInstance(
|
||||
fiber.type,
|
||||
fiber.pendingProps,
|
||||
currentRootContainer,
|
||||
currentHostContext,
|
||||
false,
|
||||
));
|
||||
hydrationParentFiber = fiber;
|
||||
nextHydratableInstance = getFirstHydratableChild(instance);
|
||||
}
|
||||
}
|
||||
|
||||
function tryToClaimNextHydratableInstance(fiber: Fiber): void {
|
||||
if (!isHydrating) {
|
||||
return;
|
||||
}
|
||||
if (enableFloat && !isHydratable(fiber.type, fiber.pendingProps)) {
|
||||
// This fiber never hydrates from the DOM and always does an insert
|
||||
fiber.flags = (fiber.flags & ~Hydrating) | Placement;
|
||||
isHydrating = false;
|
||||
hydrationParentFiber = fiber;
|
||||
return;
|
||||
}
|
||||
let nextInstance = nextHydratableInstance;
|
||||
if (!nextInstance) {
|
||||
if (shouldClientRenderOnMismatch(fiber)) {
|
||||
warnNonhydratedInstance((hydrationParentFiber: any), fiber);
|
||||
throwOnHydrationMismatch(fiber);
|
||||
}
|
||||
// Nothing to hydrate. Make it an insertion.
|
||||
insertNonHydratedInstance((hydrationParentFiber: any), fiber);
|
||||
isHydrating = false;
|
||||
hydrationParentFiber = fiber;
|
||||
return;
|
||||
}
|
||||
const firstAttemptedInstance = nextInstance;
|
||||
if (!tryHydrate(fiber, nextInstance)) {
|
||||
if (shouldClientRenderOnMismatch(fiber)) {
|
||||
warnNonhydratedInstance((hydrationParentFiber: any), fiber);
|
||||
throwOnHydrationMismatch(fiber);
|
||||
}
|
||||
// If we can't hydrate this instance let's try the next one.
|
||||
// We use this as a heuristic. It's based on intuition and not data so it
|
||||
// might be flawed or unnecessary.
|
||||
nextInstance = getNextHydratableSibling(firstAttemptedInstance);
|
||||
const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any);
|
||||
if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
|
||||
// Nothing to hydrate. Make it an insertion.
|
||||
insertNonHydratedInstance((hydrationParentFiber: any), fiber);
|
||||
isHydrating = false;
|
||||
hydrationParentFiber = fiber;
|
||||
return;
|
||||
}
|
||||
// We matched the next one, we'll now assume that the first one was
|
||||
// superfluous and we'll delete it. Since we can't eagerly delete it
|
||||
// we'll have to schedule a deletion. To do that, this node needs a dummy
|
||||
// fiber associated with it.
|
||||
deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance);
|
||||
}
|
||||
}
|
||||
|
||||
function prepareToHydrateHostInstance(
|
||||
fiber: Fiber,
|
||||
hostContext: HostContext,
|
||||
): boolean {
|
||||
if (!supportsHydration) {
|
||||
throw new Error(
|
||||
'Expected prepareToHydrateHostInstance() to never be called. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
const instance: Instance = fiber.stateNode;
|
||||
const shouldWarnIfMismatchDev = !didSuspendOrErrorDEV;
|
||||
const updatePayload = hydrateInstance(
|
||||
instance,
|
||||
fiber.type,
|
||||
fiber.memoizedProps,
|
||||
hostContext,
|
||||
fiber,
|
||||
shouldWarnIfMismatchDev,
|
||||
);
|
||||
// TODO: Type this specific to this type of component.
|
||||
fiber.updateQueue = (updatePayload: any);
|
||||
// If the update payload indicates that there is a change or if there
|
||||
// is a new ref we mark this as an update.
|
||||
if (updatePayload !== null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
|
||||
if (!supportsHydration) {
|
||||
throw new Error(
|
||||
'Expected prepareToHydrateHostTextInstance() to never be called. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
const textInstance: TextInstance = fiber.stateNode;
|
||||
const textContent: string = fiber.memoizedProps;
|
||||
const shouldWarnIfMismatchDev = !didSuspendOrErrorDEV;
|
||||
const shouldUpdate = hydrateTextInstance(
|
||||
textInstance,
|
||||
textContent,
|
||||
fiber,
|
||||
shouldWarnIfMismatchDev,
|
||||
);
|
||||
if (shouldUpdate) {
|
||||
// We assume that prepareToHydrateHostTextInstance is called in a context where the
|
||||
// hydration parent is the parent host component of this host text.
|
||||
const returnFiber = hydrationParentFiber;
|
||||
if (returnFiber !== null) {
|
||||
switch (returnFiber.tag) {
|
||||
case HostRoot: {
|
||||
const parentContainer = returnFiber.stateNode.containerInfo;
|
||||
const isConcurrentMode =
|
||||
(returnFiber.mode & ConcurrentMode) !== NoMode;
|
||||
didNotMatchHydratedContainerTextInstance(
|
||||
parentContainer,
|
||||
textInstance,
|
||||
textContent,
|
||||
// TODO: Delete this argument when we remove the legacy root API.
|
||||
isConcurrentMode,
|
||||
shouldWarnIfMismatchDev,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case HostSingleton:
|
||||
case HostComponent: {
|
||||
const parentType = returnFiber.type;
|
||||
const parentProps = returnFiber.memoizedProps;
|
||||
const parentInstance = returnFiber.stateNode;
|
||||
const isConcurrentMode =
|
||||
(returnFiber.mode & ConcurrentMode) !== NoMode;
|
||||
didNotMatchHydratedTextInstance(
|
||||
parentType,
|
||||
parentProps,
|
||||
parentInstance,
|
||||
textInstance,
|
||||
textContent,
|
||||
// TODO: Delete this argument when we remove the legacy root API.
|
||||
isConcurrentMode,
|
||||
shouldWarnIfMismatchDev,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return shouldUpdate;
|
||||
}
|
||||
|
||||
function prepareToHydrateHostSuspenseInstance(fiber: Fiber): void {
|
||||
if (!supportsHydration) {
|
||||
throw new Error(
|
||||
'Expected prepareToHydrateHostSuspenseInstance() to never be called. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
const suspenseState: null | SuspenseState = fiber.memoizedState;
|
||||
const suspenseInstance: null | SuspenseInstance =
|
||||
suspenseState !== null ? suspenseState.dehydrated : null;
|
||||
|
||||
if (!suspenseInstance) {
|
||||
throw new Error(
|
||||
'Expected to have a hydrated suspense instance. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
hydrateSuspenseInstance(suspenseInstance, fiber);
|
||||
}
|
||||
|
||||
function skipPastDehydratedSuspenseInstance(
|
||||
fiber: Fiber,
|
||||
): null | HydratableInstance {
|
||||
if (!supportsHydration) {
|
||||
throw new Error(
|
||||
'Expected skipPastDehydratedSuspenseInstance() to never be called. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
const suspenseState: null | SuspenseState = fiber.memoizedState;
|
||||
const suspenseInstance: null | SuspenseInstance =
|
||||
suspenseState !== null ? suspenseState.dehydrated : null;
|
||||
|
||||
if (!suspenseInstance) {
|
||||
throw new Error(
|
||||
'Expected to have a hydrated suspense instance. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
return getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance);
|
||||
}
|
||||
|
||||
function popToNextHostParent(fiber: Fiber): void {
|
||||
let parent = fiber.return;
|
||||
while (
|
||||
parent !== null &&
|
||||
parent.tag !== HostComponent &&
|
||||
parent.tag !== HostRoot &&
|
||||
parent.tag !== SuspenseComponent &&
|
||||
(!(enableHostSingletons && supportsSingletons)
|
||||
? true
|
||||
: parent.tag !== HostSingleton)
|
||||
) {
|
||||
parent = parent.return;
|
||||
}
|
||||
hydrationParentFiber = parent;
|
||||
}
|
||||
|
||||
function popHydrationState(fiber: Fiber): boolean {
|
||||
if (!supportsHydration) {
|
||||
return false;
|
||||
}
|
||||
if (fiber !== hydrationParentFiber) {
|
||||
// We're deeper than the current hydration context, inside an inserted
|
||||
// tree.
|
||||
return false;
|
||||
}
|
||||
if (!isHydrating) {
|
||||
// If we're not currently hydrating but we're in a hydration context, then
|
||||
// we were an insertion and now need to pop up reenter hydration of our
|
||||
// siblings.
|
||||
popToNextHostParent(fiber);
|
||||
isHydrating = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
let shouldClear = false;
|
||||
if (enableHostSingletons && supportsSingletons) {
|
||||
// With float we never clear the Root, or Singleton instances. We also do not clear Instances
|
||||
// that have singleton text content
|
||||
if (
|
||||
fiber.tag !== HostRoot &&
|
||||
fiber.tag !== HostSingleton &&
|
||||
!(
|
||||
fiber.tag === HostComponent &&
|
||||
shouldSetTextContent(fiber.type, fiber.memoizedProps)
|
||||
)
|
||||
) {
|
||||
shouldClear = true;
|
||||
}
|
||||
} else {
|
||||
// If we have any remaining hydratable nodes, we need to delete them now.
|
||||
// We only do this deeper than head and body since they tend to have random
|
||||
// other nodes in them. We also ignore components with pure text content in
|
||||
// side of them. We also don't delete anything inside the root container.
|
||||
if (
|
||||
fiber.tag !== HostRoot &&
|
||||
(fiber.tag !== HostComponent ||
|
||||
(shouldDeleteUnhydratedTailInstances(fiber.type) &&
|
||||
!shouldSetTextContent(fiber.type, fiber.memoizedProps)))
|
||||
) {
|
||||
shouldClear = true;
|
||||
}
|
||||
}
|
||||
if (shouldClear) {
|
||||
let nextInstance = nextHydratableInstance;
|
||||
if (nextInstance) {
|
||||
if (shouldClientRenderOnMismatch(fiber)) {
|
||||
warnIfUnhydratedTailNodes(fiber);
|
||||
throwOnHydrationMismatch(fiber);
|
||||
} else {
|
||||
while (nextInstance) {
|
||||
deleteHydratableInstance(fiber, nextInstance);
|
||||
nextInstance = getNextHydratableSibling(nextInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
popToNextHostParent(fiber);
|
||||
if (fiber.tag === SuspenseComponent) {
|
||||
nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber);
|
||||
} else {
|
||||
nextHydratableInstance = hydrationParentFiber
|
||||
? getNextHydratableSibling(fiber.stateNode)
|
||||
: null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function hasUnhydratedTailNodes(): boolean {
|
||||
return isHydrating && nextHydratableInstance !== null;
|
||||
}
|
||||
|
||||
function warnIfUnhydratedTailNodes(fiber: Fiber) {
|
||||
let nextInstance = nextHydratableInstance;
|
||||
while (nextInstance) {
|
||||
warnUnhydratedInstance(fiber, nextInstance);
|
||||
nextInstance = getNextHydratableSibling(nextInstance);
|
||||
}
|
||||
}
|
||||
|
||||
function resetHydrationState(): void {
|
||||
if (!supportsHydration) {
|
||||
return;
|
||||
}
|
||||
|
||||
hydrationParentFiber = null;
|
||||
nextHydratableInstance = null;
|
||||
isHydrating = false;
|
||||
didSuspendOrErrorDEV = false;
|
||||
}
|
||||
|
||||
export function upgradeHydrationErrorsToRecoverable(): void {
|
||||
if (hydrationErrors !== null) {
|
||||
// Successfully completed a forced client render. The errors that occurred
|
||||
// during the hydration attempt are now recovered. We will log them in
|
||||
// commit phase, once the entire tree has finished.
|
||||
queueRecoverableErrors(hydrationErrors);
|
||||
hydrationErrors = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getIsHydrating(): boolean {
|
||||
return isHydrating;
|
||||
}
|
||||
|
||||
export function queueHydrationError(error: CapturedValue<mixed>): void {
|
||||
if (hydrationErrors === null) {
|
||||
hydrationErrors = [error];
|
||||
} else {
|
||||
hydrationErrors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
warnIfHydrating,
|
||||
enterHydrationState,
|
||||
getIsHydrating,
|
||||
reenterHydrationStateFromDehydratedSuspenseInstance,
|
||||
resetHydrationState,
|
||||
claimHydratableSingleton,
|
||||
tryToClaimNextHydratableInstance,
|
||||
prepareToHydrateHostInstance,
|
||||
prepareToHydrateHostTextInstance,
|
||||
prepareToHydrateHostSuspenseInstance,
|
||||
popHydrationState,
|
||||
hasUnhydratedTailNodes,
|
||||
warnIfUnhydratedTailNodes,
|
||||
};
|
|
@ -1,924 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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, FiberRoot} from './ReactInternalTypes';
|
||||
import type {Transition} from './ReactFiberTracingMarkerComponent.new';
|
||||
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates.new';
|
||||
|
||||
// TODO: Ideally these types would be opaque but that doesn't work well with
|
||||
// our reconciler fork infra, since these leak into non-reconciler packages.
|
||||
|
||||
export type Lanes = number;
|
||||
export type Lane = number;
|
||||
export type LaneMap<T> = Array<T>;
|
||||
|
||||
import {
|
||||
enableSchedulingProfiler,
|
||||
enableUpdaterTracking,
|
||||
allowConcurrentByDefault,
|
||||
enableTransitionTracing,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
|
||||
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
|
||||
import {clz32} from './clz32';
|
||||
|
||||
// Lane values below should be kept in sync with getLabelForLane(), used by react-devtools-timeline.
|
||||
// If those values are changed that package should be rebuilt and redeployed.
|
||||
|
||||
export const TotalLanes = 31;
|
||||
|
||||
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
|
||||
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
|
||||
|
||||
export const SyncHydrationLane: Lane = /* */ 0b0000000000000000000000000000001;
|
||||
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010;
|
||||
|
||||
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
|
||||
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000;
|
||||
|
||||
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000;
|
||||
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000;
|
||||
|
||||
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000;
|
||||
const TransitionLanes: Lanes = /* */ 0b0000000011111111111111110000000;
|
||||
const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000;
|
||||
const TransitionLane2: Lane = /* */ 0b0000000000000000000000100000000;
|
||||
const TransitionLane3: Lane = /* */ 0b0000000000000000000001000000000;
|
||||
const TransitionLane4: Lane = /* */ 0b0000000000000000000010000000000;
|
||||
const TransitionLane5: Lane = /* */ 0b0000000000000000000100000000000;
|
||||
const TransitionLane6: Lane = /* */ 0b0000000000000000001000000000000;
|
||||
const TransitionLane7: Lane = /* */ 0b0000000000000000010000000000000;
|
||||
const TransitionLane8: Lane = /* */ 0b0000000000000000100000000000000;
|
||||
const TransitionLane9: Lane = /* */ 0b0000000000000001000000000000000;
|
||||
const TransitionLane10: Lane = /* */ 0b0000000000000010000000000000000;
|
||||
const TransitionLane11: Lane = /* */ 0b0000000000000100000000000000000;
|
||||
const TransitionLane12: Lane = /* */ 0b0000000000001000000000000000000;
|
||||
const TransitionLane13: Lane = /* */ 0b0000000000010000000000000000000;
|
||||
const TransitionLane14: Lane = /* */ 0b0000000000100000000000000000000;
|
||||
const TransitionLane15: Lane = /* */ 0b0000000001000000000000000000000;
|
||||
const TransitionLane16: Lane = /* */ 0b0000000010000000000000000000000;
|
||||
|
||||
const RetryLanes: Lanes = /* */ 0b0000111100000000000000000000000;
|
||||
const RetryLane1: Lane = /* */ 0b0000000100000000000000000000000;
|
||||
const RetryLane2: Lane = /* */ 0b0000001000000000000000000000000;
|
||||
const RetryLane3: Lane = /* */ 0b0000010000000000000000000000000;
|
||||
const RetryLane4: Lane = /* */ 0b0000100000000000000000000000000;
|
||||
|
||||
export const SomeRetryLane: Lane = RetryLane1;
|
||||
|
||||
export const SelectiveHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
|
||||
|
||||
const NonIdleLanes: Lanes = /* */ 0b0001111111111111111111111111111;
|
||||
|
||||
export const IdleHydrationLane: Lane = /* */ 0b0010000000000000000000000000000;
|
||||
export const IdleLane: Lane = /* */ 0b0100000000000000000000000000000;
|
||||
|
||||
export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
|
||||
|
||||
// This function is used for the experimental timeline (react-devtools-timeline)
|
||||
// It should be kept in sync with the Lanes values above.
|
||||
export function getLabelForLane(lane: Lane): string | void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (lane & SyncHydrationLane) {
|
||||
return 'SyncHydrationLane';
|
||||
}
|
||||
if (lane & SyncLane) {
|
||||
return 'Sync';
|
||||
}
|
||||
if (lane & InputContinuousHydrationLane) {
|
||||
return 'InputContinuousHydration';
|
||||
}
|
||||
if (lane & InputContinuousLane) {
|
||||
return 'InputContinuous';
|
||||
}
|
||||
if (lane & DefaultHydrationLane) {
|
||||
return 'DefaultHydration';
|
||||
}
|
||||
if (lane & DefaultLane) {
|
||||
return 'Default';
|
||||
}
|
||||
if (lane & TransitionHydrationLane) {
|
||||
return 'TransitionHydration';
|
||||
}
|
||||
if (lane & TransitionLanes) {
|
||||
return 'Transition';
|
||||
}
|
||||
if (lane & RetryLanes) {
|
||||
return 'Retry';
|
||||
}
|
||||
if (lane & SelectiveHydrationLane) {
|
||||
return 'SelectiveHydration';
|
||||
}
|
||||
if (lane & IdleHydrationLane) {
|
||||
return 'IdleHydration';
|
||||
}
|
||||
if (lane & IdleLane) {
|
||||
return 'Idle';
|
||||
}
|
||||
if (lane & OffscreenLane) {
|
||||
return 'Offscreen';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const NoTimestamp = -1;
|
||||
|
||||
let nextTransitionLane: Lane = TransitionLane1;
|
||||
let nextRetryLane: Lane = RetryLane1;
|
||||
|
||||
function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
|
||||
switch (getHighestPriorityLane(lanes)) {
|
||||
case SyncHydrationLane:
|
||||
return SyncHydrationLane;
|
||||
case SyncLane:
|
||||
return SyncLane;
|
||||
case InputContinuousHydrationLane:
|
||||
return InputContinuousHydrationLane;
|
||||
case InputContinuousLane:
|
||||
return InputContinuousLane;
|
||||
case DefaultHydrationLane:
|
||||
return DefaultHydrationLane;
|
||||
case DefaultLane:
|
||||
return DefaultLane;
|
||||
case TransitionHydrationLane:
|
||||
return TransitionHydrationLane;
|
||||
case TransitionLane1:
|
||||
case TransitionLane2:
|
||||
case TransitionLane3:
|
||||
case TransitionLane4:
|
||||
case TransitionLane5:
|
||||
case TransitionLane6:
|
||||
case TransitionLane7:
|
||||
case TransitionLane8:
|
||||
case TransitionLane9:
|
||||
case TransitionLane10:
|
||||
case TransitionLane11:
|
||||
case TransitionLane12:
|
||||
case TransitionLane13:
|
||||
case TransitionLane14:
|
||||
case TransitionLane15:
|
||||
case TransitionLane16:
|
||||
return lanes & TransitionLanes;
|
||||
case RetryLane1:
|
||||
case RetryLane2:
|
||||
case RetryLane3:
|
||||
case RetryLane4:
|
||||
return lanes & RetryLanes;
|
||||
case SelectiveHydrationLane:
|
||||
return SelectiveHydrationLane;
|
||||
case IdleHydrationLane:
|
||||
return IdleHydrationLane;
|
||||
case IdleLane:
|
||||
return IdleLane;
|
||||
case OffscreenLane:
|
||||
return OffscreenLane;
|
||||
default:
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'Should have found matching lanes. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
// This shouldn't be reachable, but as a fallback, return the entire bitmask.
|
||||
return lanes;
|
||||
}
|
||||
}
|
||||
|
||||
export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
|
||||
// Early bailout if there's no pending work left.
|
||||
const pendingLanes = root.pendingLanes;
|
||||
if (pendingLanes === NoLanes) {
|
||||
return NoLanes;
|
||||
}
|
||||
|
||||
let nextLanes = NoLanes;
|
||||
|
||||
const suspendedLanes = root.suspendedLanes;
|
||||
const pingedLanes = root.pingedLanes;
|
||||
|
||||
// Do not work on any idle work until all the non-idle work has finished,
|
||||
// even if the work is suspended.
|
||||
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
|
||||
if (nonIdlePendingLanes !== NoLanes) {
|
||||
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
|
||||
if (nonIdleUnblockedLanes !== NoLanes) {
|
||||
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
|
||||
} else {
|
||||
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
|
||||
if (nonIdlePingedLanes !== NoLanes) {
|
||||
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The only remaining work is Idle.
|
||||
const unblockedLanes = pendingLanes & ~suspendedLanes;
|
||||
if (unblockedLanes !== NoLanes) {
|
||||
nextLanes = getHighestPriorityLanes(unblockedLanes);
|
||||
} else {
|
||||
if (pingedLanes !== NoLanes) {
|
||||
nextLanes = getHighestPriorityLanes(pingedLanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextLanes === NoLanes) {
|
||||
// This should only be reachable if we're suspended
|
||||
// TODO: Consider warning in this path if a fallback timer is not scheduled.
|
||||
return NoLanes;
|
||||
}
|
||||
|
||||
// If we're already in the middle of a render, switching lanes will interrupt
|
||||
// it and we'll lose our progress. We should only do this if the new lanes are
|
||||
// higher priority.
|
||||
if (
|
||||
wipLanes !== NoLanes &&
|
||||
wipLanes !== nextLanes &&
|
||||
// If we already suspended with a delay, then interrupting is fine. Don't
|
||||
// bother waiting until the root is complete.
|
||||
(wipLanes & suspendedLanes) === NoLanes
|
||||
) {
|
||||
const nextLane = getHighestPriorityLane(nextLanes);
|
||||
const wipLane = getHighestPriorityLane(wipLanes);
|
||||
if (
|
||||
// Tests whether the next lane is equal or lower priority than the wip
|
||||
// one. This works because the bits decrease in priority as you go left.
|
||||
nextLane >= wipLane ||
|
||||
// Default priority updates should not interrupt transition updates. The
|
||||
// only difference between default updates and transition updates is that
|
||||
// default updates do not support refresh transitions.
|
||||
(nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)
|
||||
) {
|
||||
// Keep working on the existing in-progress tree. Do not interrupt.
|
||||
return wipLanes;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
allowConcurrentByDefault &&
|
||||
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
|
||||
) {
|
||||
// Do nothing, use the lanes as they were assigned.
|
||||
} else if ((nextLanes & InputContinuousLane) !== NoLanes) {
|
||||
// When updates are sync by default, we entangle continuous priority updates
|
||||
// and default updates, so they render in the same batch. The only reason
|
||||
// they use separate lanes is because continuous updates should interrupt
|
||||
// transitions, but default updates should not.
|
||||
nextLanes |= pendingLanes & DefaultLane;
|
||||
}
|
||||
|
||||
// Check for entangled lanes and add them to the batch.
|
||||
//
|
||||
// A lane is said to be entangled with another when it's not allowed to render
|
||||
// in a batch that does not also include the other lane. Typically we do this
|
||||
// when multiple updates have the same source, and we only want to respond to
|
||||
// the most recent event from that source.
|
||||
//
|
||||
// Note that we apply entanglements *after* checking for partial work above.
|
||||
// This means that if a lane is entangled during an interleaved event while
|
||||
// it's already rendering, we won't interrupt it. This is intentional, since
|
||||
// entanglement is usually "best effort": we'll try our best to render the
|
||||
// lanes in the same batch, but it's not worth throwing out partially
|
||||
// completed work in order to do it.
|
||||
// TODO: Reconsider this. The counter-argument is that the partial work
|
||||
// represents an intermediate state, which we don't want to show to the user.
|
||||
// And by spending extra time finishing it, we're increasing the amount of
|
||||
// time it takes to show the final state, which is what they are actually
|
||||
// waiting for.
|
||||
//
|
||||
// For those exceptions where entanglement is semantically important, like
|
||||
// useMutableSource, we should ensure that there is no partial work at the
|
||||
// time we apply the entanglement.
|
||||
const entangledLanes = root.entangledLanes;
|
||||
if (entangledLanes !== NoLanes) {
|
||||
const entanglements = root.entanglements;
|
||||
let lanes = nextLanes & entangledLanes;
|
||||
while (lanes > 0) {
|
||||
const index = pickArbitraryLaneIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
|
||||
nextLanes |= entanglements[index];
|
||||
|
||||
lanes &= ~lane;
|
||||
}
|
||||
}
|
||||
|
||||
return nextLanes;
|
||||
}
|
||||
|
||||
export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number {
|
||||
const eventTimes = root.eventTimes;
|
||||
|
||||
let mostRecentEventTime = NoTimestamp;
|
||||
while (lanes > 0) {
|
||||
const index = pickArbitraryLaneIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
|
||||
const eventTime = eventTimes[index];
|
||||
if (eventTime > mostRecentEventTime) {
|
||||
mostRecentEventTime = eventTime;
|
||||
}
|
||||
|
||||
lanes &= ~lane;
|
||||
}
|
||||
|
||||
return mostRecentEventTime;
|
||||
}
|
||||
|
||||
function computeExpirationTime(lane: Lane, currentTime: number) {
|
||||
switch (lane) {
|
||||
case SyncHydrationLane:
|
||||
case SyncLane:
|
||||
case InputContinuousHydrationLane:
|
||||
case InputContinuousLane:
|
||||
// User interactions should expire slightly more quickly.
|
||||
//
|
||||
// NOTE: This is set to the corresponding constant as in Scheduler.js.
|
||||
// When we made it larger, a product metric in www regressed, suggesting
|
||||
// there's a user interaction that's being starved by a series of
|
||||
// synchronous updates. If that theory is correct, the proper solution is
|
||||
// to fix the starvation. However, this scenario supports the idea that
|
||||
// expiration times are an important safeguard when starvation
|
||||
// does happen.
|
||||
return currentTime + 250;
|
||||
case DefaultHydrationLane:
|
||||
case DefaultLane:
|
||||
case TransitionHydrationLane:
|
||||
case TransitionLane1:
|
||||
case TransitionLane2:
|
||||
case TransitionLane3:
|
||||
case TransitionLane4:
|
||||
case TransitionLane5:
|
||||
case TransitionLane6:
|
||||
case TransitionLane7:
|
||||
case TransitionLane8:
|
||||
case TransitionLane9:
|
||||
case TransitionLane10:
|
||||
case TransitionLane11:
|
||||
case TransitionLane12:
|
||||
case TransitionLane13:
|
||||
case TransitionLane14:
|
||||
case TransitionLane15:
|
||||
case TransitionLane16:
|
||||
return currentTime + 5000;
|
||||
case RetryLane1:
|
||||
case RetryLane2:
|
||||
case RetryLane3:
|
||||
case RetryLane4:
|
||||
// TODO: Retries should be allowed to expire if they are CPU bound for
|
||||
// too long, but when I made this change it caused a spike in browser
|
||||
// crashes. There must be some other underlying bug; not super urgent but
|
||||
// ideally should figure out why and fix it. Unfortunately we don't have
|
||||
// a repro for the crashes, only detected via production metrics.
|
||||
return NoTimestamp;
|
||||
case SelectiveHydrationLane:
|
||||
case IdleHydrationLane:
|
||||
case IdleLane:
|
||||
case OffscreenLane:
|
||||
// Anything idle priority or lower should never expire.
|
||||
return NoTimestamp;
|
||||
default:
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'Should have found matching lanes. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
return NoTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
export function markStarvedLanesAsExpired(
|
||||
root: FiberRoot,
|
||||
currentTime: number,
|
||||
): void {
|
||||
// TODO: This gets called every time we yield. We can optimize by storing
|
||||
// the earliest expiration time on the root. Then use that to quickly bail out
|
||||
// of this function.
|
||||
|
||||
const pendingLanes = root.pendingLanes;
|
||||
const suspendedLanes = root.suspendedLanes;
|
||||
const pingedLanes = root.pingedLanes;
|
||||
const expirationTimes = root.expirationTimes;
|
||||
|
||||
// Iterate through the pending lanes and check if we've reached their
|
||||
// expiration time. If so, we'll assume the update is being starved and mark
|
||||
// it as expired to force it to finish.
|
||||
//
|
||||
// We exclude retry lanes because those must always be time sliced, in order
|
||||
// to unwrap uncached promises.
|
||||
// TODO: Write a test for this
|
||||
let lanes = pendingLanes & ~RetryLanes;
|
||||
while (lanes > 0) {
|
||||
const index = pickArbitraryLaneIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
|
||||
const expirationTime = expirationTimes[index];
|
||||
if (expirationTime === NoTimestamp) {
|
||||
// Found a pending lane with no expiration time. If it's not suspended, or
|
||||
// if it's pinged, assume it's CPU-bound. Compute a new expiration time
|
||||
// using the current time.
|
||||
if (
|
||||
(lane & suspendedLanes) === NoLanes ||
|
||||
(lane & pingedLanes) !== NoLanes
|
||||
) {
|
||||
// Assumes timestamps are monotonically increasing.
|
||||
expirationTimes[index] = computeExpirationTime(lane, currentTime);
|
||||
}
|
||||
} else if (expirationTime <= currentTime) {
|
||||
// This lane expired
|
||||
root.expiredLanes |= lane;
|
||||
}
|
||||
|
||||
lanes &= ~lane;
|
||||
}
|
||||
}
|
||||
|
||||
// This returns the highest priority pending lanes regardless of whether they
|
||||
// are suspended.
|
||||
export function getHighestPriorityPendingLanes(root: FiberRoot): Lanes {
|
||||
return getHighestPriorityLanes(root.pendingLanes);
|
||||
}
|
||||
|
||||
export function getLanesToRetrySynchronouslyOnError(
|
||||
root: FiberRoot,
|
||||
originallyAttemptedLanes: Lanes,
|
||||
): Lanes {
|
||||
if (root.errorRecoveryDisabledLanes & originallyAttemptedLanes) {
|
||||
// The error recovery mechanism is disabled until these lanes are cleared.
|
||||
return NoLanes;
|
||||
}
|
||||
|
||||
const everythingButOffscreen = root.pendingLanes & ~OffscreenLane;
|
||||
if (everythingButOffscreen !== NoLanes) {
|
||||
return everythingButOffscreen;
|
||||
}
|
||||
if (everythingButOffscreen & OffscreenLane) {
|
||||
return OffscreenLane;
|
||||
}
|
||||
return NoLanes;
|
||||
}
|
||||
|
||||
export function includesSyncLane(lanes: Lanes): boolean {
|
||||
return (lanes & (SyncLane | SyncHydrationLane)) !== NoLanes;
|
||||
}
|
||||
|
||||
export function includesNonIdleWork(lanes: Lanes): boolean {
|
||||
return (lanes & NonIdleLanes) !== NoLanes;
|
||||
}
|
||||
export function includesOnlyRetries(lanes: Lanes): boolean {
|
||||
return (lanes & RetryLanes) === lanes;
|
||||
}
|
||||
export function includesOnlyNonUrgentLanes(lanes: Lanes): boolean {
|
||||
// TODO: Should hydration lanes be included here? This function is only
|
||||
// used in `updateDeferredValueImpl`.
|
||||
const UrgentLanes = SyncLane | InputContinuousLane | DefaultLane;
|
||||
return (lanes & UrgentLanes) === NoLanes;
|
||||
}
|
||||
export function includesOnlyTransitions(lanes: Lanes): boolean {
|
||||
return (lanes & TransitionLanes) === lanes;
|
||||
}
|
||||
|
||||
export function includesBlockingLane(root: FiberRoot, lanes: Lanes): boolean {
|
||||
if (
|
||||
allowConcurrentByDefault &&
|
||||
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
|
||||
) {
|
||||
// Concurrent updates by default always use time slicing.
|
||||
return false;
|
||||
}
|
||||
const SyncDefaultLanes =
|
||||
InputContinuousHydrationLane |
|
||||
InputContinuousLane |
|
||||
DefaultHydrationLane |
|
||||
DefaultLane;
|
||||
return (lanes & SyncDefaultLanes) !== NoLanes;
|
||||
}
|
||||
|
||||
export function includesExpiredLane(root: FiberRoot, lanes: Lanes): boolean {
|
||||
// This is a separate check from includesBlockingLane because a lane can
|
||||
// expire after a render has already started.
|
||||
return (lanes & root.expiredLanes) !== NoLanes;
|
||||
}
|
||||
|
||||
export function isTransitionLane(lane: Lane): boolean {
|
||||
return (lane & TransitionLanes) !== NoLanes;
|
||||
}
|
||||
|
||||
export function claimNextTransitionLane(): Lane {
|
||||
// Cycle through the lanes, assigning each new transition to the next lane.
|
||||
// In most cases, this means every transition gets its own lane, until we
|
||||
// run out of lanes and cycle back to the beginning.
|
||||
const lane = nextTransitionLane;
|
||||
nextTransitionLane <<= 1;
|
||||
if ((nextTransitionLane & TransitionLanes) === NoLanes) {
|
||||
nextTransitionLane = TransitionLane1;
|
||||
}
|
||||
return lane;
|
||||
}
|
||||
|
||||
export function claimNextRetryLane(): Lane {
|
||||
const lane = nextRetryLane;
|
||||
nextRetryLane <<= 1;
|
||||
if ((nextRetryLane & RetryLanes) === NoLanes) {
|
||||
nextRetryLane = RetryLane1;
|
||||
}
|
||||
return lane;
|
||||
}
|
||||
|
||||
export function getHighestPriorityLane(lanes: Lanes): Lane {
|
||||
return lanes & -lanes;
|
||||
}
|
||||
|
||||
export function pickArbitraryLane(lanes: Lanes): Lane {
|
||||
// This wrapper function gets inlined. Only exists so to communicate that it
|
||||
// doesn't matter which bit is selected; you can pick any bit without
|
||||
// affecting the algorithms where its used. Here I'm using
|
||||
// getHighestPriorityLane because it requires the fewest operations.
|
||||
return getHighestPriorityLane(lanes);
|
||||
}
|
||||
|
||||
function pickArbitraryLaneIndex(lanes: Lanes) {
|
||||
return 31 - clz32(lanes);
|
||||
}
|
||||
|
||||
function laneToIndex(lane: Lane) {
|
||||
return pickArbitraryLaneIndex(lane);
|
||||
}
|
||||
|
||||
export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane): boolean {
|
||||
return (a & b) !== NoLanes;
|
||||
}
|
||||
|
||||
export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane): boolean {
|
||||
return (set & subset) === subset;
|
||||
}
|
||||
|
||||
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
|
||||
return a | b;
|
||||
}
|
||||
|
||||
export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
|
||||
return set & ~subset;
|
||||
}
|
||||
|
||||
export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
|
||||
return a & b;
|
||||
}
|
||||
|
||||
// Seems redundant, but it changes the type from a single lane (used for
|
||||
// updates) to a group of lanes (used for flushing work).
|
||||
export function laneToLanes(lane: Lane): Lanes {
|
||||
return lane;
|
||||
}
|
||||
|
||||
export function higherPriorityLane(a: Lane, b: Lane): Lane {
|
||||
// This works because the bit ranges decrease in priority as you go left.
|
||||
return a !== NoLane && a < b ? a : b;
|
||||
}
|
||||
|
||||
export function createLaneMap<T>(initial: T): LaneMap<T> {
|
||||
// Intentionally pushing one by one.
|
||||
// https://v8.dev/blog/elements-kinds#avoid-creating-holes
|
||||
const laneMap = [];
|
||||
for (let i = 0; i < TotalLanes; i++) {
|
||||
laneMap.push(initial);
|
||||
}
|
||||
return laneMap;
|
||||
}
|
||||
|
||||
export function markRootUpdated(
|
||||
root: FiberRoot,
|
||||
updateLane: Lane,
|
||||
eventTime: number,
|
||||
) {
|
||||
root.pendingLanes |= updateLane;
|
||||
|
||||
// If there are any suspended transitions, it's possible this new update
|
||||
// could unblock them. Clear the suspended lanes so that we can try rendering
|
||||
// them again.
|
||||
//
|
||||
// TODO: We really only need to unsuspend only lanes that are in the
|
||||
// `subtreeLanes` of the updated fiber, or the update lanes of the return
|
||||
// path. This would exclude suspended updates in an unrelated sibling tree,
|
||||
// since there's no way for this update to unblock it.
|
||||
//
|
||||
// We don't do this if the incoming update is idle, because we never process
|
||||
// idle updates until after all the regular updates have finished; there's no
|
||||
// way it could unblock a transition.
|
||||
if (updateLane !== IdleLane) {
|
||||
root.suspendedLanes = NoLanes;
|
||||
root.pingedLanes = NoLanes;
|
||||
}
|
||||
|
||||
const eventTimes = root.eventTimes;
|
||||
const index = laneToIndex(updateLane);
|
||||
// We can always overwrite an existing timestamp because we prefer the most
|
||||
// recent event, and we assume time is monotonically increasing.
|
||||
eventTimes[index] = eventTime;
|
||||
}
|
||||
|
||||
export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
|
||||
root.suspendedLanes |= suspendedLanes;
|
||||
root.pingedLanes &= ~suspendedLanes;
|
||||
|
||||
// The suspended lanes are no longer CPU-bound. Clear their expiration times.
|
||||
const expirationTimes = root.expirationTimes;
|
||||
let lanes = suspendedLanes;
|
||||
while (lanes > 0) {
|
||||
const index = pickArbitraryLaneIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
|
||||
expirationTimes[index] = NoTimestamp;
|
||||
|
||||
lanes &= ~lane;
|
||||
}
|
||||
}
|
||||
|
||||
export function markRootPinged(
|
||||
root: FiberRoot,
|
||||
pingedLanes: Lanes,
|
||||
eventTime: number,
|
||||
) {
|
||||
root.pingedLanes |= root.suspendedLanes & pingedLanes;
|
||||
}
|
||||
|
||||
export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
|
||||
root.mutableReadLanes |= updateLane & root.pendingLanes;
|
||||
}
|
||||
|
||||
export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
|
||||
const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
|
||||
|
||||
root.pendingLanes = remainingLanes;
|
||||
|
||||
// Let's try everything again
|
||||
root.suspendedLanes = NoLanes;
|
||||
root.pingedLanes = NoLanes;
|
||||
|
||||
root.expiredLanes &= remainingLanes;
|
||||
root.mutableReadLanes &= remainingLanes;
|
||||
|
||||
root.entangledLanes &= remainingLanes;
|
||||
|
||||
root.errorRecoveryDisabledLanes &= remainingLanes;
|
||||
|
||||
const entanglements = root.entanglements;
|
||||
const eventTimes = root.eventTimes;
|
||||
const expirationTimes = root.expirationTimes;
|
||||
const hiddenUpdates = root.hiddenUpdates;
|
||||
|
||||
// Clear the lanes that no longer have pending work
|
||||
let lanes = noLongerPendingLanes;
|
||||
while (lanes > 0) {
|
||||
const index = pickArbitraryLaneIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
|
||||
entanglements[index] = NoLanes;
|
||||
eventTimes[index] = NoTimestamp;
|
||||
expirationTimes[index] = NoTimestamp;
|
||||
|
||||
const hiddenUpdatesForLane = hiddenUpdates[index];
|
||||
if (hiddenUpdatesForLane !== null) {
|
||||
hiddenUpdates[index] = null;
|
||||
// "Hidden" updates are updates that were made to a hidden component. They
|
||||
// have special logic associated with them because they may be entangled
|
||||
// with updates that occur outside that tree. But once the outer tree
|
||||
// commits, they behave like regular updates.
|
||||
for (let i = 0; i < hiddenUpdatesForLane.length; i++) {
|
||||
const update = hiddenUpdatesForLane[i];
|
||||
if (update !== null) {
|
||||
update.lane &= ~OffscreenLane;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lanes &= ~lane;
|
||||
}
|
||||
}
|
||||
|
||||
export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) {
|
||||
// In addition to entangling each of the given lanes with each other, we also
|
||||
// have to consider _transitive_ entanglements. For each lane that is already
|
||||
// entangled with *any* of the given lanes, that lane is now transitively
|
||||
// entangled with *all* the given lanes.
|
||||
//
|
||||
// Translated: If C is entangled with A, then entangling A with B also
|
||||
// entangles C with B.
|
||||
//
|
||||
// If this is hard to grasp, it might help to intentionally break this
|
||||
// function and look at the tests that fail in ReactTransition-test.js. Try
|
||||
// commenting out one of the conditions below.
|
||||
|
||||
const rootEntangledLanes = (root.entangledLanes |= entangledLanes);
|
||||
const entanglements = root.entanglements;
|
||||
let lanes = rootEntangledLanes;
|
||||
while (lanes) {
|
||||
const index = pickArbitraryLaneIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
if (
|
||||
// Is this one of the newly entangled lanes?
|
||||
(lane & entangledLanes) |
|
||||
// Is this lane transitively entangled with the newly entangled lanes?
|
||||
(entanglements[index] & entangledLanes)
|
||||
) {
|
||||
entanglements[index] |= entangledLanes;
|
||||
}
|
||||
lanes &= ~lane;
|
||||
}
|
||||
}
|
||||
|
||||
export function markHiddenUpdate(
|
||||
root: FiberRoot,
|
||||
update: ConcurrentUpdate,
|
||||
lane: Lane,
|
||||
) {
|
||||
const index = laneToIndex(lane);
|
||||
const hiddenUpdates = root.hiddenUpdates;
|
||||
const hiddenUpdatesForLane = hiddenUpdates[index];
|
||||
if (hiddenUpdatesForLane === null) {
|
||||
hiddenUpdates[index] = [update];
|
||||
} else {
|
||||
hiddenUpdatesForLane.push(update);
|
||||
}
|
||||
update.lane = lane | OffscreenLane;
|
||||
}
|
||||
|
||||
export function getBumpedLaneForHydration(
|
||||
root: FiberRoot,
|
||||
renderLanes: Lanes,
|
||||
): Lane {
|
||||
const renderLane = getHighestPriorityLane(renderLanes);
|
||||
|
||||
let lane;
|
||||
switch (renderLane) {
|
||||
case SyncLane:
|
||||
lane = SyncHydrationLane;
|
||||
break;
|
||||
case InputContinuousLane:
|
||||
lane = InputContinuousHydrationLane;
|
||||
break;
|
||||
case DefaultLane:
|
||||
lane = DefaultHydrationLane;
|
||||
break;
|
||||
case TransitionLane1:
|
||||
case TransitionLane2:
|
||||
case TransitionLane3:
|
||||
case TransitionLane4:
|
||||
case TransitionLane5:
|
||||
case TransitionLane6:
|
||||
case TransitionLane7:
|
||||
case TransitionLane8:
|
||||
case TransitionLane9:
|
||||
case TransitionLane10:
|
||||
case TransitionLane11:
|
||||
case TransitionLane12:
|
||||
case TransitionLane13:
|
||||
case TransitionLane14:
|
||||
case TransitionLane15:
|
||||
case TransitionLane16:
|
||||
case RetryLane1:
|
||||
case RetryLane2:
|
||||
case RetryLane3:
|
||||
case RetryLane4:
|
||||
lane = TransitionHydrationLane;
|
||||
break;
|
||||
case IdleLane:
|
||||
lane = IdleHydrationLane;
|
||||
break;
|
||||
default:
|
||||
// Everything else is already either a hydration lane, or shouldn't
|
||||
// be retried at a hydration lane.
|
||||
lane = NoLane;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the lane we chose is suspended. If so, that indicates that we
|
||||
// already attempted and failed to hydrate at that level. Also check if we're
|
||||
// already rendering that lane, which is rare but could happen.
|
||||
if ((lane & (root.suspendedLanes | renderLanes)) !== NoLane) {
|
||||
// Give up trying to hydrate and fall back to client render.
|
||||
return NoLane;
|
||||
}
|
||||
|
||||
return lane;
|
||||
}
|
||||
|
||||
export function addFiberToLanesMap(
|
||||
root: FiberRoot,
|
||||
fiber: Fiber,
|
||||
lanes: Lanes | Lane,
|
||||
) {
|
||||
if (!enableUpdaterTracking) {
|
||||
return;
|
||||
}
|
||||
if (!isDevToolsPresent) {
|
||||
return;
|
||||
}
|
||||
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
|
||||
while (lanes > 0) {
|
||||
const index = laneToIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
|
||||
const updaters = pendingUpdatersLaneMap[index];
|
||||
updaters.add(fiber);
|
||||
|
||||
lanes &= ~lane;
|
||||
}
|
||||
}
|
||||
|
||||
export function movePendingFibersToMemoized(root: FiberRoot, lanes: Lanes) {
|
||||
if (!enableUpdaterTracking) {
|
||||
return;
|
||||
}
|
||||
if (!isDevToolsPresent) {
|
||||
return;
|
||||
}
|
||||
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
|
||||
const memoizedUpdaters = root.memoizedUpdaters;
|
||||
while (lanes > 0) {
|
||||
const index = laneToIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
|
||||
const updaters = pendingUpdatersLaneMap[index];
|
||||
if (updaters.size > 0) {
|
||||
updaters.forEach(fiber => {
|
||||
const alternate = fiber.alternate;
|
||||
if (alternate === null || !memoizedUpdaters.has(alternate)) {
|
||||
memoizedUpdaters.add(fiber);
|
||||
}
|
||||
});
|
||||
updaters.clear();
|
||||
}
|
||||
|
||||
lanes &= ~lane;
|
||||
}
|
||||
}
|
||||
|
||||
export function addTransitionToLanesMap(
|
||||
root: FiberRoot,
|
||||
transition: Transition,
|
||||
lane: Lane,
|
||||
) {
|
||||
if (enableTransitionTracing) {
|
||||
const transitionLanesMap = root.transitionLanes;
|
||||
const index = laneToIndex(lane);
|
||||
let transitions = transitionLanesMap[index];
|
||||
if (transitions === null) {
|
||||
transitions = new Set();
|
||||
}
|
||||
transitions.add(transition);
|
||||
|
||||
transitionLanesMap[index] = transitions;
|
||||
}
|
||||
}
|
||||
|
||||
export function getTransitionsForLanes(
|
||||
root: FiberRoot,
|
||||
lanes: Lane | Lanes,
|
||||
): Array<Transition> | null {
|
||||
if (!enableTransitionTracing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const transitionsForLanes = [];
|
||||
while (lanes > 0) {
|
||||
const index = laneToIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
const transitions = root.transitionLanes[index];
|
||||
if (transitions !== null) {
|
||||
transitions.forEach(transition => {
|
||||
transitionsForLanes.push(transition);
|
||||
});
|
||||
}
|
||||
|
||||
lanes &= ~lane;
|
||||
}
|
||||
|
||||
if (transitionsForLanes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return transitionsForLanes;
|
||||
}
|
||||
|
||||
export function clearTransitionsForLanes(root: FiberRoot, lanes: Lane | Lanes) {
|
||||
if (!enableTransitionTracing) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (lanes > 0) {
|
||||
const index = laneToIndex(lanes);
|
||||
const lane = 1 << index;
|
||||
|
||||
const transitions = root.transitionLanes[index];
|
||||
if (transitions !== null) {
|
||||
root.transitionLanes[index] = null;
|
||||
}
|
||||
|
||||
lanes &= ~lane;
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 assign from 'shared/assign';
|
||||
|
||||
export function resolveDefaultProps(Component: any, baseProps: Object): Object {
|
||||
if (Component && Component.defaultProps) {
|
||||
// Resolve default props. Taken from ReactElement
|
||||
const props = assign({}, baseProps);
|
||||
const defaultProps = Component.defaultProps;
|
||||
for (const propName in defaultProps) {
|
||||
if (props[propName] === undefined) {
|
||||
props[propName] = defaultProps[propName];
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
return baseProps;
|
||||
}
|
|
@ -1,706 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {ReactContext, ReactProviderType} from 'shared/ReactTypes';
|
||||
import type {
|
||||
Fiber,
|
||||
ContextDependency,
|
||||
Dependencies,
|
||||
} from './ReactInternalTypes';
|
||||
import type {StackCursor} from './ReactFiberStack.new';
|
||||
import type {Lanes} from './ReactFiberLane.new';
|
||||
import type {SharedQueue} from './ReactFiberClassUpdateQueue.new';
|
||||
|
||||
import {isPrimaryRenderer} from './ReactFiberHostConfig';
|
||||
import {createCursor, push, pop} from './ReactFiberStack.new';
|
||||
import {
|
||||
ContextProvider,
|
||||
ClassComponent,
|
||||
DehydratedFragment,
|
||||
} from './ReactWorkTags';
|
||||
import {
|
||||
NoLanes,
|
||||
NoTimestamp,
|
||||
isSubsetOfLanes,
|
||||
includesSomeLane,
|
||||
mergeLanes,
|
||||
pickArbitraryLane,
|
||||
} from './ReactFiberLane.new';
|
||||
import {
|
||||
NoFlags,
|
||||
DidPropagateContext,
|
||||
NeedsPropagation,
|
||||
} from './ReactFiberFlags';
|
||||
|
||||
import is from 'shared/objectIs';
|
||||
import {createUpdate, ForceUpdate} from './ReactFiberClassUpdateQueue.new';
|
||||
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new';
|
||||
import {
|
||||
enableLazyContextPropagation,
|
||||
enableServerContext,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
|
||||
|
||||
const valueCursor: StackCursor<mixed> = createCursor(null);
|
||||
|
||||
let rendererSigil;
|
||||
if (__DEV__) {
|
||||
// Use this to detect multiple renderers using the same context
|
||||
rendererSigil = {};
|
||||
}
|
||||
|
||||
let currentlyRenderingFiber: Fiber | null = null;
|
||||
let lastContextDependency: ContextDependency<mixed> | null = null;
|
||||
let lastFullyObservedContext: ReactContext<any> | null = null;
|
||||
|
||||
let isDisallowedContextReadInDEV: boolean = false;
|
||||
|
||||
export function resetContextDependencies(): void {
|
||||
// This is called right before React yields execution, to ensure `readContext`
|
||||
// cannot be called outside the render phase.
|
||||
currentlyRenderingFiber = null;
|
||||
lastContextDependency = null;
|
||||
lastFullyObservedContext = null;
|
||||
if (__DEV__) {
|
||||
isDisallowedContextReadInDEV = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function enterDisallowedContextReadInDEV(): void {
|
||||
if (__DEV__) {
|
||||
isDisallowedContextReadInDEV = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function exitDisallowedContextReadInDEV(): void {
|
||||
if (__DEV__) {
|
||||
isDisallowedContextReadInDEV = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function pushProvider<T>(
|
||||
providerFiber: Fiber,
|
||||
context: ReactContext<T>,
|
||||
nextValue: T,
|
||||
): void {
|
||||
if (isPrimaryRenderer) {
|
||||
push(valueCursor, context._currentValue, providerFiber);
|
||||
|
||||
context._currentValue = nextValue;
|
||||
if (__DEV__) {
|
||||
if (
|
||||
context._currentRenderer !== undefined &&
|
||||
context._currentRenderer !== null &&
|
||||
context._currentRenderer !== rendererSigil
|
||||
) {
|
||||
console.error(
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
'same context provider. This is currently unsupported.',
|
||||
);
|
||||
}
|
||||
context._currentRenderer = rendererSigil;
|
||||
}
|
||||
} else {
|
||||
push(valueCursor, context._currentValue2, providerFiber);
|
||||
|
||||
context._currentValue2 = nextValue;
|
||||
if (__DEV__) {
|
||||
if (
|
||||
context._currentRenderer2 !== undefined &&
|
||||
context._currentRenderer2 !== null &&
|
||||
context._currentRenderer2 !== rendererSigil
|
||||
) {
|
||||
console.error(
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
'same context provider. This is currently unsupported.',
|
||||
);
|
||||
}
|
||||
context._currentRenderer2 = rendererSigil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function popProvider(
|
||||
context: ReactContext<any>,
|
||||
providerFiber: Fiber,
|
||||
): void {
|
||||
const currentValue = valueCursor.current;
|
||||
pop(valueCursor, providerFiber);
|
||||
if (isPrimaryRenderer) {
|
||||
if (
|
||||
enableServerContext &&
|
||||
currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
|
||||
) {
|
||||
context._currentValue = context._defaultValue;
|
||||
} else {
|
||||
context._currentValue = currentValue;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
enableServerContext &&
|
||||
currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
|
||||
) {
|
||||
context._currentValue2 = context._defaultValue;
|
||||
} else {
|
||||
context._currentValue2 = currentValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function scheduleContextWorkOnParentPath(
|
||||
parent: Fiber | null,
|
||||
renderLanes: Lanes,
|
||||
propagationRoot: Fiber,
|
||||
) {
|
||||
// Update the child lanes of all the ancestors, including the alternates.
|
||||
let node = parent;
|
||||
while (node !== null) {
|
||||
const alternate = node.alternate;
|
||||
if (!isSubsetOfLanes(node.childLanes, renderLanes)) {
|
||||
node.childLanes = mergeLanes(node.childLanes, renderLanes);
|
||||
if (alternate !== null) {
|
||||
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
|
||||
}
|
||||
} else if (
|
||||
alternate !== null &&
|
||||
!isSubsetOfLanes(alternate.childLanes, renderLanes)
|
||||
) {
|
||||
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
|
||||
} else {
|
||||
// Neither alternate was updated.
|
||||
// Normally, this would mean that the rest of the
|
||||
// ancestor path already has sufficient priority.
|
||||
// However, this is not necessarily true inside offscreen
|
||||
// or fallback trees because childLanes may be inconsistent
|
||||
// with the surroundings. This is why we continue the loop.
|
||||
}
|
||||
if (node === propagationRoot) {
|
||||
break;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
if (__DEV__) {
|
||||
if (node !== propagationRoot) {
|
||||
console.error(
|
||||
'Expected to find the propagation root when scheduling context work. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function propagateContextChange<T>(
|
||||
workInProgress: Fiber,
|
||||
context: ReactContext<T>,
|
||||
renderLanes: Lanes,
|
||||
): void {
|
||||
if (enableLazyContextPropagation) {
|
||||
// TODO: This path is only used by Cache components. Update
|
||||
// lazilyPropagateParentContextChanges to look for Cache components so they
|
||||
// can take advantage of lazy propagation.
|
||||
const forcePropagateEntireTree = true;
|
||||
propagateContextChanges(
|
||||
workInProgress,
|
||||
[context],
|
||||
renderLanes,
|
||||
forcePropagateEntireTree,
|
||||
);
|
||||
} else {
|
||||
propagateContextChange_eager(workInProgress, context, renderLanes);
|
||||
}
|
||||
}
|
||||
|
||||
function propagateContextChange_eager<T>(
|
||||
workInProgress: Fiber,
|
||||
context: ReactContext<T>,
|
||||
renderLanes: Lanes,
|
||||
): void {
|
||||
// Only used by eager implementation
|
||||
if (enableLazyContextPropagation) {
|
||||
return;
|
||||
}
|
||||
let fiber = workInProgress.child;
|
||||
if (fiber !== null) {
|
||||
// Set the return pointer of the child to the work-in-progress fiber.
|
||||
fiber.return = workInProgress;
|
||||
}
|
||||
while (fiber !== null) {
|
||||
let nextFiber;
|
||||
|
||||
// Visit this fiber.
|
||||
const list = fiber.dependencies;
|
||||
if (list !== null) {
|
||||
nextFiber = fiber.child;
|
||||
|
||||
let dependency = list.firstContext;
|
||||
while (dependency !== null) {
|
||||
// Check if the context matches.
|
||||
if (dependency.context === context) {
|
||||
// Match! Schedule an update on this fiber.
|
||||
if (fiber.tag === ClassComponent) {
|
||||
// Schedule a force update on the work-in-progress.
|
||||
const lane = pickArbitraryLane(renderLanes);
|
||||
const update = createUpdate(NoTimestamp, lane);
|
||||
update.tag = ForceUpdate;
|
||||
// TODO: Because we don't have a work-in-progress, this will add the
|
||||
// update to the current fiber, too, which means it will persist even if
|
||||
// this render is thrown away. Since it's a race condition, not sure it's
|
||||
// worth fixing.
|
||||
|
||||
// Inlined `enqueueUpdate` to remove interleaved update check
|
||||
const updateQueue = fiber.updateQueue;
|
||||
if (updateQueue === null) {
|
||||
// Only occurs if the fiber has been unmounted.
|
||||
} else {
|
||||
const sharedQueue: SharedQueue<any> = (updateQueue: any).shared;
|
||||
const pending = sharedQueue.pending;
|
||||
if (pending === null) {
|
||||
// This is the first update. Create a circular list.
|
||||
update.next = update;
|
||||
} else {
|
||||
update.next = pending.next;
|
||||
pending.next = update;
|
||||
}
|
||||
sharedQueue.pending = update;
|
||||
}
|
||||
}
|
||||
|
||||
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
|
||||
const alternate = fiber.alternate;
|
||||
if (alternate !== null) {
|
||||
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
|
||||
}
|
||||
scheduleContextWorkOnParentPath(
|
||||
fiber.return,
|
||||
renderLanes,
|
||||
workInProgress,
|
||||
);
|
||||
|
||||
// Mark the updated lanes on the list, too.
|
||||
list.lanes = mergeLanes(list.lanes, renderLanes);
|
||||
|
||||
// Since we already found a match, we can stop traversing the
|
||||
// dependency list.
|
||||
break;
|
||||
}
|
||||
dependency = dependency.next;
|
||||
}
|
||||
} else if (fiber.tag === ContextProvider) {
|
||||
// Don't scan deeper if this is a matching provider
|
||||
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
|
||||
} else if (fiber.tag === DehydratedFragment) {
|
||||
// If a dehydrated suspense boundary is in this subtree, we don't know
|
||||
// if it will have any context consumers in it. The best we can do is
|
||||
// mark it as having updates.
|
||||
const parentSuspense = fiber.return;
|
||||
|
||||
if (parentSuspense === null) {
|
||||
throw new Error(
|
||||
'We just came from a parent so we must have had a parent. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
|
||||
parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
|
||||
const alternate = parentSuspense.alternate;
|
||||
if (alternate !== null) {
|
||||
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
|
||||
}
|
||||
// This is intentionally passing this fiber as the parent
|
||||
// because we want to schedule this fiber as having work
|
||||
// on its children. We'll use the childLanes on
|
||||
// this fiber to indicate that a context has changed.
|
||||
scheduleContextWorkOnParentPath(
|
||||
parentSuspense,
|
||||
renderLanes,
|
||||
workInProgress,
|
||||
);
|
||||
nextFiber = fiber.sibling;
|
||||
} else {
|
||||
// Traverse down.
|
||||
nextFiber = fiber.child;
|
||||
}
|
||||
|
||||
if (nextFiber !== null) {
|
||||
// Set the return pointer of the child to the work-in-progress fiber.
|
||||
nextFiber.return = fiber;
|
||||
} else {
|
||||
// No child. Traverse to next sibling.
|
||||
nextFiber = fiber;
|
||||
while (nextFiber !== null) {
|
||||
if (nextFiber === workInProgress) {
|
||||
// We're back to the root of this subtree. Exit.
|
||||
nextFiber = null;
|
||||
break;
|
||||
}
|
||||
const sibling = nextFiber.sibling;
|
||||
if (sibling !== null) {
|
||||
// Set the return pointer of the sibling to the work-in-progress fiber.
|
||||
sibling.return = nextFiber.return;
|
||||
nextFiber = sibling;
|
||||
break;
|
||||
}
|
||||
// No more siblings. Traverse up.
|
||||
nextFiber = nextFiber.return;
|
||||
}
|
||||
}
|
||||
fiber = nextFiber;
|
||||
}
|
||||
}
|
||||
|
||||
function propagateContextChanges<T>(
|
||||
workInProgress: Fiber,
|
||||
contexts: Array<any>,
|
||||
renderLanes: Lanes,
|
||||
forcePropagateEntireTree: boolean,
|
||||
): void {
|
||||
// Only used by lazy implementation
|
||||
if (!enableLazyContextPropagation) {
|
||||
return;
|
||||
}
|
||||
let fiber = workInProgress.child;
|
||||
if (fiber !== null) {
|
||||
// Set the return pointer of the child to the work-in-progress fiber.
|
||||
fiber.return = workInProgress;
|
||||
}
|
||||
while (fiber !== null) {
|
||||
let nextFiber;
|
||||
|
||||
// Visit this fiber.
|
||||
const list = fiber.dependencies;
|
||||
if (list !== null) {
|
||||
nextFiber = fiber.child;
|
||||
|
||||
let dep = list.firstContext;
|
||||
findChangedDep: while (dep !== null) {
|
||||
// Assigning these to constants to help Flow
|
||||
const dependency = dep;
|
||||
const consumer = fiber;
|
||||
findContext: for (let i = 0; i < contexts.length; i++) {
|
||||
const context: ReactContext<T> = contexts[i];
|
||||
// Check if the context matches.
|
||||
// TODO: Compare selected values to bail out early.
|
||||
if (dependency.context === context) {
|
||||
// Match! Schedule an update on this fiber.
|
||||
|
||||
// In the lazy implementation, don't mark a dirty flag on the
|
||||
// dependency itself. Not all changes are propagated, so we can't
|
||||
// rely on the propagation function alone to determine whether
|
||||
// something has changed; the consumer will check. In the future, we
|
||||
// could add back a dirty flag as an optimization to avoid double
|
||||
// checking, but until we have selectors it's not really worth
|
||||
// the trouble.
|
||||
consumer.lanes = mergeLanes(consumer.lanes, renderLanes);
|
||||
const alternate = consumer.alternate;
|
||||
if (alternate !== null) {
|
||||
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
|
||||
}
|
||||
scheduleContextWorkOnParentPath(
|
||||
consumer.return,
|
||||
renderLanes,
|
||||
workInProgress,
|
||||
);
|
||||
|
||||
if (!forcePropagateEntireTree) {
|
||||
// During lazy propagation, when we find a match, we can defer
|
||||
// propagating changes to the children, because we're going to
|
||||
// visit them during render. We should continue propagating the
|
||||
// siblings, though
|
||||
nextFiber = null;
|
||||
}
|
||||
|
||||
// Since we already found a match, we can stop traversing the
|
||||
// dependency list.
|
||||
break findChangedDep;
|
||||
}
|
||||
}
|
||||
dep = dependency.next;
|
||||
}
|
||||
} else if (fiber.tag === DehydratedFragment) {
|
||||
// If a dehydrated suspense boundary is in this subtree, we don't know
|
||||
// if it will have any context consumers in it. The best we can do is
|
||||
// mark it as having updates.
|
||||
const parentSuspense = fiber.return;
|
||||
|
||||
if (parentSuspense === null) {
|
||||
throw new Error(
|
||||
'We just came from a parent so we must have had a parent. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
|
||||
parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
|
||||
const alternate = parentSuspense.alternate;
|
||||
if (alternate !== null) {
|
||||
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
|
||||
}
|
||||
// This is intentionally passing this fiber as the parent
|
||||
// because we want to schedule this fiber as having work
|
||||
// on its children. We'll use the childLanes on
|
||||
// this fiber to indicate that a context has changed.
|
||||
scheduleContextWorkOnParentPath(
|
||||
parentSuspense,
|
||||
renderLanes,
|
||||
workInProgress,
|
||||
);
|
||||
nextFiber = null;
|
||||
} else {
|
||||
// Traverse down.
|
||||
nextFiber = fiber.child;
|
||||
}
|
||||
|
||||
if (nextFiber !== null) {
|
||||
// Set the return pointer of the child to the work-in-progress fiber.
|
||||
nextFiber.return = fiber;
|
||||
} else {
|
||||
// No child. Traverse to next sibling.
|
||||
nextFiber = fiber;
|
||||
while (nextFiber !== null) {
|
||||
if (nextFiber === workInProgress) {
|
||||
// We're back to the root of this subtree. Exit.
|
||||
nextFiber = null;
|
||||
break;
|
||||
}
|
||||
const sibling = nextFiber.sibling;
|
||||
if (sibling !== null) {
|
||||
// Set the return pointer of the sibling to the work-in-progress fiber.
|
||||
sibling.return = nextFiber.return;
|
||||
nextFiber = sibling;
|
||||
break;
|
||||
}
|
||||
// No more siblings. Traverse up.
|
||||
nextFiber = nextFiber.return;
|
||||
}
|
||||
}
|
||||
fiber = nextFiber;
|
||||
}
|
||||
}
|
||||
|
||||
export function lazilyPropagateParentContextChanges(
|
||||
current: Fiber,
|
||||
workInProgress: Fiber,
|
||||
renderLanes: Lanes,
|
||||
) {
|
||||
const forcePropagateEntireTree = false;
|
||||
propagateParentContextChanges(
|
||||
current,
|
||||
workInProgress,
|
||||
renderLanes,
|
||||
forcePropagateEntireTree,
|
||||
);
|
||||
}
|
||||
|
||||
// Used for propagating a deferred tree (Suspense, Offscreen). We must propagate
|
||||
// to the entire subtree, because we won't revisit it until after the current
|
||||
// render has completed, at which point we'll have lost track of which providers
|
||||
// have changed.
|
||||
export function propagateParentContextChangesToDeferredTree(
|
||||
current: Fiber,
|
||||
workInProgress: Fiber,
|
||||
renderLanes: Lanes,
|
||||
) {
|
||||
const forcePropagateEntireTree = true;
|
||||
propagateParentContextChanges(
|
||||
current,
|
||||
workInProgress,
|
||||
renderLanes,
|
||||
forcePropagateEntireTree,
|
||||
);
|
||||
}
|
||||
|
||||
function propagateParentContextChanges(
|
||||
current: Fiber,
|
||||
workInProgress: Fiber,
|
||||
renderLanes: Lanes,
|
||||
forcePropagateEntireTree: boolean,
|
||||
) {
|
||||
if (!enableLazyContextPropagation) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect all the parent providers that changed. Since this is usually small
|
||||
// number, we use an Array instead of Set.
|
||||
let contexts = null;
|
||||
let parent: null | Fiber = workInProgress;
|
||||
let isInsidePropagationBailout = false;
|
||||
while (parent !== null) {
|
||||
if (!isInsidePropagationBailout) {
|
||||
if ((parent.flags & NeedsPropagation) !== NoFlags) {
|
||||
isInsidePropagationBailout = true;
|
||||
} else if ((parent.flags & DidPropagateContext) !== NoFlags) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent.tag === ContextProvider) {
|
||||
const currentParent = parent.alternate;
|
||||
|
||||
if (currentParent === null) {
|
||||
throw new Error('Should have a current fiber. This is a bug in React.');
|
||||
}
|
||||
|
||||
const oldProps = currentParent.memoizedProps;
|
||||
if (oldProps !== null) {
|
||||
const providerType: ReactProviderType<any> = parent.type;
|
||||
const context: ReactContext<any> = providerType._context;
|
||||
|
||||
const newProps = parent.pendingProps;
|
||||
const newValue = newProps.value;
|
||||
|
||||
const oldValue = oldProps.value;
|
||||
|
||||
if (!is(newValue, oldValue)) {
|
||||
if (contexts !== null) {
|
||||
contexts.push(context);
|
||||
} else {
|
||||
contexts = [context];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parent = parent.return;
|
||||
}
|
||||
|
||||
if (contexts !== null) {
|
||||
// If there were any changed providers, search through the children and
|
||||
// propagate their changes.
|
||||
propagateContextChanges(
|
||||
workInProgress,
|
||||
contexts,
|
||||
renderLanes,
|
||||
forcePropagateEntireTree,
|
||||
);
|
||||
}
|
||||
|
||||
// This is an optimization so that we only propagate once per subtree. If a
|
||||
// deeply nested child bails out, and it calls this propagation function, it
|
||||
// uses this flag to know that the remaining ancestor providers have already
|
||||
// been propagated.
|
||||
//
|
||||
// NOTE: This optimization is only necessary because we sometimes enter the
|
||||
// begin phase of nodes that don't have any work scheduled on them —
|
||||
// specifically, the siblings of a node that _does_ have scheduled work. The
|
||||
// siblings will bail out and call this function again, even though we already
|
||||
// propagated content changes to it and its subtree. So we use this flag to
|
||||
// mark that the parent providers already propagated.
|
||||
//
|
||||
// Unfortunately, though, we need to ignore this flag when we're inside a
|
||||
// tree whose context propagation was deferred — that's what the
|
||||
// `NeedsPropagation` flag is for.
|
||||
//
|
||||
// If we could instead bail out before entering the siblings' begin phase,
|
||||
// then we could remove both `DidPropagateContext` and `NeedsPropagation`.
|
||||
// Consider this as part of the next refactor to the fiber tree structure.
|
||||
workInProgress.flags |= DidPropagateContext;
|
||||
}
|
||||
|
||||
export function checkIfContextChanged(
|
||||
currentDependencies: Dependencies,
|
||||
): boolean {
|
||||
if (!enableLazyContextPropagation) {
|
||||
return false;
|
||||
}
|
||||
// Iterate over the current dependencies to see if something changed. This
|
||||
// only gets called if props and state has already bailed out, so it's a
|
||||
// relatively uncommon path, except at the root of a changed subtree.
|
||||
// Alternatively, we could move these comparisons into `readContext`, but
|
||||
// that's a much hotter path, so I think this is an appropriate trade off.
|
||||
let dependency = currentDependencies.firstContext;
|
||||
while (dependency !== null) {
|
||||
const context = dependency.context;
|
||||
const newValue = isPrimaryRenderer
|
||||
? context._currentValue
|
||||
: context._currentValue2;
|
||||
const oldValue = dependency.memoizedValue;
|
||||
if (!is(newValue, oldValue)) {
|
||||
return true;
|
||||
}
|
||||
dependency = dependency.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function prepareToReadContext(
|
||||
workInProgress: Fiber,
|
||||
renderLanes: Lanes,
|
||||
): void {
|
||||
currentlyRenderingFiber = workInProgress;
|
||||
lastContextDependency = null;
|
||||
lastFullyObservedContext = null;
|
||||
|
||||
const dependencies = workInProgress.dependencies;
|
||||
if (dependencies !== null) {
|
||||
if (enableLazyContextPropagation) {
|
||||
// Reset the work-in-progress list
|
||||
dependencies.firstContext = null;
|
||||
} else {
|
||||
const firstContext = dependencies.firstContext;
|
||||
if (firstContext !== null) {
|
||||
if (includesSomeLane(dependencies.lanes, renderLanes)) {
|
||||
// Context list has a pending update. Mark that this fiber performed work.
|
||||
markWorkInProgressReceivedUpdate();
|
||||
}
|
||||
// Reset the work-in-progress list
|
||||
dependencies.firstContext = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function readContext<T>(context: ReactContext<T>): T {
|
||||
if (__DEV__) {
|
||||
// This warning would fire if you read context inside a Hook like useMemo.
|
||||
// Unlike the class check below, it's not enforced in production for perf.
|
||||
if (isDisallowedContextReadInDEV) {
|
||||
console.error(
|
||||
'Context can only be read while React is rendering. ' +
|
||||
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
|
||||
'In function components, you can read it directly in the function body, but not ' +
|
||||
'inside Hooks like useReducer() or useMemo().',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const value = isPrimaryRenderer
|
||||
? context._currentValue
|
||||
: context._currentValue2;
|
||||
|
||||
if (lastFullyObservedContext === context) {
|
||||
// Nothing to do. We already observe everything in this context.
|
||||
} else {
|
||||
const contextItem = {
|
||||
context: ((context: any): ReactContext<mixed>),
|
||||
memoizedValue: value,
|
||||
next: null,
|
||||
};
|
||||
|
||||
if (lastContextDependency === null) {
|
||||
if (currentlyRenderingFiber === null) {
|
||||
throw new Error(
|
||||
'Context can only be read while React is rendering. ' +
|
||||
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
|
||||
'In function components, you can read it directly in the function body, but not ' +
|
||||
'inside Hooks like useReducer() or useMemo().',
|
||||
);
|
||||
}
|
||||
|
||||
// This is the first dependency for this component. Create a new list.
|
||||
lastContextDependency = contextItem;
|
||||
currentlyRenderingFiber.dependencies = {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
if (enableLazyContextPropagation) {
|
||||
currentlyRenderingFiber.flags |= NeedsPropagation;
|
||||
}
|
||||
} else {
|
||||
// Append a new context item.
|
||||
lastContextDependency = lastContextDependency.next = contextItem;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
|
@ -9,12 +9,12 @@
|
|||
|
||||
import type {ReactNodeList, OffscreenMode, Wakeable} from 'shared/ReactTypes';
|
||||
import type {Lanes} from './ReactFiberLane.old';
|
||||
import type {SpawnedCachePool} from './ReactFiberCacheComponent.new';
|
||||
import type {SpawnedCachePool} from './ReactFiberCacheComponent.old';
|
||||
import type {Fiber} from './ReactInternalTypes';
|
||||
import type {
|
||||
Transition,
|
||||
TracingMarkerInstance,
|
||||
} from './ReactFiberTracingMarkerComponent.new';
|
||||
} from './ReactFiberTracingMarkerComponent.old';
|
||||
|
||||
export type OffscreenProps = {
|
||||
// TODO: Pick an API before exposing the Offscreen type. I've chosen an enum
|
||||
|
|
|
@ -1,202 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {enableNewReconciler} from 'shared/ReactFeatureFlags';
|
||||
|
||||
// The entry file imports either the old or new version of the reconciler.
|
||||
// During build and testing, this indirection is always shimmed with the actual
|
||||
// modules, otherwise both reconcilers would be initialized. So this is really
|
||||
// only here for Flow purposes.
|
||||
|
||||
import {
|
||||
createContainer as createContainer_old,
|
||||
createHydrationContainer as createHydrationContainer_old,
|
||||
updateContainer as updateContainer_old,
|
||||
batchedUpdates as batchedUpdates_old,
|
||||
deferredUpdates as deferredUpdates_old,
|
||||
discreteUpdates as discreteUpdates_old,
|
||||
flushControlled as flushControlled_old,
|
||||
flushSync as flushSync_old,
|
||||
isAlreadyRendering as isAlreadyRendering_old,
|
||||
flushPassiveEffects as flushPassiveEffects_old,
|
||||
getPublicRootInstance as getPublicRootInstance_old,
|
||||
attemptSynchronousHydration as attemptSynchronousHydration_old,
|
||||
attemptDiscreteHydration as attemptDiscreteHydration_old,
|
||||
attemptContinuousHydration as attemptContinuousHydration_old,
|
||||
attemptHydrationAtCurrentPriority as attemptHydrationAtCurrentPriority_old,
|
||||
findHostInstance as findHostInstance_old,
|
||||
findHostInstanceWithWarning as findHostInstanceWithWarning_old,
|
||||
findHostInstanceWithNoPortals as findHostInstanceWithNoPortals_old,
|
||||
shouldError as shouldError_old,
|
||||
shouldSuspend as shouldSuspend_old,
|
||||
injectIntoDevTools as injectIntoDevTools_old,
|
||||
createPortal as createPortal_old,
|
||||
createComponentSelector as createComponentSelector_old,
|
||||
createHasPseudoClassSelector as createHasPseudoClassSelector_old,
|
||||
createRoleSelector as createRoleSelector_old,
|
||||
createTestNameSelector as createTestNameSelector_old,
|
||||
createTextSelector as createTextSelector_old,
|
||||
getFindAllNodesFailureDescription as getFindAllNodesFailureDescription_old,
|
||||
findAllNodes as findAllNodes_old,
|
||||
findBoundingRects as findBoundingRects_old,
|
||||
focusWithin as focusWithin_old,
|
||||
observeVisibleRects as observeVisibleRects_old,
|
||||
registerMutableSourceForHydration as registerMutableSourceForHydration_old,
|
||||
runWithPriority as runWithPriority_old,
|
||||
getCurrentUpdatePriority as getCurrentUpdatePriority_old,
|
||||
} from './ReactFiberReconciler.old';
|
||||
|
||||
import {
|
||||
createContainer as createContainer_new,
|
||||
createHydrationContainer as createHydrationContainer_new,
|
||||
updateContainer as updateContainer_new,
|
||||
batchedUpdates as batchedUpdates_new,
|
||||
deferredUpdates as deferredUpdates_new,
|
||||
discreteUpdates as discreteUpdates_new,
|
||||
flushControlled as flushControlled_new,
|
||||
flushSync as flushSync_new,
|
||||
isAlreadyRendering as isAlreadyRendering_new,
|
||||
flushPassiveEffects as flushPassiveEffects_new,
|
||||
getPublicRootInstance as getPublicRootInstance_new,
|
||||
attemptSynchronousHydration as attemptSynchronousHydration_new,
|
||||
attemptDiscreteHydration as attemptDiscreteHydration_new,
|
||||
attemptContinuousHydration as attemptContinuousHydration_new,
|
||||
attemptHydrationAtCurrentPriority as attemptHydrationAtCurrentPriority_new,
|
||||
findHostInstance as findHostInstance_new,
|
||||
findHostInstanceWithWarning as findHostInstanceWithWarning_new,
|
||||
findHostInstanceWithNoPortals as findHostInstanceWithNoPortals_new,
|
||||
shouldError as shouldError_new,
|
||||
shouldSuspend as shouldSuspend_new,
|
||||
injectIntoDevTools as injectIntoDevTools_new,
|
||||
createPortal as createPortal_new,
|
||||
createComponentSelector as createComponentSelector_new,
|
||||
createHasPseudoClassSelector as createHasPseudoClassSelector_new,
|
||||
createRoleSelector as createRoleSelector_new,
|
||||
createTestNameSelector as createTestNameSelector_new,
|
||||
createTextSelector as createTextSelector_new,
|
||||
getFindAllNodesFailureDescription as getFindAllNodesFailureDescription_new,
|
||||
findAllNodes as findAllNodes_new,
|
||||
findBoundingRects as findBoundingRects_new,
|
||||
focusWithin as focusWithin_new,
|
||||
observeVisibleRects as observeVisibleRects_new,
|
||||
registerMutableSourceForHydration as registerMutableSourceForHydration_new,
|
||||
runWithPriority as runWithPriority_new,
|
||||
getCurrentUpdatePriority as getCurrentUpdatePriority_new,
|
||||
} from './ReactFiberReconciler.new';
|
||||
|
||||
export const createContainer: typeof createContainer_new = enableNewReconciler
|
||||
? createContainer_new
|
||||
: createContainer_old;
|
||||
export const createHydrationContainer: typeof createHydrationContainer_new = enableNewReconciler
|
||||
? createHydrationContainer_new
|
||||
: createHydrationContainer_old;
|
||||
export const updateContainer: typeof updateContainer_new = enableNewReconciler
|
||||
? updateContainer_new
|
||||
: updateContainer_old;
|
||||
export const batchedUpdates: typeof batchedUpdates_new = enableNewReconciler
|
||||
? batchedUpdates_new
|
||||
: batchedUpdates_old;
|
||||
export const deferredUpdates: typeof deferredUpdates_new = enableNewReconciler
|
||||
? deferredUpdates_new
|
||||
: deferredUpdates_old;
|
||||
export const discreteUpdates: typeof discreteUpdates_new = enableNewReconciler
|
||||
? discreteUpdates_new
|
||||
: discreteUpdates_old;
|
||||
export const flushControlled: typeof flushControlled_new = enableNewReconciler
|
||||
? flushControlled_new
|
||||
: flushControlled_old;
|
||||
export const flushSync: typeof flushSync_new = enableNewReconciler
|
||||
? flushSync_new
|
||||
: flushSync_old;
|
||||
export const isAlreadyRendering: typeof isAlreadyRendering_new = enableNewReconciler
|
||||
? isAlreadyRendering_new
|
||||
: isAlreadyRendering_old;
|
||||
export const flushPassiveEffects: typeof flushPassiveEffects_new = enableNewReconciler
|
||||
? flushPassiveEffects_new
|
||||
: flushPassiveEffects_old;
|
||||
export const getPublicRootInstance: typeof getPublicRootInstance_new = enableNewReconciler
|
||||
? getPublicRootInstance_new
|
||||
: getPublicRootInstance_old;
|
||||
export const attemptSynchronousHydration: typeof attemptSynchronousHydration_new = enableNewReconciler
|
||||
? attemptSynchronousHydration_new
|
||||
: attemptSynchronousHydration_old;
|
||||
export const attemptDiscreteHydration: typeof attemptDiscreteHydration_new = enableNewReconciler
|
||||
? attemptDiscreteHydration_new
|
||||
: attemptDiscreteHydration_old;
|
||||
export const attemptContinuousHydration: typeof attemptContinuousHydration_new = enableNewReconciler
|
||||
? attemptContinuousHydration_new
|
||||
: attemptContinuousHydration_old;
|
||||
export const attemptHydrationAtCurrentPriority: typeof attemptHydrationAtCurrentPriority_new = enableNewReconciler
|
||||
? attemptHydrationAtCurrentPriority_new
|
||||
: attemptHydrationAtCurrentPriority_old;
|
||||
export const getCurrentUpdatePriority: typeof getCurrentUpdatePriority_new = enableNewReconciler
|
||||
? getCurrentUpdatePriority_new
|
||||
: /* $FlowFixMe[incompatible-type] opaque types EventPriority from new and old
|
||||
* are incompatible. */
|
||||
getCurrentUpdatePriority_old;
|
||||
export const findHostInstance: typeof findHostInstance_new = enableNewReconciler
|
||||
? findHostInstance_new
|
||||
: findHostInstance_old;
|
||||
export const findHostInstanceWithWarning: typeof findHostInstanceWithWarning_new = enableNewReconciler
|
||||
? findHostInstanceWithWarning_new
|
||||
: findHostInstanceWithWarning_old;
|
||||
export const findHostInstanceWithNoPortals: typeof findHostInstanceWithNoPortals_new = enableNewReconciler
|
||||
? findHostInstanceWithNoPortals_new
|
||||
: findHostInstanceWithNoPortals_old;
|
||||
export const shouldError: typeof shouldError_new = enableNewReconciler
|
||||
? shouldError_new
|
||||
: shouldError_old;
|
||||
export const shouldSuspend: typeof shouldSuspend_new = enableNewReconciler
|
||||
? shouldSuspend_new
|
||||
: shouldSuspend_old;
|
||||
export const injectIntoDevTools: typeof injectIntoDevTools_new = enableNewReconciler
|
||||
? injectIntoDevTools_new
|
||||
: injectIntoDevTools_old;
|
||||
export const createPortal: typeof createPortal_new = enableNewReconciler
|
||||
? createPortal_new
|
||||
: createPortal_old;
|
||||
export const createComponentSelector: typeof createComponentSelector_new = enableNewReconciler
|
||||
? createComponentSelector_new
|
||||
: createComponentSelector_old;
|
||||
|
||||
export const createHasPseudoClassSelector: typeof createHasPseudoClassSelector_new = enableNewReconciler
|
||||
? createHasPseudoClassSelector_new
|
||||
: createHasPseudoClassSelector_old;
|
||||
export const createRoleSelector: typeof createRoleSelector_new = enableNewReconciler
|
||||
? createRoleSelector_new
|
||||
: createRoleSelector_old;
|
||||
export const createTextSelector: typeof createTextSelector_new = enableNewReconciler
|
||||
? createTextSelector_new
|
||||
: createTextSelector_old;
|
||||
export const createTestNameSelector: typeof createTestNameSelector_new = enableNewReconciler
|
||||
? createTestNameSelector_new
|
||||
: createTestNameSelector_old;
|
||||
export const getFindAllNodesFailureDescription: typeof getFindAllNodesFailureDescription_new = enableNewReconciler
|
||||
? getFindAllNodesFailureDescription_new
|
||||
: getFindAllNodesFailureDescription_old;
|
||||
export const findAllNodes: typeof findAllNodes_new = enableNewReconciler
|
||||
? findAllNodes_new
|
||||
: findAllNodes_old;
|
||||
export const findBoundingRects: typeof findBoundingRects_new = enableNewReconciler
|
||||
? findBoundingRects_new
|
||||
: findBoundingRects_old;
|
||||
export const focusWithin: typeof focusWithin_new = enableNewReconciler
|
||||
? focusWithin_new
|
||||
: focusWithin_old;
|
||||
export const observeVisibleRects: typeof observeVisibleRects_new = enableNewReconciler
|
||||
? observeVisibleRects_new
|
||||
: observeVisibleRects_old;
|
||||
export const registerMutableSourceForHydration: typeof registerMutableSourceForHydration_new = enableNewReconciler
|
||||
? registerMutableSourceForHydration_new
|
||||
: registerMutableSourceForHydration_old;
|
||||
/* $FlowFixMe[incompatible-type] opaque types EventPriority from new and old
|
||||
* are incompatible. */
|
||||
export const runWithPriority: typeof runWithPriority_new = enableNewReconciler
|
||||
? runWithPriority_new
|
||||
: runWithPriority_old;
|
|
@ -1,845 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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,
|
||||
FiberRoot,
|
||||
SuspenseHydrationCallbacks,
|
||||
TransitionTracingCallbacks,
|
||||
} from './ReactInternalTypes';
|
||||
import type {RootTag} from './ReactRootTags';
|
||||
import type {
|
||||
Instance,
|
||||
TextInstance,
|
||||
Container,
|
||||
PublicInstance,
|
||||
RendererInspectionConfig,
|
||||
} from './ReactFiberHostConfig';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
import type {Lane} from './ReactFiberLane.new';
|
||||
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
|
||||
|
||||
import {
|
||||
findCurrentHostFiber,
|
||||
findCurrentHostFiberWithNoPortals,
|
||||
} from './ReactFiberTreeReflection';
|
||||
import {get as getInstance} from 'shared/ReactInstanceMap';
|
||||
import {
|
||||
HostComponent,
|
||||
HostSingleton,
|
||||
ClassComponent,
|
||||
HostRoot,
|
||||
SuspenseComponent,
|
||||
} from './ReactWorkTags';
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
import isArray from 'shared/isArray';
|
||||
import {enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {getPublicInstance} from './ReactFiberHostConfig';
|
||||
import {
|
||||
findCurrentUnmaskedContext,
|
||||
processChildContext,
|
||||
emptyContextObject,
|
||||
isContextProvider as isLegacyContextProvider,
|
||||
} from './ReactFiberContext.new';
|
||||
import {createFiberRoot} from './ReactFiberRoot.new';
|
||||
import {isRootDehydrated} from './ReactFiberShellHydration';
|
||||
import {
|
||||
injectInternals,
|
||||
markRenderScheduled,
|
||||
onScheduleRoot,
|
||||
} from './ReactFiberDevToolsHook.new';
|
||||
import {
|
||||
requestEventTime,
|
||||
requestUpdateLane,
|
||||
scheduleUpdateOnFiber,
|
||||
scheduleInitialHydrationOnRoot,
|
||||
flushRoot,
|
||||
batchedUpdates,
|
||||
flushSync,
|
||||
isAlreadyRendering,
|
||||
flushControlled,
|
||||
deferredUpdates,
|
||||
discreteUpdates,
|
||||
flushPassiveEffects,
|
||||
} from './ReactFiberWorkLoop.new';
|
||||
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new';
|
||||
import {
|
||||
createUpdate,
|
||||
enqueueUpdate,
|
||||
entangleTransitions,
|
||||
} from './ReactFiberClassUpdateQueue.new';
|
||||
import {
|
||||
isRendering as ReactCurrentFiberIsRendering,
|
||||
current as ReactCurrentFiberCurrent,
|
||||
resetCurrentFiber as resetCurrentDebugFiberInDEV,
|
||||
setCurrentFiber as setCurrentDebugFiberInDEV,
|
||||
} from './ReactCurrentFiber';
|
||||
import {StrictLegacyMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
SyncLane,
|
||||
SelectiveHydrationLane,
|
||||
NoTimestamp,
|
||||
getHighestPriorityPendingLanes,
|
||||
higherPriorityLane,
|
||||
} from './ReactFiberLane.new';
|
||||
import {
|
||||
getCurrentUpdatePriority,
|
||||
runWithPriority,
|
||||
} from './ReactEventPriorities.new';
|
||||
import {
|
||||
scheduleRefresh,
|
||||
scheduleRoot,
|
||||
setRefreshHandler,
|
||||
findHostInstancesForRefresh,
|
||||
} from './ReactFiberHotReloading.new';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
|
||||
export {createPortal} from './ReactPortal';
|
||||
export {
|
||||
createComponentSelector,
|
||||
createHasPseudoClassSelector,
|
||||
createRoleSelector,
|
||||
createTestNameSelector,
|
||||
createTextSelector,
|
||||
getFindAllNodesFailureDescription,
|
||||
findAllNodes,
|
||||
findBoundingRects,
|
||||
focusWithin,
|
||||
observeVisibleRects,
|
||||
} from './ReactTestSelectors';
|
||||
|
||||
type OpaqueRoot = FiberRoot;
|
||||
|
||||
// 0 is PROD, 1 is DEV.
|
||||
// Might add PROFILE later.
|
||||
type BundleType = 0 | 1;
|
||||
|
||||
type DevToolsConfig = {
|
||||
bundleType: BundleType,
|
||||
version: string,
|
||||
rendererPackageName: string,
|
||||
// Note: this actually *does* depend on Fiber internal fields.
|
||||
// Used by "inspect clicked DOM element" in React DevTools.
|
||||
findFiberByHostInstance?: (instance: Instance | TextInstance) => Fiber | null,
|
||||
rendererConfig?: RendererInspectionConfig,
|
||||
};
|
||||
|
||||
let didWarnAboutNestedUpdates;
|
||||
let didWarnAboutFindNodeInStrictMode;
|
||||
|
||||
if (__DEV__) {
|
||||
didWarnAboutNestedUpdates = false;
|
||||
didWarnAboutFindNodeInStrictMode = {};
|
||||
}
|
||||
|
||||
function getContextForSubtree(
|
||||
parentComponent: ?React$Component<any, any>,
|
||||
): Object {
|
||||
if (!parentComponent) {
|
||||
return emptyContextObject;
|
||||
}
|
||||
|
||||
const fiber = getInstance(parentComponent);
|
||||
const parentContext = findCurrentUnmaskedContext(fiber);
|
||||
|
||||
if (fiber.tag === ClassComponent) {
|
||||
const Component = fiber.type;
|
||||
if (isLegacyContextProvider(Component)) {
|
||||
return processChildContext(fiber, Component, parentContext);
|
||||
}
|
||||
}
|
||||
|
||||
return parentContext;
|
||||
}
|
||||
|
||||
function findHostInstance(component: Object): PublicInstance | null {
|
||||
const fiber = getInstance(component);
|
||||
if (fiber === undefined) {
|
||||
if (typeof component.render === 'function') {
|
||||
throw new Error('Unable to find node on an unmounted component.');
|
||||
} else {
|
||||
const keys = Object.keys(component).join(',');
|
||||
throw new Error(
|
||||
`Argument appears to not be a ReactComponent. Keys: ${keys}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const hostFiber = findCurrentHostFiber(fiber);
|
||||
if (hostFiber === null) {
|
||||
return null;
|
||||
}
|
||||
return hostFiber.stateNode;
|
||||
}
|
||||
|
||||
function findHostInstanceWithWarning(
|
||||
component: Object,
|
||||
methodName: string,
|
||||
): PublicInstance | null {
|
||||
if (__DEV__) {
|
||||
const fiber = getInstance(component);
|
||||
if (fiber === undefined) {
|
||||
if (typeof component.render === 'function') {
|
||||
throw new Error('Unable to find node on an unmounted component.');
|
||||
} else {
|
||||
const keys = Object.keys(component).join(',');
|
||||
throw new Error(
|
||||
`Argument appears to not be a ReactComponent. Keys: ${keys}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const hostFiber = findCurrentHostFiber(fiber);
|
||||
if (hostFiber === null) {
|
||||
return null;
|
||||
}
|
||||
if (hostFiber.mode & StrictLegacyMode) {
|
||||
const componentName = getComponentNameFromFiber(fiber) || 'Component';
|
||||
if (!didWarnAboutFindNodeInStrictMode[componentName]) {
|
||||
didWarnAboutFindNodeInStrictMode[componentName] = true;
|
||||
|
||||
const previousFiber = ReactCurrentFiberCurrent;
|
||||
try {
|
||||
setCurrentDebugFiberInDEV(hostFiber);
|
||||
if (fiber.mode & StrictLegacyMode) {
|
||||
console.error(
|
||||
'%s is deprecated in StrictMode. ' +
|
||||
'%s was passed an instance of %s which is inside StrictMode. ' +
|
||||
'Instead, add a ref directly to the element you want to reference. ' +
|
||||
'Learn more about using refs safely here: ' +
|
||||
'https://reactjs.org/link/strict-mode-find-node',
|
||||
methodName,
|
||||
methodName,
|
||||
componentName,
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
'%s is deprecated in StrictMode. ' +
|
||||
'%s was passed an instance of %s which renders StrictMode children. ' +
|
||||
'Instead, add a ref directly to the element you want to reference. ' +
|
||||
'Learn more about using refs safely here: ' +
|
||||
'https://reactjs.org/link/strict-mode-find-node',
|
||||
methodName,
|
||||
methodName,
|
||||
componentName,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
// Ideally this should reset to previous but this shouldn't be called in
|
||||
// render and there's another warning for that anyway.
|
||||
if (previousFiber) {
|
||||
setCurrentDebugFiberInDEV(previousFiber);
|
||||
} else {
|
||||
resetCurrentDebugFiberInDEV();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return hostFiber.stateNode;
|
||||
}
|
||||
return findHostInstance(component);
|
||||
}
|
||||
|
||||
export function createContainer(
|
||||
containerInfo: Container,
|
||||
tag: RootTag,
|
||||
hydrationCallbacks: null | SuspenseHydrationCallbacks,
|
||||
isStrictMode: boolean,
|
||||
concurrentUpdatesByDefaultOverride: null | boolean,
|
||||
identifierPrefix: string,
|
||||
onRecoverableError: (error: mixed) => void,
|
||||
transitionCallbacks: null | TransitionTracingCallbacks,
|
||||
): OpaqueRoot {
|
||||
const hydrate = false;
|
||||
const initialChildren = null;
|
||||
return createFiberRoot(
|
||||
containerInfo,
|
||||
tag,
|
||||
hydrate,
|
||||
initialChildren,
|
||||
hydrationCallbacks,
|
||||
isStrictMode,
|
||||
concurrentUpdatesByDefaultOverride,
|
||||
identifierPrefix,
|
||||
onRecoverableError,
|
||||
transitionCallbacks,
|
||||
);
|
||||
}
|
||||
|
||||
export function createHydrationContainer(
|
||||
initialChildren: ReactNodeList,
|
||||
// TODO: Remove `callback` when we delete legacy mode.
|
||||
callback: ?Function,
|
||||
containerInfo: Container,
|
||||
tag: RootTag,
|
||||
hydrationCallbacks: null | SuspenseHydrationCallbacks,
|
||||
isStrictMode: boolean,
|
||||
concurrentUpdatesByDefaultOverride: null | boolean,
|
||||
identifierPrefix: string,
|
||||
onRecoverableError: (error: mixed) => void,
|
||||
transitionCallbacks: null | TransitionTracingCallbacks,
|
||||
): OpaqueRoot {
|
||||
const hydrate = true;
|
||||
const root = createFiberRoot(
|
||||
containerInfo,
|
||||
tag,
|
||||
hydrate,
|
||||
initialChildren,
|
||||
hydrationCallbacks,
|
||||
isStrictMode,
|
||||
concurrentUpdatesByDefaultOverride,
|
||||
identifierPrefix,
|
||||
onRecoverableError,
|
||||
transitionCallbacks,
|
||||
);
|
||||
|
||||
// TODO: Move this to FiberRoot constructor
|
||||
root.context = getContextForSubtree(null);
|
||||
|
||||
// Schedule the initial render. In a hydration root, this is different from
|
||||
// a regular update because the initial render must match was was rendered
|
||||
// on the server.
|
||||
// NOTE: This update intentionally doesn't have a payload. We're only using
|
||||
// the update to schedule work on the root fiber (and, for legacy roots, to
|
||||
// enqueue the callback if one is provided).
|
||||
const current = root.current;
|
||||
const eventTime = requestEventTime();
|
||||
const lane = requestUpdateLane(current);
|
||||
const update = createUpdate(eventTime, lane);
|
||||
update.callback =
|
||||
callback !== undefined && callback !== null ? callback : null;
|
||||
enqueueUpdate(current, update, lane);
|
||||
scheduleInitialHydrationOnRoot(root, lane, eventTime);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
export function updateContainer(
|
||||
element: ReactNodeList,
|
||||
container: OpaqueRoot,
|
||||
parentComponent: ?React$Component<any, any>,
|
||||
callback: ?Function,
|
||||
): Lane {
|
||||
if (__DEV__) {
|
||||
onScheduleRoot(container, element);
|
||||
}
|
||||
const current = container.current;
|
||||
const eventTime = requestEventTime();
|
||||
const lane = requestUpdateLane(current);
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markRenderScheduled(lane);
|
||||
}
|
||||
|
||||
const context = getContextForSubtree(parentComponent);
|
||||
if (container.context === null) {
|
||||
container.context = context;
|
||||
} else {
|
||||
container.pendingContext = context;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (
|
||||
ReactCurrentFiberIsRendering &&
|
||||
ReactCurrentFiberCurrent !== null &&
|
||||
!didWarnAboutNestedUpdates
|
||||
) {
|
||||
didWarnAboutNestedUpdates = true;
|
||||
console.error(
|
||||
'Render methods should be a pure function of props and state; ' +
|
||||
'triggering nested component updates from render is not allowed. ' +
|
||||
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
|
||||
'Check the render method of %s.',
|
||||
getComponentNameFromFiber(ReactCurrentFiberCurrent) || 'Unknown',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const update = createUpdate(eventTime, lane);
|
||||
// Caution: React DevTools currently depends on this property
|
||||
// being called "element".
|
||||
update.payload = {element};
|
||||
|
||||
callback = callback === undefined ? null : callback;
|
||||
if (callback !== null) {
|
||||
if (__DEV__) {
|
||||
if (typeof callback !== 'function') {
|
||||
console.error(
|
||||
'render(...): Expected the last optional `callback` argument to be a ' +
|
||||
'function. Instead received: %s.',
|
||||
callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
update.callback = callback;
|
||||
}
|
||||
|
||||
const root = enqueueUpdate(current, update, lane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, current, lane, eventTime);
|
||||
entangleTransitions(root, current, lane);
|
||||
}
|
||||
|
||||
return lane;
|
||||
}
|
||||
|
||||
export {
|
||||
batchedUpdates,
|
||||
deferredUpdates,
|
||||
discreteUpdates,
|
||||
flushControlled,
|
||||
flushSync,
|
||||
isAlreadyRendering,
|
||||
flushPassiveEffects,
|
||||
};
|
||||
|
||||
export function getPublicRootInstance(
|
||||
container: OpaqueRoot,
|
||||
): React$Component<any, any> | PublicInstance | null {
|
||||
const containerFiber = container.current;
|
||||
if (!containerFiber.child) {
|
||||
return null;
|
||||
}
|
||||
switch (containerFiber.child.tag) {
|
||||
case HostSingleton:
|
||||
case HostComponent:
|
||||
return getPublicInstance(containerFiber.child.stateNode);
|
||||
default:
|
||||
return containerFiber.child.stateNode;
|
||||
}
|
||||
}
|
||||
|
||||
export function attemptSynchronousHydration(fiber: Fiber): void {
|
||||
switch (fiber.tag) {
|
||||
case HostRoot: {
|
||||
const root: FiberRoot = fiber.stateNode;
|
||||
if (isRootDehydrated(root)) {
|
||||
// Flush the first scheduled "update".
|
||||
const lanes = getHighestPriorityPendingLanes(root);
|
||||
flushRoot(root, lanes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
flushSync(() => {
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
const eventTime = requestEventTime();
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane, eventTime);
|
||||
}
|
||||
});
|
||||
// If we're still blocked after this, we need to increase
|
||||
// the priority of any promises resolving within this
|
||||
// boundary so that they next attempt also has higher pri.
|
||||
const retryLane = SyncLane;
|
||||
markRetryLaneIfNotHydrated(fiber, retryLane);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function markRetryLaneImpl(fiber: Fiber, retryLane: Lane) {
|
||||
const suspenseState: null | SuspenseState = fiber.memoizedState;
|
||||
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
||||
suspenseState.retryLane = higherPriorityLane(
|
||||
suspenseState.retryLane,
|
||||
retryLane,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Increases the priority of thenables when they resolve within this boundary.
|
||||
function markRetryLaneIfNotHydrated(fiber: Fiber, retryLane: Lane) {
|
||||
markRetryLaneImpl(fiber, retryLane);
|
||||
const alternate = fiber.alternate;
|
||||
if (alternate) {
|
||||
markRetryLaneImpl(alternate, retryLane);
|
||||
}
|
||||
}
|
||||
|
||||
export function attemptDiscreteHydration(fiber: Fiber): void {
|
||||
if (fiber.tag !== SuspenseComponent) {
|
||||
// We ignore HostRoots here because we can't increase
|
||||
// their priority and they should not suspend on I/O,
|
||||
// since you have to wrap anything that might suspend in
|
||||
// Suspense.
|
||||
return;
|
||||
}
|
||||
const lane = SyncLane;
|
||||
const root = enqueueConcurrentRenderForLane(fiber, lane);
|
||||
if (root !== null) {
|
||||
const eventTime = requestEventTime();
|
||||
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
||||
}
|
||||
markRetryLaneIfNotHydrated(fiber, lane);
|
||||
}
|
||||
|
||||
export function attemptContinuousHydration(fiber: Fiber): void {
|
||||
if (fiber.tag !== SuspenseComponent) {
|
||||
// We ignore HostRoots here because we can't increase
|
||||
// their priority and they should not suspend on I/O,
|
||||
// since you have to wrap anything that might suspend in
|
||||
// Suspense.
|
||||
return;
|
||||
}
|
||||
const lane = SelectiveHydrationLane;
|
||||
const root = enqueueConcurrentRenderForLane(fiber, lane);
|
||||
if (root !== null) {
|
||||
const eventTime = requestEventTime();
|
||||
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
||||
}
|
||||
markRetryLaneIfNotHydrated(fiber, lane);
|
||||
}
|
||||
|
||||
export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
|
||||
if (fiber.tag !== SuspenseComponent) {
|
||||
// We ignore HostRoots here because we can't increase
|
||||
// their priority other than synchronously flush it.
|
||||
return;
|
||||
}
|
||||
const lane = requestUpdateLane(fiber);
|
||||
const root = enqueueConcurrentRenderForLane(fiber, lane);
|
||||
if (root !== null) {
|
||||
const eventTime = requestEventTime();
|
||||
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
|
||||
}
|
||||
markRetryLaneIfNotHydrated(fiber, lane);
|
||||
}
|
||||
|
||||
export {getCurrentUpdatePriority, runWithPriority};
|
||||
|
||||
export {findHostInstance};
|
||||
|
||||
export {findHostInstanceWithWarning};
|
||||
|
||||
export function findHostInstanceWithNoPortals(
|
||||
fiber: Fiber,
|
||||
): PublicInstance | null {
|
||||
const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
|
||||
if (hostFiber === null) {
|
||||
return null;
|
||||
}
|
||||
return hostFiber.stateNode;
|
||||
}
|
||||
|
||||
let shouldErrorImpl: Fiber => ?boolean = fiber => null;
|
||||
|
||||
export function shouldError(fiber: Fiber): ?boolean {
|
||||
return shouldErrorImpl(fiber);
|
||||
}
|
||||
|
||||
let shouldSuspendImpl = fiber => false;
|
||||
|
||||
export function shouldSuspend(fiber: Fiber): boolean {
|
||||
return shouldSuspendImpl(fiber);
|
||||
}
|
||||
|
||||
let overrideHookState = null;
|
||||
let overrideHookStateDeletePath = null;
|
||||
let overrideHookStateRenamePath = null;
|
||||
let overrideProps = null;
|
||||
let overridePropsDeletePath = null;
|
||||
let overridePropsRenamePath = null;
|
||||
let scheduleUpdate = null;
|
||||
let setErrorHandler = null;
|
||||
let setSuspenseHandler = null;
|
||||
|
||||
if (__DEV__) {
|
||||
const copyWithDeleteImpl = (
|
||||
obj: Object | Array<any>,
|
||||
path: Array<string | number>,
|
||||
index: number,
|
||||
) => {
|
||||
const key = path[index];
|
||||
const updated = isArray(obj) ? obj.slice() : {...obj};
|
||||
if (index + 1 === path.length) {
|
||||
if (isArray(updated)) {
|
||||
updated.splice(((key: any): number), 1);
|
||||
} else {
|
||||
delete updated[key];
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
// $FlowFixMe number or string is fine here
|
||||
updated[key] = copyWithDeleteImpl(obj[key], path, index + 1);
|
||||
return updated;
|
||||
};
|
||||
|
||||
const copyWithDelete = (
|
||||
obj: Object | Array<any>,
|
||||
path: Array<string | number>,
|
||||
): Object | Array<any> => {
|
||||
return copyWithDeleteImpl(obj, path, 0);
|
||||
};
|
||||
|
||||
const copyWithRenameImpl = (
|
||||
obj: Object | Array<any>,
|
||||
oldPath: Array<string | number>,
|
||||
newPath: Array<string | number>,
|
||||
index: number,
|
||||
) => {
|
||||
const oldKey = oldPath[index];
|
||||
const updated = isArray(obj) ? obj.slice() : {...obj};
|
||||
if (index + 1 === oldPath.length) {
|
||||
const newKey = newPath[index];
|
||||
// $FlowFixMe number or string is fine here
|
||||
updated[newKey] = updated[oldKey];
|
||||
if (isArray(updated)) {
|
||||
updated.splice(((oldKey: any): number), 1);
|
||||
} else {
|
||||
delete updated[oldKey];
|
||||
}
|
||||
} else {
|
||||
// $FlowFixMe number or string is fine here
|
||||
updated[oldKey] = copyWithRenameImpl(
|
||||
// $FlowFixMe number or string is fine here
|
||||
obj[oldKey],
|
||||
oldPath,
|
||||
newPath,
|
||||
index + 1,
|
||||
);
|
||||
}
|
||||
return updated;
|
||||
};
|
||||
|
||||
const copyWithRename = (
|
||||
obj: Object | Array<any>,
|
||||
oldPath: Array<string | number>,
|
||||
newPath: Array<string | number>,
|
||||
): Object | Array<any> => {
|
||||
if (oldPath.length !== newPath.length) {
|
||||
console.warn('copyWithRename() expects paths of the same length');
|
||||
return;
|
||||
} else {
|
||||
for (let i = 0; i < newPath.length - 1; i++) {
|
||||
if (oldPath[i] !== newPath[i]) {
|
||||
console.warn(
|
||||
'copyWithRename() expects paths to be the same except for the deepest key',
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return copyWithRenameImpl(obj, oldPath, newPath, 0);
|
||||
};
|
||||
|
||||
const copyWithSetImpl = (
|
||||
obj: Object | Array<any>,
|
||||
path: Array<string | number>,
|
||||
index: number,
|
||||
value: any,
|
||||
) => {
|
||||
if (index >= path.length) {
|
||||
return value;
|
||||
}
|
||||
const key = path[index];
|
||||
const updated = isArray(obj) ? obj.slice() : {...obj};
|
||||
// $FlowFixMe number or string is fine here
|
||||
updated[key] = copyWithSetImpl(obj[key], path, index + 1, value);
|
||||
return updated;
|
||||
};
|
||||
|
||||
const copyWithSet = (
|
||||
obj: Object | Array<any>,
|
||||
path: Array<string | number>,
|
||||
value: any,
|
||||
): Object | Array<any> => {
|
||||
return copyWithSetImpl(obj, path, 0, value);
|
||||
};
|
||||
|
||||
const findHook = (fiber: Fiber, id: number) => {
|
||||
// For now, the "id" of stateful hooks is just the stateful hook index.
|
||||
// This may change in the future with e.g. nested hooks.
|
||||
let currentHook = fiber.memoizedState;
|
||||
while (currentHook !== null && id > 0) {
|
||||
currentHook = currentHook.next;
|
||||
id--;
|
||||
}
|
||||
return currentHook;
|
||||
};
|
||||
|
||||
// Support DevTools editable values for useState and useReducer.
|
||||
overrideHookState = (
|
||||
fiber: Fiber,
|
||||
id: number,
|
||||
path: Array<string | number>,
|
||||
value: any,
|
||||
) => {
|
||||
const hook = findHook(fiber, id);
|
||||
if (hook !== null) {
|
||||
const newState = copyWithSet(hook.memoizedState, path, value);
|
||||
hook.memoizedState = newState;
|
||||
hook.baseState = newState;
|
||||
|
||||
// We aren't actually adding an update to the queue,
|
||||
// because there is no update we can add for useReducer hooks that won't trigger an error.
|
||||
// (There's no appropriate action type for DevTools overrides.)
|
||||
// As a result though, React will see the scheduled update as a noop and bailout.
|
||||
// Shallow cloning props works as a workaround for now to bypass the bailout check.
|
||||
fiber.memoizedProps = {...fiber.memoizedProps};
|
||||
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
||||
}
|
||||
}
|
||||
};
|
||||
overrideHookStateDeletePath = (
|
||||
fiber: Fiber,
|
||||
id: number,
|
||||
path: Array<string | number>,
|
||||
) => {
|
||||
const hook = findHook(fiber, id);
|
||||
if (hook !== null) {
|
||||
const newState = copyWithDelete(hook.memoizedState, path);
|
||||
hook.memoizedState = newState;
|
||||
hook.baseState = newState;
|
||||
|
||||
// We aren't actually adding an update to the queue,
|
||||
// because there is no update we can add for useReducer hooks that won't trigger an error.
|
||||
// (There's no appropriate action type for DevTools overrides.)
|
||||
// As a result though, React will see the scheduled update as a noop and bailout.
|
||||
// Shallow cloning props works as a workaround for now to bypass the bailout check.
|
||||
fiber.memoizedProps = {...fiber.memoizedProps};
|
||||
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
||||
}
|
||||
}
|
||||
};
|
||||
overrideHookStateRenamePath = (
|
||||
fiber: Fiber,
|
||||
id: number,
|
||||
oldPath: Array<string | number>,
|
||||
newPath: Array<string | number>,
|
||||
) => {
|
||||
const hook = findHook(fiber, id);
|
||||
if (hook !== null) {
|
||||
const newState = copyWithRename(hook.memoizedState, oldPath, newPath);
|
||||
hook.memoizedState = newState;
|
||||
hook.baseState = newState;
|
||||
|
||||
// We aren't actually adding an update to the queue,
|
||||
// because there is no update we can add for useReducer hooks that won't trigger an error.
|
||||
// (There's no appropriate action type for DevTools overrides.)
|
||||
// As a result though, React will see the scheduled update as a noop and bailout.
|
||||
// Shallow cloning props works as a workaround for now to bypass the bailout check.
|
||||
fiber.memoizedProps = {...fiber.memoizedProps};
|
||||
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Support DevTools props for function components, forwardRef, memo, host components, etc.
|
||||
overrideProps = (fiber: Fiber, path: Array<string | number>, value: any) => {
|
||||
fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
|
||||
if (fiber.alternate) {
|
||||
fiber.alternate.pendingProps = fiber.pendingProps;
|
||||
}
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
||||
}
|
||||
};
|
||||
overridePropsDeletePath = (fiber: Fiber, path: Array<string | number>) => {
|
||||
fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path);
|
||||
if (fiber.alternate) {
|
||||
fiber.alternate.pendingProps = fiber.pendingProps;
|
||||
}
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
||||
}
|
||||
};
|
||||
overridePropsRenamePath = (
|
||||
fiber: Fiber,
|
||||
oldPath: Array<string | number>,
|
||||
newPath: Array<string | number>,
|
||||
) => {
|
||||
fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath);
|
||||
if (fiber.alternate) {
|
||||
fiber.alternate.pendingProps = fiber.pendingProps;
|
||||
}
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
||||
}
|
||||
};
|
||||
|
||||
scheduleUpdate = (fiber: Fiber) => {
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
|
||||
}
|
||||
};
|
||||
|
||||
setErrorHandler = (newShouldErrorImpl: Fiber => ?boolean) => {
|
||||
shouldErrorImpl = newShouldErrorImpl;
|
||||
};
|
||||
|
||||
setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
|
||||
shouldSuspendImpl = newShouldSuspendImpl;
|
||||
};
|
||||
}
|
||||
|
||||
function findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null {
|
||||
const hostFiber = findCurrentHostFiber(fiber);
|
||||
if (hostFiber === null) {
|
||||
return null;
|
||||
}
|
||||
return hostFiber.stateNode;
|
||||
}
|
||||
|
||||
function emptyFindFiberByHostInstance(
|
||||
instance: Instance | TextInstance,
|
||||
): Fiber | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getCurrentFiberForDevTools() {
|
||||
return ReactCurrentFiberCurrent;
|
||||
}
|
||||
|
||||
export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
|
||||
const {findFiberByHostInstance} = devToolsConfig;
|
||||
const {ReactCurrentDispatcher} = ReactSharedInternals;
|
||||
|
||||
return injectInternals({
|
||||
bundleType: devToolsConfig.bundleType,
|
||||
version: devToolsConfig.version,
|
||||
rendererPackageName: devToolsConfig.rendererPackageName,
|
||||
rendererConfig: devToolsConfig.rendererConfig,
|
||||
overrideHookState,
|
||||
overrideHookStateDeletePath,
|
||||
overrideHookStateRenamePath,
|
||||
overrideProps,
|
||||
overridePropsDeletePath,
|
||||
overridePropsRenamePath,
|
||||
setErrorHandler,
|
||||
setSuspenseHandler,
|
||||
scheduleUpdate,
|
||||
currentDispatcherRef: ReactCurrentDispatcher,
|
||||
findHostInstanceByFiber,
|
||||
findFiberByHostInstance:
|
||||
findFiberByHostInstance || emptyFindFiberByHostInstance,
|
||||
// React Refresh
|
||||
findHostInstancesForRefresh: __DEV__ ? findHostInstancesForRefresh : null,
|
||||
scheduleRefresh: __DEV__ ? scheduleRefresh : null,
|
||||
scheduleRoot: __DEV__ ? scheduleRoot : null,
|
||||
setRefreshHandler: __DEV__ ? setRefreshHandler : null,
|
||||
// Enables DevTools to append owner stacks to error messages in DEV mode.
|
||||
getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null,
|
||||
// Enables DevTools to detect reconciler version rather than renderer version
|
||||
// which may not match for third party renderers.
|
||||
reconcilerVersion: ReactVersion,
|
||||
});
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {ReactNodeList} from 'shared/ReactTypes';
|
||||
import type {
|
||||
FiberRoot,
|
||||
SuspenseHydrationCallbacks,
|
||||
TransitionTracingCallbacks,
|
||||
} from './ReactInternalTypes';
|
||||
import type {RootTag} from './ReactRootTags';
|
||||
import type {Cache} from './ReactFiberCacheComponent.new';
|
||||
import type {Container} from './ReactFiberHostConfig';
|
||||
|
||||
import {noTimeout, supportsHydration} from './ReactFiberHostConfig';
|
||||
import {createHostRootFiber} from './ReactFiber.new';
|
||||
import {
|
||||
NoLane,
|
||||
NoLanes,
|
||||
NoTimestamp,
|
||||
TotalLanes,
|
||||
createLaneMap,
|
||||
} from './ReactFiberLane.new';
|
||||
import {
|
||||
enableSuspenseCallback,
|
||||
enableCache,
|
||||
enableProfilerCommitHooks,
|
||||
enableProfilerTimer,
|
||||
enableUpdaterTracking,
|
||||
enableTransitionTracing,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {initializeUpdateQueue} from './ReactFiberClassUpdateQueue.new';
|
||||
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
|
||||
import {createCache, retainCache} from './ReactFiberCacheComponent.new';
|
||||
|
||||
export type RootState = {
|
||||
element: any,
|
||||
isDehydrated: boolean,
|
||||
cache: Cache,
|
||||
};
|
||||
|
||||
function FiberRootNode(
|
||||
containerInfo,
|
||||
tag,
|
||||
hydrate,
|
||||
identifierPrefix,
|
||||
onRecoverableError,
|
||||
) {
|
||||
this.tag = tag;
|
||||
this.containerInfo = containerInfo;
|
||||
this.pendingChildren = null;
|
||||
this.current = null;
|
||||
this.pingCache = null;
|
||||
this.finishedWork = null;
|
||||
this.timeoutHandle = noTimeout;
|
||||
this.context = null;
|
||||
this.pendingContext = null;
|
||||
this.callbackNode = null;
|
||||
this.callbackPriority = NoLane;
|
||||
this.eventTimes = createLaneMap(NoLanes);
|
||||
this.expirationTimes = createLaneMap(NoTimestamp);
|
||||
|
||||
this.pendingLanes = NoLanes;
|
||||
this.suspendedLanes = NoLanes;
|
||||
this.pingedLanes = NoLanes;
|
||||
this.expiredLanes = NoLanes;
|
||||
this.mutableReadLanes = NoLanes;
|
||||
this.finishedLanes = NoLanes;
|
||||
this.errorRecoveryDisabledLanes = NoLanes;
|
||||
|
||||
this.entangledLanes = NoLanes;
|
||||
this.entanglements = createLaneMap(NoLanes);
|
||||
|
||||
this.hiddenUpdates = createLaneMap(null);
|
||||
|
||||
this.identifierPrefix = identifierPrefix;
|
||||
this.onRecoverableError = onRecoverableError;
|
||||
|
||||
if (enableCache) {
|
||||
this.pooledCache = null;
|
||||
this.pooledCacheLanes = NoLanes;
|
||||
}
|
||||
|
||||
if (supportsHydration) {
|
||||
this.mutableSourceEagerHydrationData = null;
|
||||
}
|
||||
|
||||
if (enableSuspenseCallback) {
|
||||
this.hydrationCallbacks = null;
|
||||
}
|
||||
|
||||
this.incompleteTransitions = new Map();
|
||||
if (enableTransitionTracing) {
|
||||
this.transitionCallbacks = null;
|
||||
const transitionLanesMap = (this.transitionLanes = []);
|
||||
for (let i = 0; i < TotalLanes; i++) {
|
||||
transitionLanesMap.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
this.effectDuration = 0;
|
||||
this.passiveEffectDuration = 0;
|
||||
}
|
||||
|
||||
if (enableUpdaterTracking) {
|
||||
this.memoizedUpdaters = new Set();
|
||||
const pendingUpdatersLaneMap = (this.pendingUpdatersLaneMap = []);
|
||||
for (let i = 0; i < TotalLanes; i++) {
|
||||
pendingUpdatersLaneMap.push(new Set());
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
switch (tag) {
|
||||
case ConcurrentRoot:
|
||||
this._debugRootType = hydrate ? 'hydrateRoot()' : 'createRoot()';
|
||||
break;
|
||||
case LegacyRoot:
|
||||
this._debugRootType = hydrate ? 'hydrate()' : 'render()';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createFiberRoot(
|
||||
containerInfo: Container,
|
||||
tag: RootTag,
|
||||
hydrate: boolean,
|
||||
initialChildren: ReactNodeList,
|
||||
hydrationCallbacks: null | SuspenseHydrationCallbacks,
|
||||
isStrictMode: boolean,
|
||||
concurrentUpdatesByDefaultOverride: null | boolean,
|
||||
// TODO: We have several of these arguments that are conceptually part of the
|
||||
// host config, but because they are passed in at runtime, we have to thread
|
||||
// them through the root constructor. Perhaps we should put them all into a
|
||||
// single type, like a DynamicHostConfig that is defined by the renderer.
|
||||
identifierPrefix: string,
|
||||
onRecoverableError: null | ((error: mixed) => void),
|
||||
transitionCallbacks: null | TransitionTracingCallbacks,
|
||||
): FiberRoot {
|
||||
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
|
||||
const root: FiberRoot = (new FiberRootNode(
|
||||
containerInfo,
|
||||
tag,
|
||||
hydrate,
|
||||
identifierPrefix,
|
||||
onRecoverableError,
|
||||
): any);
|
||||
if (enableSuspenseCallback) {
|
||||
root.hydrationCallbacks = hydrationCallbacks;
|
||||
}
|
||||
|
||||
if (enableTransitionTracing) {
|
||||
root.transitionCallbacks = transitionCallbacks;
|
||||
}
|
||||
|
||||
// Cyclic construction. This cheats the type system right now because
|
||||
// stateNode is any.
|
||||
const uninitializedFiber = createHostRootFiber(
|
||||
tag,
|
||||
isStrictMode,
|
||||
concurrentUpdatesByDefaultOverride,
|
||||
);
|
||||
root.current = uninitializedFiber;
|
||||
uninitializedFiber.stateNode = root;
|
||||
|
||||
if (enableCache) {
|
||||
const initialCache = createCache();
|
||||
retainCache(initialCache);
|
||||
|
||||
// The pooledCache is a fresh cache instance that is used temporarily
|
||||
// for newly mounted boundaries during a render. In general, the
|
||||
// pooledCache is always cleared from the root at the end of a render:
|
||||
// it is either released when render commits, or moved to an Offscreen
|
||||
// component if rendering suspends. Because the lifetime of the pooled
|
||||
// cache is distinct from the main memoizedState.cache, it must be
|
||||
// retained separately.
|
||||
root.pooledCache = initialCache;
|
||||
retainCache(initialCache);
|
||||
const initialState: RootState = {
|
||||
element: initialChildren,
|
||||
isDehydrated: hydrate,
|
||||
cache: initialCache,
|
||||
};
|
||||
uninitializedFiber.memoizedState = initialState;
|
||||
} else {
|
||||
const initialState: RootState = {
|
||||
element: initialChildren,
|
||||
isDehydrated: hydrate,
|
||||
cache: (null: any), // not enabled yet
|
||||
};
|
||||
uninitializedFiber.memoizedState = initialState;
|
||||
}
|
||||
|
||||
initializeUpdateQueue(uninitializedFiber);
|
||||
|
||||
return root;
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 './ReactInternalTypes';
|
||||
import type {
|
||||
ReactScopeInstance,
|
||||
ReactContext,
|
||||
ReactScopeQuery,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import {
|
||||
getPublicInstance,
|
||||
getInstanceFromNode,
|
||||
getInstanceFromScope,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {isFiberSuspenseAndTimedOut} from './ReactFiberTreeReflection';
|
||||
|
||||
import {HostComponent, ScopeComponent, ContextProvider} from './ReactWorkTags';
|
||||
import {enableScopeAPI} from 'shared/ReactFeatureFlags';
|
||||
|
||||
function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
|
||||
return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
|
||||
}
|
||||
|
||||
const emptyObject = {};
|
||||
|
||||
function collectScopedNodes(
|
||||
node: Fiber,
|
||||
fn: ReactScopeQuery,
|
||||
scopedNodes: Array<any>,
|
||||
): void {
|
||||
if (enableScopeAPI) {
|
||||
if (node.tag === HostComponent) {
|
||||
const {type, memoizedProps, stateNode} = node;
|
||||
const instance = getPublicInstance(stateNode);
|
||||
if (
|
||||
instance !== null &&
|
||||
fn(type, memoizedProps || emptyObject, instance) === true
|
||||
) {
|
||||
scopedNodes.push(instance);
|
||||
}
|
||||
}
|
||||
let child = node.child;
|
||||
|
||||
if (isFiberSuspenseAndTimedOut(node)) {
|
||||
child = getSuspenseFallbackChild(node);
|
||||
}
|
||||
if (child !== null) {
|
||||
collectScopedNodesFromChildren(child, fn, scopedNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectFirstScopedNode(
|
||||
node: Fiber,
|
||||
fn: ReactScopeQuery,
|
||||
): null | Object {
|
||||
if (enableScopeAPI) {
|
||||
if (node.tag === HostComponent) {
|
||||
const {type, memoizedProps, stateNode} = node;
|
||||
const instance = getPublicInstance(stateNode);
|
||||
if (instance !== null && fn(type, memoizedProps, instance) === true) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
let child = node.child;
|
||||
|
||||
if (isFiberSuspenseAndTimedOut(node)) {
|
||||
child = getSuspenseFallbackChild(node);
|
||||
}
|
||||
if (child !== null) {
|
||||
return collectFirstScopedNodeFromChildren(child, fn);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectScopedNodesFromChildren(
|
||||
startingChild: Fiber,
|
||||
fn: ReactScopeQuery,
|
||||
scopedNodes: Array<any>,
|
||||
): void {
|
||||
let child: null | Fiber = startingChild;
|
||||
while (child !== null) {
|
||||
collectScopedNodes(child, fn, scopedNodes);
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function collectFirstScopedNodeFromChildren(
|
||||
startingChild: Fiber,
|
||||
fn: ReactScopeQuery,
|
||||
): Object | null {
|
||||
let child: null | Fiber = startingChild;
|
||||
while (child !== null) {
|
||||
const scopedNode = collectFirstScopedNode(child, fn);
|
||||
if (scopedNode !== null) {
|
||||
return scopedNode;
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectNearestContextValues<T>(
|
||||
node: Fiber,
|
||||
context: ReactContext<T>,
|
||||
childContextValues: Array<T>,
|
||||
): void {
|
||||
if (node.tag === ContextProvider && node.type._context === context) {
|
||||
const contextValue = node.memoizedProps.value;
|
||||
childContextValues.push(contextValue);
|
||||
} else {
|
||||
let child = node.child;
|
||||
|
||||
if (isFiberSuspenseAndTimedOut(node)) {
|
||||
child = getSuspenseFallbackChild(node);
|
||||
}
|
||||
if (child !== null) {
|
||||
collectNearestChildContextValues(child, context, childContextValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectNearestChildContextValues<T>(
|
||||
startingChild: Fiber | null,
|
||||
context: ReactContext<T>,
|
||||
childContextValues: Array<T>,
|
||||
): void {
|
||||
let child = startingChild;
|
||||
while (child !== null) {
|
||||
collectNearestContextValues(child, context, childContextValues);
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function DO_NOT_USE_queryAllNodes(fn: ReactScopeQuery): null | Array<Object> {
|
||||
const currentFiber = getInstanceFromScope(this);
|
||||
if (currentFiber === null) {
|
||||
return null;
|
||||
}
|
||||
const child = currentFiber.child;
|
||||
const scopedNodes = [];
|
||||
if (child !== null) {
|
||||
collectScopedNodesFromChildren(child, fn, scopedNodes);
|
||||
}
|
||||
return scopedNodes.length === 0 ? null : scopedNodes;
|
||||
}
|
||||
|
||||
function DO_NOT_USE_queryFirstNode(fn: ReactScopeQuery): null | Object {
|
||||
const currentFiber = getInstanceFromScope(this);
|
||||
if (currentFiber === null) {
|
||||
return null;
|
||||
}
|
||||
const child = currentFiber.child;
|
||||
if (child !== null) {
|
||||
return collectFirstScopedNodeFromChildren(child, fn);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function containsNode(node: Object): boolean {
|
||||
let fiber = getInstanceFromNode(node);
|
||||
while (fiber !== null) {
|
||||
if (fiber.tag === ScopeComponent && fiber.stateNode === this) {
|
||||
return true;
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getChildContextValues<T>(context: ReactContext<T>): Array<T> {
|
||||
const currentFiber = getInstanceFromScope(this);
|
||||
if (currentFiber === null) {
|
||||
return [];
|
||||
}
|
||||
const child = currentFiber.child;
|
||||
const childContextValues = [];
|
||||
if (child !== null) {
|
||||
collectNearestChildContextValues(child, context, childContextValues);
|
||||
}
|
||||
return childContextValues;
|
||||
}
|
||||
|
||||
export function createScopeInstance(): ReactScopeInstance {
|
||||
return {
|
||||
DO_NOT_USE_queryAllNodes,
|
||||
DO_NOT_USE_queryFirstNode,
|
||||
containsNode,
|
||||
getChildContextValues,
|
||||
};
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import type {FiberRoot} from './ReactInternalTypes';
|
||||
import type {RootState} from './ReactFiberRoot.new';
|
||||
import type {RootState} from './ReactFiberRoot.old';
|
||||
|
||||
// This is imported by the event replaying implementation in React DOM. It's
|
||||
// in a separate file to break a circular dependency between the renderer and
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 './ReactInternalTypes';
|
||||
|
||||
export type StackCursor<T> = {current: T};
|
||||
|
||||
const valueStack: Array<any> = [];
|
||||
|
||||
let fiberStack: Array<Fiber | null>;
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack = [];
|
||||
}
|
||||
|
||||
let index = -1;
|
||||
|
||||
function createCursor<T>(defaultValue: T): StackCursor<T> {
|
||||
return {
|
||||
current: defaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
function isEmpty(): boolean {
|
||||
return index === -1;
|
||||
}
|
||||
|
||||
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
|
||||
if (index < 0) {
|
||||
if (__DEV__) {
|
||||
console.error('Unexpected pop.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (fiber !== fiberStack[index]) {
|
||||
console.error('Unexpected Fiber popped.');
|
||||
}
|
||||
}
|
||||
|
||||
cursor.current = valueStack[index];
|
||||
|
||||
valueStack[index] = null;
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack[index] = null;
|
||||
}
|
||||
|
||||
index--;
|
||||
}
|
||||
|
||||
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
|
||||
index++;
|
||||
|
||||
valueStack[index] = cursor.current;
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack[index] = fiber;
|
||||
}
|
||||
|
||||
cursor.current = value;
|
||||
}
|
||||
|
||||
function checkThatStackIsEmpty() {
|
||||
if (__DEV__) {
|
||||
if (index !== -1) {
|
||||
console.error(
|
||||
'Expected an empty stack. Something was not reset properly.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetStackAfterFatalErrorInDev() {
|
||||
if (__DEV__) {
|
||||
index = -1;
|
||||
valueStack.length = 0;
|
||||
fiberStack.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
createCursor,
|
||||
isEmpty,
|
||||
pop,
|
||||
push,
|
||||
// DEV only:
|
||||
checkThatStackIsEmpty,
|
||||
resetStackAfterFatalErrorInDev,
|
||||
};
|
|
@ -1,113 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {ReactNodeList, Wakeable} from 'shared/ReactTypes';
|
||||
import type {Fiber} from './ReactInternalTypes';
|
||||
import type {SuspenseInstance} from './ReactFiberHostConfig';
|
||||
import type {Lane} from './ReactFiberLane.new';
|
||||
import type {TreeContext} from './ReactFiberTreeContext.new';
|
||||
|
||||
import {SuspenseComponent, SuspenseListComponent} from './ReactWorkTags';
|
||||
import {NoFlags, DidCapture} from './ReactFiberFlags';
|
||||
import {
|
||||
isSuspenseInstancePending,
|
||||
isSuspenseInstanceFallback,
|
||||
} from './ReactFiberHostConfig';
|
||||
|
||||
export type SuspenseProps = {
|
||||
children?: ReactNodeList,
|
||||
fallback?: ReactNodeList,
|
||||
|
||||
// TODO: Add "unstable_" prefix?
|
||||
suspenseCallback?: (Set<Wakeable> | null) => mixed,
|
||||
|
||||
unstable_avoidThisFallback?: boolean,
|
||||
unstable_expectedLoadTime?: number,
|
||||
unstable_name?: string,
|
||||
};
|
||||
|
||||
// A null SuspenseState represents an unsuspended normal Suspense boundary.
|
||||
// A non-null SuspenseState means that it is blocked for one reason or another.
|
||||
// - A non-null dehydrated field means it's blocked pending hydration.
|
||||
// - A non-null dehydrated field can use isSuspenseInstancePending or
|
||||
// isSuspenseInstanceFallback to query the reason for being dehydrated.
|
||||
// - A null dehydrated field means it's blocked by something suspending and
|
||||
// we're currently showing a fallback instead.
|
||||
export type SuspenseState = {
|
||||
// If this boundary is still dehydrated, we store the SuspenseInstance
|
||||
// here to indicate that it is dehydrated (flag) and for quick access
|
||||
// to check things like isSuspenseInstancePending.
|
||||
dehydrated: null | SuspenseInstance,
|
||||
treeContext: null | TreeContext,
|
||||
// Represents the lane we should attempt to hydrate a dehydrated boundary at.
|
||||
// OffscreenLane is the default for dehydrated boundaries.
|
||||
// NoLane is the default for normal boundaries, which turns into "normal" pri.
|
||||
retryLane: Lane,
|
||||
};
|
||||
|
||||
export type SuspenseListTailMode = 'collapsed' | 'hidden' | void;
|
||||
|
||||
export type SuspenseListRenderState = {
|
||||
isBackwards: boolean,
|
||||
// The currently rendering tail row.
|
||||
rendering: null | Fiber,
|
||||
// The absolute time when we started rendering the most recent tail row.
|
||||
renderingStartTime: number,
|
||||
// The last of the already rendered children.
|
||||
last: null | Fiber,
|
||||
// Remaining rows on the tail of the list.
|
||||
tail: null | Fiber,
|
||||
// Tail insertions setting.
|
||||
tailMode: SuspenseListTailMode,
|
||||
};
|
||||
|
||||
export function findFirstSuspended(row: Fiber): null | Fiber {
|
||||
let node = row;
|
||||
while (node !== null) {
|
||||
if (node.tag === SuspenseComponent) {
|
||||
const state: SuspenseState | null = node.memoizedState;
|
||||
if (state !== null) {
|
||||
const dehydrated: null | SuspenseInstance = state.dehydrated;
|
||||
if (
|
||||
dehydrated === null ||
|
||||
isSuspenseInstancePending(dehydrated) ||
|
||||
isSuspenseInstanceFallback(dehydrated)
|
||||
) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
node.tag === SuspenseListComponent &&
|
||||
// revealOrder undefined can't be trusted because it don't
|
||||
// keep track of whether it suspended or not.
|
||||
node.memoizedProps.revealOrder !== undefined
|
||||
) {
|
||||
const didSuspend = (node.flags & DidCapture) !== NoFlags;
|
||||
if (didSuspend) {
|
||||
return node;
|
||||
}
|
||||
} else if (node.child !== null) {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
continue;
|
||||
}
|
||||
if (node === row) {
|
||||
return null;
|
||||
}
|
||||
while (node.sibling === null) {
|
||||
if (node.return === null || node.return === row) {
|
||||
return null;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 './ReactInternalTypes';
|
||||
import type {StackCursor} from './ReactFiberStack.new';
|
||||
import type {
|
||||
SuspenseState,
|
||||
SuspenseProps,
|
||||
} from './ReactFiberSuspenseComponent.new';
|
||||
|
||||
import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags';
|
||||
import {createCursor, push, pop} from './ReactFiberStack.new';
|
||||
import {isCurrentTreeHidden} from './ReactFiberHiddenContext.new';
|
||||
import {SuspenseComponent, OffscreenComponent} from './ReactWorkTags';
|
||||
|
||||
// The Suspense handler is the boundary that should capture if something
|
||||
// suspends, i.e. it's the nearest `catch` block on the stack.
|
||||
const suspenseHandlerStackCursor: StackCursor<Fiber | null> = createCursor(
|
||||
null,
|
||||
);
|
||||
|
||||
function shouldAvoidedBoundaryCapture(
|
||||
workInProgress: Fiber,
|
||||
handlerOnStack: Fiber,
|
||||
props: any,
|
||||
): boolean {
|
||||
if (enableSuspenseAvoidThisFallback) {
|
||||
// If the parent is already showing content, and we're not inside a hidden
|
||||
// tree, then we should show the avoided fallback.
|
||||
if (handlerOnStack.alternate !== null && !isCurrentTreeHidden()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the handler on the stack is also an avoided boundary, then we should
|
||||
// favor this inner one.
|
||||
if (
|
||||
handlerOnStack.tag === SuspenseComponent &&
|
||||
handlerOnStack.memoizedProps.unstable_avoidThisFallback === true
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this avoided boundary is dehydrated, then it should capture.
|
||||
const suspenseState: SuspenseState | null = workInProgress.memoizedState;
|
||||
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If none of those cases apply, then we should avoid this fallback and show
|
||||
// the outer one instead.
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isBadSuspenseFallback(
|
||||
current: Fiber | null,
|
||||
nextProps: SuspenseProps,
|
||||
): boolean {
|
||||
// Check if this is a "bad" fallback state or a good one. A bad fallback state
|
||||
// is one that we only show as a last resort; if this is a transition, we'll
|
||||
// block it from displaying, and wait for more data to arrive.
|
||||
if (current !== null) {
|
||||
const prevState: SuspenseState = current.memoizedState;
|
||||
const isShowingFallback = prevState !== null;
|
||||
if (!isShowingFallback && !isCurrentTreeHidden()) {
|
||||
// It's bad to switch to a fallback if content is already visible
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
enableSuspenseAvoidThisFallback &&
|
||||
nextProps.unstable_avoidThisFallback === true
|
||||
) {
|
||||
// Experimental: Some fallbacks are always bad
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void {
|
||||
const props = handler.pendingProps;
|
||||
const handlerOnStack = suspenseHandlerStackCursor.current;
|
||||
if (
|
||||
enableSuspenseAvoidThisFallback &&
|
||||
props.unstable_avoidThisFallback === true &&
|
||||
handlerOnStack !== null &&
|
||||
!shouldAvoidedBoundaryCapture(handler, handlerOnStack, props)
|
||||
) {
|
||||
// This boundary should not capture if something suspends. Reuse the
|
||||
// existing handler on the stack.
|
||||
push(suspenseHandlerStackCursor, handlerOnStack, handler);
|
||||
} else {
|
||||
// Push this handler onto the stack.
|
||||
push(suspenseHandlerStackCursor, handler, handler);
|
||||
}
|
||||
}
|
||||
|
||||
export function pushFallbackTreeSuspenseHandler(fiber: Fiber): void {
|
||||
// We're about to render the fallback. If something in the fallback suspends,
|
||||
// it's akin to throwing inside of a `catch` block. This boundary should not
|
||||
// capture. Reuse the existing handler on the stack.
|
||||
reuseSuspenseHandlerOnStack(fiber);
|
||||
}
|
||||
|
||||
export function pushOffscreenSuspenseHandler(fiber: Fiber): void {
|
||||
if (fiber.tag === OffscreenComponent) {
|
||||
push(suspenseHandlerStackCursor, fiber, fiber);
|
||||
} else {
|
||||
// This is a LegacyHidden component.
|
||||
reuseSuspenseHandlerOnStack(fiber);
|
||||
}
|
||||
}
|
||||
|
||||
export function reuseSuspenseHandlerOnStack(fiber: Fiber) {
|
||||
push(suspenseHandlerStackCursor, getSuspenseHandler(), fiber);
|
||||
}
|
||||
|
||||
export function getSuspenseHandler(): Fiber | null {
|
||||
return suspenseHandlerStackCursor.current;
|
||||
}
|
||||
|
||||
export function popSuspenseHandler(fiber: Fiber): void {
|
||||
pop(suspenseHandlerStackCursor, fiber);
|
||||
}
|
||||
|
||||
// SuspenseList context
|
||||
// TODO: Move to a separate module? We may change the SuspenseList
|
||||
// implementation to hide/show in the commit phase, anyway.
|
||||
export opaque type SuspenseContext = number;
|
||||
export opaque type SubtreeSuspenseContext: SuspenseContext = number;
|
||||
export opaque type ShallowSuspenseContext: SuspenseContext = number;
|
||||
|
||||
const DefaultSuspenseContext: SuspenseContext = 0b00;
|
||||
|
||||
const SubtreeSuspenseContextMask: SuspenseContext = 0b01;
|
||||
|
||||
// ForceSuspenseFallback can be used by SuspenseList to force newly added
|
||||
// items into their fallback state during one of the render passes.
|
||||
export const ForceSuspenseFallback: ShallowSuspenseContext = 0b10;
|
||||
|
||||
export const suspenseStackCursor: StackCursor<SuspenseContext> = createCursor(
|
||||
DefaultSuspenseContext,
|
||||
);
|
||||
|
||||
export function hasSuspenseListContext(
|
||||
parentContext: SuspenseContext,
|
||||
flag: SuspenseContext,
|
||||
): boolean {
|
||||
return (parentContext & flag) !== 0;
|
||||
}
|
||||
|
||||
export function setDefaultShallowSuspenseListContext(
|
||||
parentContext: SuspenseContext,
|
||||
): SuspenseContext {
|
||||
return parentContext & SubtreeSuspenseContextMask;
|
||||
}
|
||||
|
||||
export function setShallowSuspenseListContext(
|
||||
parentContext: SuspenseContext,
|
||||
shallowContext: ShallowSuspenseContext,
|
||||
): SuspenseContext {
|
||||
return (parentContext & SubtreeSuspenseContextMask) | shallowContext;
|
||||
}
|
||||
|
||||
export function pushSuspenseListContext(
|
||||
fiber: Fiber,
|
||||
newContext: SuspenseContext,
|
||||
): void {
|
||||
push(suspenseStackCursor, newContext, fiber);
|
||||
}
|
||||
|
||||
export function popSuspenseListContext(fiber: Fiber): void {
|
||||
pop(suspenseStackCursor, fiber);
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {SchedulerCallback} from './Scheduler';
|
||||
|
||||
import {
|
||||
DiscreteEventPriority,
|
||||
getCurrentUpdatePriority,
|
||||
setCurrentUpdatePriority,
|
||||
} from './ReactEventPriorities.new';
|
||||
import {ImmediatePriority, scheduleCallback} from './Scheduler';
|
||||
|
||||
let syncQueue: Array<SchedulerCallback> | null = null;
|
||||
let includesLegacySyncCallbacks: boolean = false;
|
||||
let isFlushingSyncQueue: boolean = false;
|
||||
|
||||
export function scheduleSyncCallback(callback: SchedulerCallback) {
|
||||
// Push this callback into an internal queue. We'll flush these either in
|
||||
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
|
||||
if (syncQueue === null) {
|
||||
syncQueue = [callback];
|
||||
} else {
|
||||
// Push onto existing queue. Don't need to schedule a callback because
|
||||
// we already scheduled one when we created the queue.
|
||||
syncQueue.push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export function scheduleLegacySyncCallback(callback: SchedulerCallback) {
|
||||
includesLegacySyncCallbacks = true;
|
||||
scheduleSyncCallback(callback);
|
||||
}
|
||||
|
||||
export function flushSyncCallbacksOnlyInLegacyMode() {
|
||||
// Only flushes the queue if there's a legacy sync callback scheduled.
|
||||
// TODO: There's only a single type of callback: performSyncOnWorkOnRoot. So
|
||||
// it might make more sense for the queue to be a list of roots instead of a
|
||||
// list of generic callbacks. Then we can have two: one for legacy roots, one
|
||||
// for concurrent roots. And this method would only flush the legacy ones.
|
||||
if (includesLegacySyncCallbacks) {
|
||||
flushSyncCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
export function flushSyncCallbacks(): null {
|
||||
if (!isFlushingSyncQueue && syncQueue !== null) {
|
||||
// Prevent re-entrance.
|
||||
isFlushingSyncQueue = true;
|
||||
let i = 0;
|
||||
const previousUpdatePriority = getCurrentUpdatePriority();
|
||||
try {
|
||||
const isSync = true;
|
||||
const queue = syncQueue;
|
||||
// TODO: Is this necessary anymore? The only user code that runs in this
|
||||
// queue is in the render or commit phases.
|
||||
setCurrentUpdatePriority(DiscreteEventPriority);
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
for (; i < queue.length; i++) {
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
let callback: SchedulerCallback = queue[i];
|
||||
do {
|
||||
// $FlowFixMe[incompatible-type] we bail out when we get a null
|
||||
callback = callback(isSync);
|
||||
} while (callback !== null);
|
||||
}
|
||||
syncQueue = null;
|
||||
includesLegacySyncCallbacks = false;
|
||||
} catch (error) {
|
||||
// If something throws, leave the remaining callbacks on the queue.
|
||||
if (syncQueue !== null) {
|
||||
syncQueue = syncQueue.slice(i + 1);
|
||||
}
|
||||
// Resume flushing in the next tick
|
||||
scheduleCallback(ImmediatePriority, flushSyncCallbacks);
|
||||
throw error;
|
||||
} finally {
|
||||
setCurrentUpdatePriority(previousUpdatePriority);
|
||||
isFlushingSyncQueue = false;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {
|
||||
Thenable,
|
||||
PendingThenable,
|
||||
FulfilledThenable,
|
||||
RejectedThenable,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
const {ReactCurrentActQueue} = ReactSharedInternals;
|
||||
|
||||
export opaque type ThenableState = Array<Thenable<any>>;
|
||||
|
||||
// An error that is thrown (e.g. by `use`) to trigger Suspense. If we
|
||||
// detect this is caught by userspace, we'll log a warning in development.
|
||||
export const SuspenseException: mixed = new Error(
|
||||
"Suspense Exception: This is not a real error! It's an implementation " +
|
||||
'detail of `use` to interrupt the current render. You must either ' +
|
||||
'rethrow it immediately, or move the `use` call outside of the ' +
|
||||
'`try/catch` block. Capturing without rethrowing will lead to ' +
|
||||
'unexpected behavior.\n\n' +
|
||||
'To handle async errors, wrap your component in an error boundary, or ' +
|
||||
"call the promise's `.catch` method and pass the result to `use`",
|
||||
);
|
||||
|
||||
export function createThenableState(): ThenableState {
|
||||
// The ThenableState is created the first time a component suspends. If it
|
||||
// suspends again, we'll reuse the same state.
|
||||
return [];
|
||||
}
|
||||
|
||||
export function isThenableResolved(thenable: Thenable<mixed>): boolean {
|
||||
const status = thenable.status;
|
||||
return status === 'fulfilled' || status === 'rejected';
|
||||
}
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
export function trackUsedThenable<T>(
|
||||
thenableState: ThenableState,
|
||||
thenable: Thenable<T>,
|
||||
index: number,
|
||||
): T {
|
||||
if (__DEV__ && ReactCurrentActQueue.current !== null) {
|
||||
ReactCurrentActQueue.didUsePromise = true;
|
||||
}
|
||||
|
||||
const previous = thenableState[index];
|
||||
if (previous === undefined) {
|
||||
thenableState.push(thenable);
|
||||
} else {
|
||||
if (previous !== thenable) {
|
||||
// Reuse the previous thenable, and drop the new one. We can assume
|
||||
// they represent the same value, because components are idempotent.
|
||||
|
||||
// Avoid an unhandled rejection errors for the Promises that we'll
|
||||
// intentionally ignore.
|
||||
thenable.then(noop, noop);
|
||||
thenable = previous;
|
||||
}
|
||||
}
|
||||
|
||||
// We use an expando to track the status and result of a thenable so that we
|
||||
// can synchronously unwrap the value. Think of this as an extension of the
|
||||
// Promise API, or a custom interface that is a superset of Thenable.
|
||||
//
|
||||
// If the thenable doesn't have a status, set it to "pending" and attach
|
||||
// a listener that will update its status and result when it resolves.
|
||||
switch (thenable.status) {
|
||||
case 'fulfilled': {
|
||||
const fulfilledValue: T = thenable.value;
|
||||
return fulfilledValue;
|
||||
}
|
||||
case 'rejected': {
|
||||
const rejectedError = thenable.reason;
|
||||
throw rejectedError;
|
||||
}
|
||||
default: {
|
||||
if (typeof thenable.status === 'string') {
|
||||
// Only instrument the thenable if the status if not defined. If
|
||||
// it's defined, but an unknown value, assume it's been instrumented by
|
||||
// some custom userspace implementation. We treat it as "pending".
|
||||
} else {
|
||||
const pendingThenable: PendingThenable<T> = (thenable: any);
|
||||
pendingThenable.status = 'pending';
|
||||
pendingThenable.then(
|
||||
fulfilledValue => {
|
||||
if (thenable.status === 'pending') {
|
||||
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
|
||||
fulfilledThenable.status = 'fulfilled';
|
||||
fulfilledThenable.value = fulfilledValue;
|
||||
}
|
||||
},
|
||||
(error: mixed) => {
|
||||
if (thenable.status === 'pending') {
|
||||
const rejectedThenable: RejectedThenable<T> = (thenable: any);
|
||||
rejectedThenable.status = 'rejected';
|
||||
rejectedThenable.reason = error;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Check one more time in case the thenable resolved synchronously
|
||||
switch (thenable.status) {
|
||||
case 'fulfilled': {
|
||||
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
|
||||
return fulfilledThenable.value;
|
||||
}
|
||||
case 'rejected': {
|
||||
const rejectedThenable: RejectedThenable<T> = (thenable: any);
|
||||
throw rejectedThenable.reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Suspend.
|
||||
//
|
||||
// Throwing here is an implementation detail that allows us to unwind the
|
||||
// call stack. But we shouldn't allow it to leak into userspace. Throw an
|
||||
// opaque placeholder value instead of the actual thenable. If it doesn't
|
||||
// get captured by the work loop, log a warning, because that means
|
||||
// something in userspace must have caught it.
|
||||
suspendedThenable = thenable;
|
||||
if (__DEV__) {
|
||||
needsToResetSuspendedThenableDEV = true;
|
||||
}
|
||||
throw SuspenseException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is used to track the actual thenable that suspended so it can be
|
||||
// passed to the rest of the Suspense implementation — which, for historical
|
||||
// reasons, expects to receive a thenable.
|
||||
let suspendedThenable: Thenable<any> | null = null;
|
||||
let needsToResetSuspendedThenableDEV = false;
|
||||
export function getSuspendedThenable(): Thenable<mixed> {
|
||||
// This is called right after `use` suspends by throwing an exception. `use`
|
||||
// throws an opaque value instead of the thenable itself so that it can't be
|
||||
// caught in userspace. Then the work loop accesses the actual thenable using
|
||||
// this function.
|
||||
if (suspendedThenable === null) {
|
||||
throw new Error(
|
||||
'Expected a suspended thenable. This is a bug in React. Please file ' +
|
||||
'an issue.',
|
||||
);
|
||||
}
|
||||
const thenable = suspendedThenable;
|
||||
suspendedThenable = null;
|
||||
if (__DEV__) {
|
||||
needsToResetSuspendedThenableDEV = false;
|
||||
}
|
||||
return thenable;
|
||||
}
|
||||
|
||||
export function checkIfUseWrappedInTryCatch(): boolean {
|
||||
if (__DEV__) {
|
||||
// This was set right before SuspenseException was thrown, and it should
|
||||
// have been cleared when the exception was handled. If it wasn't,
|
||||
// it must have been caught by userspace.
|
||||
if (needsToResetSuspendedThenableDEV) {
|
||||
needsToResetSuspendedThenableDEV = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -1,529 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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, FiberRoot} from './ReactInternalTypes';
|
||||
import type {Lane, Lanes} from './ReactFiberLane.new';
|
||||
import type {CapturedValue} from './ReactCapturedValue';
|
||||
import type {Update} from './ReactFiberClassUpdateQueue.new';
|
||||
import type {Wakeable} from 'shared/ReactTypes';
|
||||
import type {OffscreenQueue} from './ReactFiberOffscreenComponent';
|
||||
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
import {
|
||||
ClassComponent,
|
||||
HostRoot,
|
||||
IncompleteClassComponent,
|
||||
FunctionComponent,
|
||||
ForwardRef,
|
||||
SimpleMemoComponent,
|
||||
SuspenseComponent,
|
||||
OffscreenComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {
|
||||
DidCapture,
|
||||
Incomplete,
|
||||
NoFlags,
|
||||
ShouldCapture,
|
||||
LifecycleEffectMask,
|
||||
ForceUpdateForLegacySuspense,
|
||||
ForceClientRender,
|
||||
} from './ReactFiberFlags';
|
||||
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
enableDebugTracing,
|
||||
enableLazyContextPropagation,
|
||||
enableUpdaterTracking,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {createCapturedValueAtFiber} from './ReactCapturedValue';
|
||||
import {
|
||||
enqueueCapturedUpdate,
|
||||
createUpdate,
|
||||
CaptureUpdate,
|
||||
ForceUpdate,
|
||||
enqueueUpdate,
|
||||
} from './ReactFiberClassUpdateQueue.new';
|
||||
import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading.new';
|
||||
import {getSuspenseHandler} from './ReactFiberSuspenseContext.new';
|
||||
import {
|
||||
renderDidError,
|
||||
renderDidSuspendDelayIfPossible,
|
||||
onUncaughtError,
|
||||
markLegacyErrorBoundaryAsFailed,
|
||||
isAlreadyFailedLegacyErrorBoundary,
|
||||
attachPingListener,
|
||||
restorePendingUpdaters,
|
||||
} from './ReactFiberWorkLoop.new';
|
||||
import {propagateParentContextChangesToDeferredTree} from './ReactFiberNewContext.new';
|
||||
import {logCapturedError} from './ReactFiberErrorLogger';
|
||||
import {logComponentSuspended} from './DebugTracing';
|
||||
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
|
||||
import {
|
||||
SyncLane,
|
||||
NoTimestamp,
|
||||
includesSomeLane,
|
||||
mergeLanes,
|
||||
pickArbitraryLane,
|
||||
} from './ReactFiberLane.new';
|
||||
import {
|
||||
getIsHydrating,
|
||||
markDidThrowWhileHydratingDEV,
|
||||
queueHydrationError,
|
||||
} from './ReactFiberHydrationContext.new';
|
||||
import {ConcurrentRoot} from './ReactRootTags';
|
||||
|
||||
function createRootErrorUpdate(
|
||||
fiber: Fiber,
|
||||
errorInfo: CapturedValue<mixed>,
|
||||
lane: Lane,
|
||||
): Update<mixed> {
|
||||
const update = createUpdate(NoTimestamp, lane);
|
||||
// Unmount the root by rendering null.
|
||||
update.tag = CaptureUpdate;
|
||||
// Caution: React DevTools currently depends on this property
|
||||
// being called "element".
|
||||
update.payload = {element: null};
|
||||
const error = errorInfo.value;
|
||||
update.callback = () => {
|
||||
onUncaughtError(error);
|
||||
logCapturedError(fiber, errorInfo);
|
||||
};
|
||||
return update;
|
||||
}
|
||||
|
||||
function createClassErrorUpdate(
|
||||
fiber: Fiber,
|
||||
errorInfo: CapturedValue<mixed>,
|
||||
lane: Lane,
|
||||
): Update<mixed> {
|
||||
const update = createUpdate(NoTimestamp, lane);
|
||||
update.tag = CaptureUpdate;
|
||||
const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
|
||||
if (typeof getDerivedStateFromError === 'function') {
|
||||
const error = errorInfo.value;
|
||||
update.payload = () => {
|
||||
return getDerivedStateFromError(error);
|
||||
};
|
||||
update.callback = () => {
|
||||
if (__DEV__) {
|
||||
markFailedErrorBoundaryForHotReloading(fiber);
|
||||
}
|
||||
logCapturedError(fiber, errorInfo);
|
||||
};
|
||||
}
|
||||
|
||||
const inst = fiber.stateNode;
|
||||
if (inst !== null && typeof inst.componentDidCatch === 'function') {
|
||||
update.callback = function callback() {
|
||||
if (__DEV__) {
|
||||
markFailedErrorBoundaryForHotReloading(fiber);
|
||||
}
|
||||
logCapturedError(fiber, errorInfo);
|
||||
if (typeof getDerivedStateFromError !== 'function') {
|
||||
// To preserve the preexisting retry behavior of error boundaries,
|
||||
// we keep track of which ones already failed during this batch.
|
||||
// This gets reset before we yield back to the browser.
|
||||
// TODO: Warn in strict mode if getDerivedStateFromError is
|
||||
// not defined.
|
||||
markLegacyErrorBoundaryAsFailed(this);
|
||||
}
|
||||
const error = errorInfo.value;
|
||||
const stack = errorInfo.stack;
|
||||
this.componentDidCatch(error, {
|
||||
componentStack: stack !== null ? stack : '',
|
||||
});
|
||||
if (__DEV__) {
|
||||
if (typeof getDerivedStateFromError !== 'function') {
|
||||
// If componentDidCatch is the only error boundary method defined,
|
||||
// then it needs to call setState to recover from errors.
|
||||
// If no state update is scheduled then the boundary will swallow the error.
|
||||
if (!includesSomeLane(fiber.lanes, (SyncLane: Lane))) {
|
||||
console.error(
|
||||
'%s: Error boundaries should implement getDerivedStateFromError(). ' +
|
||||
'In that method, return a state update to display an error message or fallback UI.',
|
||||
getComponentNameFromFiber(fiber) || 'Unknown',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
function resetSuspendedComponent(sourceFiber: Fiber, rootRenderLanes: Lanes) {
|
||||
if (enableLazyContextPropagation) {
|
||||
const currentSourceFiber = sourceFiber.alternate;
|
||||
if (currentSourceFiber !== null) {
|
||||
// Since we never visited the children of the suspended component, we
|
||||
// need to propagate the context change now, to ensure that we visit
|
||||
// them during the retry.
|
||||
//
|
||||
// We don't have to do this for errors because we retry errors without
|
||||
// committing in between. So this is specific to Suspense.
|
||||
propagateParentContextChangesToDeferredTree(
|
||||
currentSourceFiber,
|
||||
sourceFiber,
|
||||
rootRenderLanes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the memoizedState to what it was before we attempted to render it.
|
||||
// A legacy mode Suspense quirk, only relevant to hook components.
|
||||
const tag = sourceFiber.tag;
|
||||
if (
|
||||
(sourceFiber.mode & ConcurrentMode) === NoMode &&
|
||||
(tag === FunctionComponent ||
|
||||
tag === ForwardRef ||
|
||||
tag === SimpleMemoComponent)
|
||||
) {
|
||||
const currentSource = sourceFiber.alternate;
|
||||
if (currentSource) {
|
||||
sourceFiber.updateQueue = currentSource.updateQueue;
|
||||
sourceFiber.memoizedState = currentSource.memoizedState;
|
||||
sourceFiber.lanes = currentSource.lanes;
|
||||
} else {
|
||||
sourceFiber.updateQueue = null;
|
||||
sourceFiber.memoizedState = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function markSuspenseBoundaryShouldCapture(
|
||||
suspenseBoundary: Fiber,
|
||||
returnFiber: Fiber,
|
||||
sourceFiber: Fiber,
|
||||
root: FiberRoot,
|
||||
rootRenderLanes: Lanes,
|
||||
): Fiber | null {
|
||||
// This marks a Suspense boundary so that when we're unwinding the stack,
|
||||
// it captures the suspended "exception" and does a second (fallback) pass.
|
||||
if ((suspenseBoundary.mode & ConcurrentMode) === NoMode) {
|
||||
// Legacy Mode Suspense
|
||||
//
|
||||
// If the boundary is in legacy mode, we should *not*
|
||||
// suspend the commit. Pretend as if the suspended component rendered
|
||||
// null and keep rendering. When the Suspense boundary completes,
|
||||
// we'll do a second pass to render the fallback.
|
||||
if (suspenseBoundary === returnFiber) {
|
||||
// Special case where we suspended while reconciling the children of
|
||||
// a Suspense boundary's inner Offscreen wrapper fiber. This happens
|
||||
// when a React.lazy component is a direct child of a
|
||||
// Suspense boundary.
|
||||
//
|
||||
// Suspense boundaries are implemented as multiple fibers, but they
|
||||
// are a single conceptual unit. The legacy mode behavior where we
|
||||
// pretend the suspended fiber committed as `null` won't work,
|
||||
// because in this case the "suspended" fiber is the inner
|
||||
// Offscreen wrapper.
|
||||
//
|
||||
// Because the contents of the boundary haven't started rendering
|
||||
// yet (i.e. nothing in the tree has partially rendered) we can
|
||||
// switch to the regular, concurrent mode behavior: mark the
|
||||
// boundary with ShouldCapture and enter the unwind phase.
|
||||
suspenseBoundary.flags |= ShouldCapture;
|
||||
} else {
|
||||
suspenseBoundary.flags |= DidCapture;
|
||||
sourceFiber.flags |= ForceUpdateForLegacySuspense;
|
||||
|
||||
// We're going to commit this fiber even though it didn't complete.
|
||||
// But we shouldn't call any lifecycle methods or callbacks. Remove
|
||||
// all lifecycle effect tags.
|
||||
sourceFiber.flags &= ~(LifecycleEffectMask | Incomplete);
|
||||
|
||||
if (sourceFiber.tag === ClassComponent) {
|
||||
const currentSourceFiber = sourceFiber.alternate;
|
||||
if (currentSourceFiber === null) {
|
||||
// This is a new mount. Change the tag so it's not mistaken for a
|
||||
// completed class component. For example, we should not call
|
||||
// componentWillUnmount if it is deleted.
|
||||
sourceFiber.tag = IncompleteClassComponent;
|
||||
} else {
|
||||
// When we try rendering again, we should not reuse the current fiber,
|
||||
// since it's known to be in an inconsistent state. Use a force update to
|
||||
// prevent a bail out.
|
||||
const update = createUpdate(NoTimestamp, SyncLane);
|
||||
update.tag = ForceUpdate;
|
||||
enqueueUpdate(sourceFiber, update, SyncLane);
|
||||
}
|
||||
}
|
||||
|
||||
// The source fiber did not complete. Mark it with Sync priority to
|
||||
// indicate that it still has pending work.
|
||||
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, SyncLane);
|
||||
}
|
||||
return suspenseBoundary;
|
||||
}
|
||||
// Confirmed that the boundary is in a concurrent mode tree. Continue
|
||||
// with the normal suspend path.
|
||||
//
|
||||
// After this we'll use a set of heuristics to determine whether this
|
||||
// render pass will run to completion or restart or "suspend" the commit.
|
||||
// The actual logic for this is spread out in different places.
|
||||
//
|
||||
// This first principle is that if we're going to suspend when we complete
|
||||
// a root, then we should also restart if we get an update or ping that
|
||||
// might unsuspend it, and vice versa. The only reason to suspend is
|
||||
// because you think you might want to restart before committing. However,
|
||||
// it doesn't make sense to restart only while in the period we're suspended.
|
||||
//
|
||||
// Restarting too aggressively is also not good because it starves out any
|
||||
// intermediate loading state. So we use heuristics to determine when.
|
||||
|
||||
// Suspense Heuristics
|
||||
//
|
||||
// If nothing threw a Promise or all the same fallbacks are already showing,
|
||||
// then don't suspend/restart.
|
||||
//
|
||||
// If this is an initial render of a new tree of Suspense boundaries and
|
||||
// those trigger a fallback, then don't suspend/restart. We want to ensure
|
||||
// that we can show the initial loading state as quickly as possible.
|
||||
//
|
||||
// If we hit a "Delayed" case, such as when we'd switch from content back into
|
||||
// a fallback, then we should always suspend/restart. Transitions apply
|
||||
// to this case. If none is defined, JND is used instead.
|
||||
//
|
||||
// If we're already showing a fallback and it gets "retried", allowing us to show
|
||||
// another level, but there's still an inner boundary that would show a fallback,
|
||||
// then we suspend/restart for 500ms since the last time we showed a fallback
|
||||
// anywhere in the tree. This effectively throttles progressive loading into a
|
||||
// consistent train of commits. This also gives us an opportunity to restart to
|
||||
// get to the completed state slightly earlier.
|
||||
//
|
||||
// If there's ambiguity due to batching it's resolved in preference of:
|
||||
// 1) "delayed", 2) "initial render", 3) "retry".
|
||||
//
|
||||
// We want to ensure that a "busy" state doesn't get force committed. We want to
|
||||
// ensure that new initial loading states can commit as soon as possible.
|
||||
suspenseBoundary.flags |= ShouldCapture;
|
||||
// TODO: I think we can remove this, since we now use `DidCapture` in
|
||||
// the begin phase to prevent an early bailout.
|
||||
suspenseBoundary.lanes = rootRenderLanes;
|
||||
return suspenseBoundary;
|
||||
}
|
||||
|
||||
function throwException(
|
||||
root: FiberRoot,
|
||||
returnFiber: Fiber,
|
||||
sourceFiber: Fiber,
|
||||
value: mixed,
|
||||
rootRenderLanes: Lanes,
|
||||
): void {
|
||||
// The source fiber did not complete.
|
||||
sourceFiber.flags |= Incomplete;
|
||||
|
||||
if (enableUpdaterTracking) {
|
||||
if (isDevToolsPresent) {
|
||||
// If we have pending work still, restore the original updaters
|
||||
restorePendingUpdaters(root, rootRenderLanes);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
value !== null &&
|
||||
typeof value === 'object' &&
|
||||
typeof value.then === 'function'
|
||||
) {
|
||||
// This is a wakeable. The component suspended.
|
||||
const wakeable: Wakeable = (value: any);
|
||||
resetSuspendedComponent(sourceFiber, rootRenderLanes);
|
||||
|
||||
if (__DEV__) {
|
||||
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
|
||||
markDidThrowWhileHydratingDEV();
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (enableDebugTracing) {
|
||||
if (sourceFiber.mode & DebugTracingMode) {
|
||||
const name = getComponentNameFromFiber(sourceFiber) || 'Unknown';
|
||||
logComponentSuspended(name, wakeable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule the nearest Suspense to re-render the timed out view.
|
||||
const suspenseBoundary = getSuspenseHandler();
|
||||
if (suspenseBoundary !== null) {
|
||||
switch (suspenseBoundary.tag) {
|
||||
case SuspenseComponent: {
|
||||
suspenseBoundary.flags &= ~ForceClientRender;
|
||||
markSuspenseBoundaryShouldCapture(
|
||||
suspenseBoundary,
|
||||
returnFiber,
|
||||
sourceFiber,
|
||||
root,
|
||||
rootRenderLanes,
|
||||
);
|
||||
// Retry listener
|
||||
//
|
||||
// If the fallback does commit, we need to attach a different type of
|
||||
// listener. This one schedules an update on the Suspense boundary to
|
||||
// turn the fallback state off.
|
||||
//
|
||||
// Stash the wakeable on the boundary fiber so we can access it in the
|
||||
// commit phase.
|
||||
//
|
||||
// When the wakeable resolves, we'll attempt to render the boundary
|
||||
// again ("retry").
|
||||
const wakeables: Set<Wakeable> | null = (suspenseBoundary.updateQueue: any);
|
||||
if (wakeables === null) {
|
||||
suspenseBoundary.updateQueue = new Set([wakeable]);
|
||||
} else {
|
||||
wakeables.add(wakeable);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OffscreenComponent: {
|
||||
if (suspenseBoundary.mode & ConcurrentMode) {
|
||||
suspenseBoundary.flags |= ShouldCapture;
|
||||
const offscreenQueue: OffscreenQueue | null = (suspenseBoundary.updateQueue: any);
|
||||
if (offscreenQueue === null) {
|
||||
const newOffscreenQueue: OffscreenQueue = {
|
||||
transitions: null,
|
||||
markerInstances: null,
|
||||
wakeables: new Set([wakeable]),
|
||||
};
|
||||
suspenseBoundary.updateQueue = newOffscreenQueue;
|
||||
} else {
|
||||
const wakeables = offscreenQueue.wakeables;
|
||||
if (wakeables === null) {
|
||||
offscreenQueue.wakeables = new Set([wakeable]);
|
||||
} else {
|
||||
wakeables.add(wakeable);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
default: {
|
||||
throw new Error(
|
||||
`Unexpected Suspense handler tag (${suspenseBoundary.tag}). This ` +
|
||||
'is a bug in React.',
|
||||
);
|
||||
}
|
||||
}
|
||||
// We only attach ping listeners in concurrent mode. Legacy Suspense always
|
||||
// commits fallbacks synchronously, so there are no pings.
|
||||
if (suspenseBoundary.mode & ConcurrentMode) {
|
||||
attachPingListener(root, wakeable, rootRenderLanes);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// No boundary was found. Unless this is a sync update, this is OK.
|
||||
// We can suspend and wait for more data to arrive.
|
||||
|
||||
if (root.tag === ConcurrentRoot) {
|
||||
// In a concurrent root, suspending without a Suspense boundary is
|
||||
// allowed. It will suspend indefinitely without committing.
|
||||
//
|
||||
// TODO: Should we have different behavior for discrete updates? What
|
||||
// about flushSync? Maybe it should put the tree into an inert state,
|
||||
// and potentially log a warning. Revisit this for a future release.
|
||||
attachPingListener(root, wakeable, rootRenderLanes);
|
||||
renderDidSuspendDelayIfPossible();
|
||||
return;
|
||||
} else {
|
||||
// In a legacy root, suspending without a boundary is always an error.
|
||||
const uncaughtSuspenseError = new Error(
|
||||
'A component suspended while responding to synchronous input. This ' +
|
||||
'will cause the UI to be replaced with a loading indicator. To ' +
|
||||
'fix, updates that suspend should be wrapped ' +
|
||||
'with startTransition.',
|
||||
);
|
||||
value = uncaughtSuspenseError;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a regular error, not a Suspense wakeable.
|
||||
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
|
||||
markDidThrowWhileHydratingDEV();
|
||||
const suspenseBoundary = getSuspenseHandler();
|
||||
// If the error was thrown during hydration, we may be able to recover by
|
||||
// discarding the dehydrated content and switching to a client render.
|
||||
// Instead of surfacing the error, find the nearest Suspense boundary
|
||||
// and render it again without hydration.
|
||||
if (suspenseBoundary !== null) {
|
||||
if ((suspenseBoundary.flags & ShouldCapture) === NoFlags) {
|
||||
// Set a flag to indicate that we should try rendering the normal
|
||||
// children again, not the fallback.
|
||||
suspenseBoundary.flags |= ForceClientRender;
|
||||
}
|
||||
markSuspenseBoundaryShouldCapture(
|
||||
suspenseBoundary,
|
||||
returnFiber,
|
||||
sourceFiber,
|
||||
root,
|
||||
rootRenderLanes,
|
||||
);
|
||||
|
||||
// Even though the user may not be affected by this error, we should
|
||||
// still log it so it can be fixed.
|
||||
queueHydrationError(createCapturedValueAtFiber(value, sourceFiber));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, fall through to the error path.
|
||||
}
|
||||
}
|
||||
|
||||
value = createCapturedValueAtFiber(value, sourceFiber);
|
||||
renderDidError(value);
|
||||
|
||||
// We didn't find a boundary that could handle this type of exception. Start
|
||||
// over and traverse parent path again, this time treating the exception
|
||||
// as an error.
|
||||
let workInProgress: Fiber = returnFiber;
|
||||
do {
|
||||
switch (workInProgress.tag) {
|
||||
case HostRoot: {
|
||||
const errorInfo = value;
|
||||
workInProgress.flags |= ShouldCapture;
|
||||
const lane = pickArbitraryLane(rootRenderLanes);
|
||||
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
|
||||
const update = createRootErrorUpdate(workInProgress, errorInfo, lane);
|
||||
enqueueCapturedUpdate(workInProgress, update);
|
||||
return;
|
||||
}
|
||||
case ClassComponent:
|
||||
// Capture and retry
|
||||
const errorInfo = value;
|
||||
const ctor = workInProgress.type;
|
||||
const instance = workInProgress.stateNode;
|
||||
if (
|
||||
(workInProgress.flags & DidCapture) === NoFlags &&
|
||||
(typeof ctor.getDerivedStateFromError === 'function' ||
|
||||
(instance !== null &&
|
||||
typeof instance.componentDidCatch === 'function' &&
|
||||
!isAlreadyFailedLegacyErrorBoundary(instance)))
|
||||
) {
|
||||
workInProgress.flags |= ShouldCapture;
|
||||
const lane = pickArbitraryLane(rootRenderLanes);
|
||||
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
|
||||
// Schedule the error boundary to re-render using updated state
|
||||
const update = createClassErrorUpdate(
|
||||
workInProgress,
|
||||
errorInfo,
|
||||
lane,
|
||||
);
|
||||
enqueueCapturedUpdate(workInProgress, update);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// $FlowFixMe[incompatible-type] we bail out when we get a null
|
||||
workInProgress = workInProgress.return;
|
||||
} while (workInProgress !== null);
|
||||
}
|
||||
|
||||
export {throwException, createRootErrorUpdate, createClassErrorUpdate};
|
|
@ -1,271 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {
|
||||
TransitionTracingCallbacks,
|
||||
Fiber,
|
||||
FiberRoot,
|
||||
} from './ReactInternalTypes';
|
||||
import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
|
||||
import type {StackCursor} from './ReactFiberStack.new';
|
||||
|
||||
import {enableTransitionTracing} from 'shared/ReactFeatureFlags';
|
||||
import {createCursor, push, pop} from './ReactFiberStack.new';
|
||||
import {getWorkInProgressTransitions} from './ReactFiberWorkLoop.new';
|
||||
|
||||
export type SuspenseInfo = {name: string | null};
|
||||
|
||||
export type PendingTransitionCallbacks = {
|
||||
transitionStart: Array<Transition> | null,
|
||||
transitionProgress: Map<Transition, PendingBoundaries> | null,
|
||||
transitionComplete: Array<Transition> | null,
|
||||
markerProgress: Map<
|
||||
string,
|
||||
{pendingBoundaries: PendingBoundaries, transitions: Set<Transition>},
|
||||
> | null,
|
||||
markerIncomplete: Map<
|
||||
string,
|
||||
{aborts: Array<TransitionAbort>, transitions: Set<Transition>},
|
||||
> | null,
|
||||
markerComplete: Map<string, Set<Transition>> | null,
|
||||
};
|
||||
|
||||
export type Transition = {
|
||||
name: string,
|
||||
startTime: number,
|
||||
};
|
||||
|
||||
export type BatchConfigTransition = {
|
||||
name?: string,
|
||||
startTime?: number,
|
||||
_updatedFibers?: Set<Fiber>,
|
||||
};
|
||||
|
||||
// TODO: Is there a way to not include the tag or name here?
|
||||
export type TracingMarkerInstance = {
|
||||
tag?: TracingMarkerTag,
|
||||
transitions: Set<Transition> | null,
|
||||
pendingBoundaries: PendingBoundaries | null,
|
||||
aborts: Array<TransitionAbort> | null,
|
||||
name: string | null,
|
||||
};
|
||||
|
||||
export type TransitionAbort = {
|
||||
reason: 'error' | 'unknown' | 'marker' | 'suspense',
|
||||
name?: string | null,
|
||||
};
|
||||
|
||||
export const TransitionRoot = 0;
|
||||
export const TransitionTracingMarker = 1;
|
||||
export type TracingMarkerTag = 0 | 1;
|
||||
|
||||
export type PendingBoundaries = Map<OffscreenInstance, SuspenseInfo>;
|
||||
|
||||
export function processTransitionCallbacks(
|
||||
pendingTransitions: PendingTransitionCallbacks,
|
||||
endTime: number,
|
||||
callbacks: TransitionTracingCallbacks,
|
||||
): void {
|
||||
if (enableTransitionTracing) {
|
||||
if (pendingTransitions !== null) {
|
||||
const transitionStart = pendingTransitions.transitionStart;
|
||||
const onTransitionStart = callbacks.onTransitionStart;
|
||||
if (transitionStart !== null && onTransitionStart != null) {
|
||||
transitionStart.forEach(transition =>
|
||||
onTransitionStart(transition.name, transition.startTime),
|
||||
);
|
||||
}
|
||||
|
||||
const markerProgress = pendingTransitions.markerProgress;
|
||||
const onMarkerProgress = callbacks.onMarkerProgress;
|
||||
if (onMarkerProgress != null && markerProgress !== null) {
|
||||
markerProgress.forEach((markerInstance, markerName) => {
|
||||
if (markerInstance.transitions !== null) {
|
||||
// TODO: Clone the suspense object so users can't modify it
|
||||
const pending =
|
||||
markerInstance.pendingBoundaries !== null
|
||||
? Array.from(markerInstance.pendingBoundaries.values())
|
||||
: [];
|
||||
markerInstance.transitions.forEach(transition => {
|
||||
onMarkerProgress(
|
||||
transition.name,
|
||||
markerName,
|
||||
transition.startTime,
|
||||
endTime,
|
||||
pending,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const markerComplete = pendingTransitions.markerComplete;
|
||||
const onMarkerComplete = callbacks.onMarkerComplete;
|
||||
if (markerComplete !== null && onMarkerComplete != null) {
|
||||
markerComplete.forEach((transitions, markerName) => {
|
||||
transitions.forEach(transition => {
|
||||
onMarkerComplete(
|
||||
transition.name,
|
||||
markerName,
|
||||
transition.startTime,
|
||||
endTime,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const markerIncomplete = pendingTransitions.markerIncomplete;
|
||||
const onMarkerIncomplete = callbacks.onMarkerIncomplete;
|
||||
if (onMarkerIncomplete != null && markerIncomplete !== null) {
|
||||
markerIncomplete.forEach(({transitions, aborts}, markerName) => {
|
||||
transitions.forEach(transition => {
|
||||
const filteredAborts = [];
|
||||
aborts.forEach(abort => {
|
||||
switch (abort.reason) {
|
||||
case 'marker': {
|
||||
filteredAborts.push({
|
||||
type: 'marker',
|
||||
name: abort.name,
|
||||
endTime,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'suspense': {
|
||||
filteredAborts.push({
|
||||
type: 'suspense',
|
||||
name: abort.name,
|
||||
endTime,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (filteredAborts.length > 0) {
|
||||
onMarkerIncomplete(
|
||||
transition.name,
|
||||
markerName,
|
||||
transition.startTime,
|
||||
filteredAborts,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const transitionProgress = pendingTransitions.transitionProgress;
|
||||
const onTransitionProgress = callbacks.onTransitionProgress;
|
||||
if (onTransitionProgress != null && transitionProgress !== null) {
|
||||
transitionProgress.forEach((pending, transition) => {
|
||||
onTransitionProgress(
|
||||
transition.name,
|
||||
transition.startTime,
|
||||
endTime,
|
||||
Array.from(pending.values()),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const transitionComplete = pendingTransitions.transitionComplete;
|
||||
const onTransitionComplete = callbacks.onTransitionComplete;
|
||||
if (transitionComplete !== null && onTransitionComplete != null) {
|
||||
transitionComplete.forEach(transition =>
|
||||
onTransitionComplete(transition.name, transition.startTime, endTime),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For every tracing marker, store a pointer to it. We will later access it
|
||||
// to get the set of suspense boundaries that need to resolve before the
|
||||
// tracing marker can be logged as complete
|
||||
// This code lives separate from the ReactFiberTransition code because
|
||||
// we push and pop on the tracing marker, not the suspense boundary
|
||||
const markerInstanceStack: StackCursor<Array<TracingMarkerInstance> | null> = createCursor(
|
||||
null,
|
||||
);
|
||||
|
||||
export function pushRootMarkerInstance(workInProgress: Fiber): void {
|
||||
if (enableTransitionTracing) {
|
||||
// On the root, every transition gets mapped to it's own map of
|
||||
// suspense boundaries. The transition is marked as complete when
|
||||
// the suspense boundaries map is empty. We do this because every
|
||||
// transition completes at different times and depends on different
|
||||
// suspense boundaries to complete. We store all the transitions
|
||||
// along with its map of suspense boundaries in the root incomplete
|
||||
// transitions map. Each entry in this map functions like a tracing
|
||||
// marker does, so we can push it onto the marker instance stack
|
||||
const transitions = getWorkInProgressTransitions();
|
||||
const root: FiberRoot = workInProgress.stateNode;
|
||||
|
||||
if (transitions !== null) {
|
||||
transitions.forEach(transition => {
|
||||
if (!root.incompleteTransitions.has(transition)) {
|
||||
const markerInstance: TracingMarkerInstance = {
|
||||
tag: TransitionRoot,
|
||||
transitions: new Set([transition]),
|
||||
pendingBoundaries: null,
|
||||
aborts: null,
|
||||
name: null,
|
||||
};
|
||||
root.incompleteTransitions.set(transition, markerInstance);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const markerInstances = [];
|
||||
// For ever transition on the suspense boundary, we push the transition
|
||||
// along with its map of pending suspense boundaries onto the marker
|
||||
// instance stack.
|
||||
root.incompleteTransitions.forEach(markerInstance => {
|
||||
markerInstances.push(markerInstance);
|
||||
});
|
||||
push(markerInstanceStack, markerInstances, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
export function popRootMarkerInstance(workInProgress: Fiber) {
|
||||
if (enableTransitionTracing) {
|
||||
pop(markerInstanceStack, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
export function pushMarkerInstance(
|
||||
workInProgress: Fiber,
|
||||
markerInstance: TracingMarkerInstance,
|
||||
): void {
|
||||
if (enableTransitionTracing) {
|
||||
if (markerInstanceStack.current === null) {
|
||||
push(markerInstanceStack, [markerInstance], workInProgress);
|
||||
} else {
|
||||
push(
|
||||
markerInstanceStack,
|
||||
markerInstanceStack.current.concat(markerInstance),
|
||||
workInProgress,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function popMarkerInstance(workInProgress: Fiber): void {
|
||||
if (enableTransitionTracing) {
|
||||
pop(markerInstanceStack, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
export function getMarkerInstances(): Array<TracingMarkerInstance> | null {
|
||||
if (enableTransitionTracing) {
|
||||
return markerInstanceStack.current;
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import type {Transition} from './ReactFiberTracingMarkerComponent.new';
|
||||
|
||||
const {ReactCurrentBatchConfig} = ReactSharedInternals;
|
||||
|
||||
export const NoTransition = null;
|
||||
|
||||
export function requestCurrentTransition(): Transition | null {
|
||||
return ReactCurrentBatchConfig.transition;
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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, FiberRoot} from './ReactInternalTypes';
|
||||
import type {Lanes} from './ReactFiberLane.new';
|
||||
import type {StackCursor} from './ReactFiberStack.new';
|
||||
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.new';
|
||||
import type {Transition} from './ReactFiberTracingMarkerComponent.new';
|
||||
|
||||
import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags';
|
||||
import {isPrimaryRenderer} from './ReactFiberHostConfig';
|
||||
import {createCursor, push, pop} from './ReactFiberStack.new';
|
||||
import {
|
||||
getWorkInProgressRoot,
|
||||
getWorkInProgressTransitions,
|
||||
} from './ReactFiberWorkLoop.new';
|
||||
import {
|
||||
createCache,
|
||||
retainCache,
|
||||
CacheContext,
|
||||
} from './ReactFiberCacheComponent.new';
|
||||
|
||||
// When retrying a Suspense/Offscreen boundary, we restore the cache that was
|
||||
// used during the previous render by placing it here, on the stack.
|
||||
const resumedCache: StackCursor<Cache | null> = createCursor(null);
|
||||
|
||||
// During the render/synchronous commit phase, we don't actually process the
|
||||
// transitions. Therefore, we want to lazily combine transitions. Instead of
|
||||
// comparing the arrays of transitions when we combine them and storing them
|
||||
// and filtering out the duplicates, we will instead store the unprocessed transitions
|
||||
// in an array and actually filter them in the passive phase.
|
||||
const transitionStack: StackCursor<Array<Transition> | null> = createCursor(
|
||||
null,
|
||||
);
|
||||
|
||||
function peekCacheFromPool(): Cache | null {
|
||||
if (!enableCache) {
|
||||
return (null: any);
|
||||
}
|
||||
|
||||
// Check if the cache pool already has a cache we can use.
|
||||
|
||||
// If we're rendering inside a Suspense boundary that is currently hidden,
|
||||
// we should use the same cache that we used during the previous render, if
|
||||
// one exists.
|
||||
const cacheResumedFromPreviousRender = resumedCache.current;
|
||||
if (cacheResumedFromPreviousRender !== null) {
|
||||
return cacheResumedFromPreviousRender;
|
||||
}
|
||||
|
||||
// Otherwise, check the root's cache pool.
|
||||
const root = (getWorkInProgressRoot(): any);
|
||||
const cacheFromRootCachePool = root.pooledCache;
|
||||
|
||||
return cacheFromRootCachePool;
|
||||
}
|
||||
|
||||
export function requestCacheFromPool(renderLanes: Lanes): Cache {
|
||||
// Similar to previous function, except if there's not already a cache in the
|
||||
// pool, we allocate a new one.
|
||||
const cacheFromPool = peekCacheFromPool();
|
||||
if (cacheFromPool !== null) {
|
||||
return cacheFromPool;
|
||||
}
|
||||
|
||||
// Create a fresh cache and add it to the root cache pool. A cache can have
|
||||
// multiple owners:
|
||||
// - A cache pool that lives on the FiberRoot. This is where all fresh caches
|
||||
// are originally created (TODO: except during refreshes, until we implement
|
||||
// this correctly). The root takes ownership immediately when the cache is
|
||||
// created. Conceptually, root.pooledCache is an Option<Arc<Cache>> (owned),
|
||||
// and the return value of this function is a &Arc<Cache> (borrowed).
|
||||
// - One of several fiber types: host root, cache boundary, suspense
|
||||
// component. These retain and release in the commit phase.
|
||||
|
||||
const root = (getWorkInProgressRoot(): any);
|
||||
const freshCache = createCache();
|
||||
root.pooledCache = freshCache;
|
||||
retainCache(freshCache);
|
||||
if (freshCache !== null) {
|
||||
root.pooledCacheLanes |= renderLanes;
|
||||
}
|
||||
return freshCache;
|
||||
}
|
||||
|
||||
export function pushRootTransition(
|
||||
workInProgress: Fiber,
|
||||
root: FiberRoot,
|
||||
renderLanes: Lanes,
|
||||
) {
|
||||
if (enableTransitionTracing) {
|
||||
const rootTransitions = getWorkInProgressTransitions();
|
||||
push(transitionStack, rootTransitions, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
export function popRootTransition(
|
||||
workInProgress: Fiber,
|
||||
root: FiberRoot,
|
||||
renderLanes: Lanes,
|
||||
) {
|
||||
if (enableTransitionTracing) {
|
||||
pop(transitionStack, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
export function pushTransition(
|
||||
offscreenWorkInProgress: Fiber,
|
||||
prevCachePool: SpawnedCachePool | null,
|
||||
newTransitions: Array<Transition> | null,
|
||||
): void {
|
||||
if (enableCache) {
|
||||
if (prevCachePool === null) {
|
||||
push(resumedCache, resumedCache.current, offscreenWorkInProgress);
|
||||
} else {
|
||||
push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableTransitionTracing) {
|
||||
if (transitionStack.current === null) {
|
||||
push(transitionStack, newTransitions, offscreenWorkInProgress);
|
||||
} else if (newTransitions === null) {
|
||||
push(transitionStack, transitionStack.current, offscreenWorkInProgress);
|
||||
} else {
|
||||
push(
|
||||
transitionStack,
|
||||
transitionStack.current.concat(newTransitions),
|
||||
offscreenWorkInProgress,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function popTransition(workInProgress: Fiber, current: Fiber | null) {
|
||||
if (current !== null) {
|
||||
if (enableTransitionTracing) {
|
||||
pop(transitionStack, workInProgress);
|
||||
}
|
||||
|
||||
if (enableCache) {
|
||||
pop(resumedCache, workInProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getPendingTransitions(): Array<Transition> | null {
|
||||
if (!enableTransitionTracing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return transitionStack.current;
|
||||
}
|
||||
|
||||
export function getSuspendedCache(): SpawnedCachePool | null {
|
||||
if (!enableCache) {
|
||||
return null;
|
||||
}
|
||||
// This function is called when a Suspense boundary suspends. It returns the
|
||||
// cache that would have been used to render fresh data during this render,
|
||||
// if there was any, so that we can resume rendering with the same cache when
|
||||
// we receive more data.
|
||||
const cacheFromPool = peekCacheFromPool();
|
||||
if (cacheFromPool === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
// We must also save the parent, so that when we resume we can detect
|
||||
// a refresh.
|
||||
parent: isPrimaryRenderer
|
||||
? CacheContext._currentValue
|
||||
: CacheContext._currentValue2,
|
||||
pool: cacheFromPool,
|
||||
};
|
||||
}
|
||||
|
||||
export function getOffscreenDeferredCache(): SpawnedCachePool | null {
|
||||
if (!enableCache) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheFromPool = peekCacheFromPool();
|
||||
if (cacheFromPool === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
// We must also store the parent, so that when we resume we can detect
|
||||
// a refresh.
|
||||
parent: isPrimaryRenderer
|
||||
? CacheContext._currentValue
|
||||
: CacheContext._currentValue2,
|
||||
pool: cacheFromPool,
|
||||
};
|
||||
}
|
|
@ -25,6 +25,16 @@ import {
|
|||
CacheContext,
|
||||
} from './ReactFiberCacheComponent.old';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
|
||||
const {ReactCurrentBatchConfig} = ReactSharedInternals;
|
||||
|
||||
export const NoTransition = null;
|
||||
|
||||
export function requestCurrentTransition(): Transition | null {
|
||||
return ReactCurrentBatchConfig.transition;
|
||||
}
|
||||
|
||||
// When retrying a Suspense/Offscreen boundary, we restore the cache that was
|
||||
// used during the previous render by placing it here, on the stack.
|
||||
const resumedCache: StackCursor<Cache | null> = createCursor(null);
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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, FiberRoot} from './ReactInternalTypes';
|
||||
import type {Lanes} from './ReactFiberLane.old';
|
||||
import type {StackCursor} from './ReactFiberStack.old';
|
||||
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old';
|
||||
|
||||
import {enableCache} from 'shared/ReactFeatureFlags';
|
||||
import {isPrimaryRenderer} from './ReactFiberHostConfig';
|
||||
import {createCursor, push, pop} from './ReactFiberStack.old';
|
||||
import {getWorkInProgressRoot} from './ReactFiberWorkLoop.old';
|
||||
import {
|
||||
createCache,
|
||||
retainCache,
|
||||
CacheContext,
|
||||
} from './ReactFiberCacheComponent.old';
|
||||
|
||||
// When retrying a Suspense/Offscreen boundary, we restore the cache that was
|
||||
// used during the previous render by placing it here, on the stack.
|
||||
const resumedCache: StackCursor<Cache | null> = createCursor(null);
|
||||
|
||||
function peekCacheFromPool(): Cache | null {
|
||||
if (!enableCache) {
|
||||
return (null: any);
|
||||
}
|
||||
|
||||
// Check if the cache pool already has a cache we can use.
|
||||
|
||||
// If we're rendering inside a Suspense boundary that is currently hidden,
|
||||
// we should use the same cache that we used during the previous render, if
|
||||
// one exists.
|
||||
const cacheResumedFromPreviousRender = resumedCache.current;
|
||||
if (cacheResumedFromPreviousRender !== null) {
|
||||
return cacheResumedFromPreviousRender;
|
||||
}
|
||||
|
||||
// Otherwise, check the root's cache pool.
|
||||
const root = (getWorkInProgressRoot(): any);
|
||||
const cacheFromRootCachePool = root.pooledCache;
|
||||
|
||||
return cacheFromRootCachePool;
|
||||
}
|
||||
|
||||
export function requestCacheFromPool(renderLanes: Lanes): Cache {
|
||||
// Similar to previous function, except if there's not already a cache in the
|
||||
// pool, we allocate a new one.
|
||||
const cacheFromPool = peekCacheFromPool();
|
||||
if (cacheFromPool !== null) {
|
||||
return cacheFromPool;
|
||||
}
|
||||
|
||||
// Create a fresh cache and add it to the root cache pool. A cache can have
|
||||
// multiple owners:
|
||||
// - A cache pool that lives on the FiberRoot. This is where all fresh caches
|
||||
// are originally created (TODO: except during refreshes, until we implement
|
||||
// this correctly). The root takes ownership immediately when the cache is
|
||||
// created. Conceptually, root.pooledCache is an Option<Arc<Cache>> (owned),
|
||||
// and the return value of this function is a &Arc<Cache> (borrowed).
|
||||
// - One of several fiber types: host root, cache boundary, suspense
|
||||
// component. These retain and release in the commit phase.
|
||||
|
||||
const root = (getWorkInProgressRoot(): any);
|
||||
const freshCache = createCache();
|
||||
root.pooledCache = freshCache;
|
||||
retainCache(freshCache);
|
||||
if (freshCache !== null) {
|
||||
root.pooledCacheLanes |= renderLanes;
|
||||
}
|
||||
return freshCache;
|
||||
}
|
||||
|
||||
export function pushRootTransitionPool(root: FiberRoot) {
|
||||
if (enableCache) {
|
||||
return;
|
||||
}
|
||||
// Note: This function currently does nothing but I'll leave it here for
|
||||
// code organization purposes in case that changes.
|
||||
}
|
||||
|
||||
export function popRootTransitionPool(root: FiberRoot, renderLanes: Lanes) {
|
||||
if (enableCache) {
|
||||
return;
|
||||
}
|
||||
// Note: This function currently does nothing but I'll leave it here for
|
||||
// code organization purposes in case that changes.
|
||||
}
|
||||
|
||||
export function pushTransitionPool(
|
||||
offscreenWorkInProgress: Fiber,
|
||||
prevCachePool: SpawnedCachePool | null,
|
||||
): void {
|
||||
if (enableCache) {
|
||||
if (prevCachePool === null) {
|
||||
push(resumedCache, resumedCache.current, offscreenWorkInProgress);
|
||||
} else {
|
||||
push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function popTransitionPool(workInProgress: Fiber) {
|
||||
if (enableCache) {
|
||||
pop(resumedCache, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
export function getSuspendedCachePool(): SpawnedCachePool | null {
|
||||
if (!enableCache) {
|
||||
return null;
|
||||
}
|
||||
// This function is called when a Suspense boundary suspends. It returns the
|
||||
// cache that would have been used to render fresh data during this render,
|
||||
// if there was any, so that we can resume rendering with the same cache when
|
||||
// we receive more data.
|
||||
const cacheFromPool = peekCacheFromPool();
|
||||
if (cacheFromPool === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
// We must also save the parent, so that when we resume we can detect
|
||||
// a refresh.
|
||||
parent: isPrimaryRenderer
|
||||
? CacheContext._currentValue
|
||||
: CacheContext._currentValue2,
|
||||
pool: cacheFromPool,
|
||||
};
|
||||
}
|
||||
|
||||
export function getOffscreenDeferredCachePool(): SpawnedCachePool | null {
|
||||
if (!enableCache) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheFromPool = peekCacheFromPool();
|
||||
if (cacheFromPool === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
// We must also store the parent, so that when we resume we can detect
|
||||
// a refresh.
|
||||
parent: isPrimaryRenderer
|
||||
? CacheContext._currentValue
|
||||
: CacheContext._currentValue2,
|
||||
pool: cacheFromPool,
|
||||
};
|
||||
}
|
|
@ -1,289 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// Ids are base 32 strings whose binary representation corresponds to the
|
||||
// position of a node in a tree.
|
||||
|
||||
// Every time the tree forks into multiple children, we add additional bits to
|
||||
// the left of the sequence that represent the position of the child within the
|
||||
// current level of children.
|
||||
//
|
||||
// 00101 00010001011010101
|
||||
// ╰─┬─╯ ╰───────┬───────╯
|
||||
// Fork 5 of 20 Parent id
|
||||
//
|
||||
// The leading 0s are important. In the above example, you only need 3 bits to
|
||||
// represent slot 5. However, you need 5 bits to represent all the forks at
|
||||
// the current level, so we must account for the empty bits at the end.
|
||||
//
|
||||
// For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise,
|
||||
// the zeroth id at a level would be indistinguishable from its parent.
|
||||
//
|
||||
// If a node has only one child, and does not materialize an id (i.e. does not
|
||||
// contain a useId hook), then we don't need to allocate any space in the
|
||||
// sequence. It's treated as a transparent indirection. For example, these two
|
||||
// trees produce the same ids:
|
||||
//
|
||||
// <> <>
|
||||
// <Indirection> <A />
|
||||
// <A /> <B />
|
||||
// </Indirection> </>
|
||||
// <B />
|
||||
// </>
|
||||
//
|
||||
// However, we cannot skip any node that materializes an id. Otherwise, a parent
|
||||
// id that does not fork would be indistinguishable from its child id. For
|
||||
// example, this tree does not fork, but the parent and child must have
|
||||
// different ids.
|
||||
//
|
||||
// <Parent>
|
||||
// <Child />
|
||||
// </Parent>
|
||||
//
|
||||
// To handle this scenario, every time we materialize an id, we allocate a
|
||||
// new level with a single slot. You can think of this as a fork with only one
|
||||
// prong, or an array of children with length 1.
|
||||
//
|
||||
// It's possible for the size of the sequence to exceed 32 bits, the max
|
||||
// size for bitwise operations. When this happens, we make more room by
|
||||
// converting the right part of the id to a string and storing it in an overflow
|
||||
// variable. We use a base 32 string representation, because 32 is the largest
|
||||
// power of 2 that is supported by toString(). We want the base to be large so
|
||||
// that the resulting ids are compact, and we want the base to be a power of 2
|
||||
// because every log2(base) bits corresponds to a single character, i.e. every
|
||||
// log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without
|
||||
// affecting the final result.
|
||||
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
|
||||
import {getIsHydrating} from './ReactFiberHydrationContext.new';
|
||||
import {clz32} from './clz32';
|
||||
import {Forked, NoFlags} from './ReactFiberFlags';
|
||||
|
||||
export type TreeContext = {
|
||||
id: number,
|
||||
overflow: string,
|
||||
};
|
||||
|
||||
// TODO: Use the unified fiber stack module instead of this local one?
|
||||
// Intentionally not using it yet to derisk the initial implementation, because
|
||||
// the way we push/pop these values is a bit unusual. If there's a mistake, I'd
|
||||
// rather the ids be wrong than crash the whole reconciler.
|
||||
const forkStack: Array<any> = [];
|
||||
let forkStackIndex: number = 0;
|
||||
let treeForkProvider: Fiber | null = null;
|
||||
let treeForkCount: number = 0;
|
||||
|
||||
const idStack: Array<any> = [];
|
||||
let idStackIndex: number = 0;
|
||||
let treeContextProvider: Fiber | null = null;
|
||||
let treeContextId: number = 1;
|
||||
let treeContextOverflow: string = '';
|
||||
|
||||
export function isForkedChild(workInProgress: Fiber): boolean {
|
||||
warnIfNotHydrating();
|
||||
return (workInProgress.flags & Forked) !== NoFlags;
|
||||
}
|
||||
|
||||
export function getForksAtLevel(workInProgress: Fiber): number {
|
||||
warnIfNotHydrating();
|
||||
return treeForkCount;
|
||||
}
|
||||
|
||||
export function getTreeId(): string {
|
||||
const overflow = treeContextOverflow;
|
||||
const idWithLeadingBit = treeContextId;
|
||||
const id = idWithLeadingBit & ~getLeadingBit(idWithLeadingBit);
|
||||
return id.toString(32) + overflow;
|
||||
}
|
||||
|
||||
export function pushTreeFork(
|
||||
workInProgress: Fiber,
|
||||
totalChildren: number,
|
||||
): void {
|
||||
// This is called right after we reconcile an array (or iterator) of child
|
||||
// fibers, because that's the only place where we know how many children in
|
||||
// the whole set without doing extra work later, or storing addtional
|
||||
// information on the fiber.
|
||||
//
|
||||
// That's why this function is separate from pushTreeId — it's called during
|
||||
// the render phase of the fork parent, not the child, which is where we push
|
||||
// the other context values.
|
||||
//
|
||||
// In the Fizz implementation this is much simpler because the child is
|
||||
// rendered in the same callstack as the parent.
|
||||
//
|
||||
// It might be better to just add a `forks` field to the Fiber type. It would
|
||||
// make this module simpler.
|
||||
|
||||
warnIfNotHydrating();
|
||||
|
||||
forkStack[forkStackIndex++] = treeForkCount;
|
||||
forkStack[forkStackIndex++] = treeForkProvider;
|
||||
|
||||
treeForkProvider = workInProgress;
|
||||
treeForkCount = totalChildren;
|
||||
}
|
||||
|
||||
export function pushTreeId(
|
||||
workInProgress: Fiber,
|
||||
totalChildren: number,
|
||||
index: number,
|
||||
) {
|
||||
warnIfNotHydrating();
|
||||
|
||||
idStack[idStackIndex++] = treeContextId;
|
||||
idStack[idStackIndex++] = treeContextOverflow;
|
||||
idStack[idStackIndex++] = treeContextProvider;
|
||||
|
||||
treeContextProvider = workInProgress;
|
||||
|
||||
const baseIdWithLeadingBit = treeContextId;
|
||||
const baseOverflow = treeContextOverflow;
|
||||
|
||||
// The leftmost 1 marks the end of the sequence, non-inclusive. It's not part
|
||||
// of the id; we use it to account for leading 0s.
|
||||
const baseLength = getBitLength(baseIdWithLeadingBit) - 1;
|
||||
const baseId = baseIdWithLeadingBit & ~(1 << baseLength);
|
||||
|
||||
const slot = index + 1;
|
||||
const length = getBitLength(totalChildren) + baseLength;
|
||||
|
||||
// 30 is the max length we can store without overflowing, taking into
|
||||
// consideration the leading 1 we use to mark the end of the sequence.
|
||||
if (length > 30) {
|
||||
// We overflowed the bitwise-safe range. Fall back to slower algorithm.
|
||||
// This branch assumes the length of the base id is greater than 5; it won't
|
||||
// work for smaller ids, because you need 5 bits per character.
|
||||
//
|
||||
// We encode the id in multiple steps: first the base id, then the
|
||||
// remaining digits.
|
||||
//
|
||||
// Each 5 bit sequence corresponds to a single base 32 character. So for
|
||||
// example, if the current id is 23 bits long, we can convert 20 of those
|
||||
// bits into a string of 4 characters, with 3 bits left over.
|
||||
//
|
||||
// First calculate how many bits in the base id represent a complete
|
||||
// sequence of characters.
|
||||
const numberOfOverflowBits = baseLength - (baseLength % 5);
|
||||
|
||||
// Then create a bitmask that selects only those bits.
|
||||
const newOverflowBits = (1 << numberOfOverflowBits) - 1;
|
||||
|
||||
// Select the bits, and convert them to a base 32 string.
|
||||
const newOverflow = (baseId & newOverflowBits).toString(32);
|
||||
|
||||
// Now we can remove those bits from the base id.
|
||||
const restOfBaseId = baseId >> numberOfOverflowBits;
|
||||
const restOfBaseLength = baseLength - numberOfOverflowBits;
|
||||
|
||||
// Finally, encode the rest of the bits using the normal algorithm. Because
|
||||
// we made more room, this time it won't overflow.
|
||||
const restOfLength = getBitLength(totalChildren) + restOfBaseLength;
|
||||
const restOfNewBits = slot << restOfBaseLength;
|
||||
const id = restOfNewBits | restOfBaseId;
|
||||
const overflow = newOverflow + baseOverflow;
|
||||
|
||||
treeContextId = (1 << restOfLength) | id;
|
||||
treeContextOverflow = overflow;
|
||||
} else {
|
||||
// Normal path
|
||||
const newBits = slot << baseLength;
|
||||
const id = newBits | baseId;
|
||||
const overflow = baseOverflow;
|
||||
|
||||
treeContextId = (1 << length) | id;
|
||||
treeContextOverflow = overflow;
|
||||
}
|
||||
}
|
||||
|
||||
export function pushMaterializedTreeId(workInProgress: Fiber) {
|
||||
warnIfNotHydrating();
|
||||
|
||||
// This component materialized an id. This will affect any ids that appear
|
||||
// in its children.
|
||||
const returnFiber = workInProgress.return;
|
||||
if (returnFiber !== null) {
|
||||
const numberOfForks = 1;
|
||||
const slotIndex = 0;
|
||||
pushTreeFork(workInProgress, numberOfForks);
|
||||
pushTreeId(workInProgress, numberOfForks, slotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function getBitLength(number: number): number {
|
||||
return 32 - clz32(number);
|
||||
}
|
||||
|
||||
function getLeadingBit(id: number) {
|
||||
return 1 << (getBitLength(id) - 1);
|
||||
}
|
||||
|
||||
export function popTreeContext(workInProgress: Fiber) {
|
||||
// Restore the previous values.
|
||||
|
||||
// This is a bit more complicated than other context-like modules in Fiber
|
||||
// because the same Fiber may appear on the stack multiple times and for
|
||||
// different reasons. We have to keep popping until the work-in-progress is
|
||||
// no longer at the top of the stack.
|
||||
|
||||
while (workInProgress === treeForkProvider) {
|
||||
treeForkProvider = forkStack[--forkStackIndex];
|
||||
forkStack[forkStackIndex] = null;
|
||||
treeForkCount = forkStack[--forkStackIndex];
|
||||
forkStack[forkStackIndex] = null;
|
||||
}
|
||||
|
||||
while (workInProgress === treeContextProvider) {
|
||||
treeContextProvider = idStack[--idStackIndex];
|
||||
idStack[idStackIndex] = null;
|
||||
treeContextOverflow = idStack[--idStackIndex];
|
||||
idStack[idStackIndex] = null;
|
||||
treeContextId = idStack[--idStackIndex];
|
||||
idStack[idStackIndex] = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getSuspendedTreeContext(): TreeContext | null {
|
||||
warnIfNotHydrating();
|
||||
if (treeContextProvider !== null) {
|
||||
return {
|
||||
id: treeContextId,
|
||||
overflow: treeContextOverflow,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function restoreSuspendedTreeContext(
|
||||
workInProgress: Fiber,
|
||||
suspendedContext: TreeContext,
|
||||
) {
|
||||
warnIfNotHydrating();
|
||||
|
||||
idStack[idStackIndex++] = treeContextId;
|
||||
idStack[idStackIndex++] = treeContextOverflow;
|
||||
idStack[idStackIndex++] = treeContextProvider;
|
||||
|
||||
treeContextId = suspendedContext.id;
|
||||
treeContextOverflow = suspendedContext.overflow;
|
||||
treeContextProvider = workInProgress;
|
||||
}
|
||||
|
||||
function warnIfNotHydrating() {
|
||||
if (__DEV__) {
|
||||
if (!getIsHydrating()) {
|
||||
console.error(
|
||||
'Expected to be hydrating. This is a bug in React. Please file ' +
|
||||
'an issue.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,285 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {ReactContext} from 'shared/ReactTypes';
|
||||
import type {Fiber, FiberRoot} from './ReactInternalTypes';
|
||||
import type {Lanes} from './ReactFiberLane.new';
|
||||
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
|
||||
import type {Cache} from './ReactFiberCacheComponent.new';
|
||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
|
||||
|
||||
import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
|
||||
import {
|
||||
ClassComponent,
|
||||
HostRoot,
|
||||
HostComponent,
|
||||
HostResource,
|
||||
HostSingleton,
|
||||
HostPortal,
|
||||
ContextProvider,
|
||||
SuspenseComponent,
|
||||
SuspenseListComponent,
|
||||
OffscreenComponent,
|
||||
LegacyHiddenComponent,
|
||||
CacheComponent,
|
||||
TracingMarkerComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {DidCapture, NoFlags, ShouldCapture} from './ReactFiberFlags';
|
||||
import {NoMode, ProfileMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
enableProfilerTimer,
|
||||
enableCache,
|
||||
enableTransitionTracing,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {popHostContainer, popHostContext} from './ReactFiberHostContext.new';
|
||||
import {
|
||||
popSuspenseListContext,
|
||||
popSuspenseHandler,
|
||||
} from './ReactFiberSuspenseContext.new';
|
||||
import {popHiddenContext} from './ReactFiberHiddenContext.new';
|
||||
import {resetHydrationState} from './ReactFiberHydrationContext.new';
|
||||
import {
|
||||
isContextProvider as isLegacyContextProvider,
|
||||
popContext as popLegacyContext,
|
||||
popTopLevelContextObject as popTopLevelLegacyContextObject,
|
||||
} from './ReactFiberContext.new';
|
||||
import {popProvider} from './ReactFiberNewContext.new';
|
||||
import {popCacheProvider} from './ReactFiberCacheComponent.new';
|
||||
import {transferActualDuration} from './ReactProfilerTimer.new';
|
||||
import {popTreeContext} from './ReactFiberTreeContext.new';
|
||||
import {popRootTransition, popTransition} from './ReactFiberTransition.new';
|
||||
import {
|
||||
popMarkerInstance,
|
||||
popRootMarkerInstance,
|
||||
} from './ReactFiberTracingMarkerComponent.new';
|
||||
|
||||
function unwindWork(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
renderLanes: Lanes,
|
||||
): Fiber | null {
|
||||
// Note: This intentionally doesn't check if we're hydrating because comparing
|
||||
// to the current tree provider fiber is just as fast and less error-prone.
|
||||
// Ideally we would have a special version of the work loop only
|
||||
// for hydration.
|
||||
popTreeContext(workInProgress);
|
||||
switch (workInProgress.tag) {
|
||||
case ClassComponent: {
|
||||
const Component = workInProgress.type;
|
||||
if (isLegacyContextProvider(Component)) {
|
||||
popLegacyContext(workInProgress);
|
||||
}
|
||||
const flags = workInProgress.flags;
|
||||
if (flags & ShouldCapture) {
|
||||
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
||||
if (
|
||||
enableProfilerTimer &&
|
||||
(workInProgress.mode & ProfileMode) !== NoMode
|
||||
) {
|
||||
transferActualDuration(workInProgress);
|
||||
}
|
||||
return workInProgress;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case HostRoot: {
|
||||
const root: FiberRoot = workInProgress.stateNode;
|
||||
if (enableCache) {
|
||||
const cache: Cache = workInProgress.memoizedState.cache;
|
||||
popCacheProvider(workInProgress, cache);
|
||||
}
|
||||
|
||||
if (enableTransitionTracing) {
|
||||
popRootMarkerInstance(workInProgress);
|
||||
}
|
||||
|
||||
popRootTransition(workInProgress, root, renderLanes);
|
||||
popHostContainer(workInProgress);
|
||||
popTopLevelLegacyContextObject(workInProgress);
|
||||
resetMutableSourceWorkInProgressVersions();
|
||||
const flags = workInProgress.flags;
|
||||
if (
|
||||
(flags & ShouldCapture) !== NoFlags &&
|
||||
(flags & DidCapture) === NoFlags
|
||||
) {
|
||||
// There was an error during render that wasn't captured by a suspense
|
||||
// boundary. Do a second pass on the root to unmount the children.
|
||||
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
||||
return workInProgress;
|
||||
}
|
||||
// We unwound to the root without completing it. Exit.
|
||||
return null;
|
||||
}
|
||||
case HostResource:
|
||||
case HostSingleton:
|
||||
case HostComponent: {
|
||||
// TODO: popHydrationState
|
||||
popHostContext(workInProgress);
|
||||
return null;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
popSuspenseHandler(workInProgress);
|
||||
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
||||
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
||||
if (workInProgress.alternate === null) {
|
||||
throw new Error(
|
||||
'Threw in newly mounted dehydrated component. This is likely a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
resetHydrationState();
|
||||
}
|
||||
|
||||
const flags = workInProgress.flags;
|
||||
if (flags & ShouldCapture) {
|
||||
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
||||
// Captured a suspense effect. Re-render the boundary.
|
||||
if (
|
||||
enableProfilerTimer &&
|
||||
(workInProgress.mode & ProfileMode) !== NoMode
|
||||
) {
|
||||
transferActualDuration(workInProgress);
|
||||
}
|
||||
return workInProgress;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case SuspenseListComponent: {
|
||||
popSuspenseListContext(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:
|
||||
const context: ReactContext<any> = workInProgress.type._context;
|
||||
popProvider(context, workInProgress);
|
||||
return null;
|
||||
case OffscreenComponent:
|
||||
case LegacyHiddenComponent: {
|
||||
popSuspenseHandler(workInProgress);
|
||||
popHiddenContext(workInProgress);
|
||||
popTransition(workInProgress, current);
|
||||
const flags = workInProgress.flags;
|
||||
if (flags & ShouldCapture) {
|
||||
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
||||
// Captured a suspense effect. Re-render the boundary.
|
||||
if (
|
||||
enableProfilerTimer &&
|
||||
(workInProgress.mode & ProfileMode) !== NoMode
|
||||
) {
|
||||
transferActualDuration(workInProgress);
|
||||
}
|
||||
return workInProgress;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case CacheComponent:
|
||||
if (enableCache) {
|
||||
const cache: Cache = workInProgress.memoizedState.cache;
|
||||
popCacheProvider(workInProgress, cache);
|
||||
}
|
||||
return null;
|
||||
case TracingMarkerComponent:
|
||||
if (enableTransitionTracing) {
|
||||
if (workInProgress.stateNode !== null) {
|
||||
popMarkerInstance(workInProgress);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function unwindInterruptedWork(
|
||||
current: Fiber | null,
|
||||
interruptedWork: Fiber,
|
||||
renderLanes: Lanes,
|
||||
) {
|
||||
// Note: This intentionally doesn't check if we're hydrating because comparing
|
||||
// to the current tree provider fiber is just as fast and less error-prone.
|
||||
// Ideally we would have a special version of the work loop only
|
||||
// for hydration.
|
||||
popTreeContext(interruptedWork);
|
||||
switch (interruptedWork.tag) {
|
||||
case ClassComponent: {
|
||||
const childContextTypes = interruptedWork.type.childContextTypes;
|
||||
if (childContextTypes !== null && childContextTypes !== undefined) {
|
||||
popLegacyContext(interruptedWork);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HostRoot: {
|
||||
const root: FiberRoot = interruptedWork.stateNode;
|
||||
if (enableCache) {
|
||||
const cache: Cache = interruptedWork.memoizedState.cache;
|
||||
popCacheProvider(interruptedWork, cache);
|
||||
}
|
||||
|
||||
if (enableTransitionTracing) {
|
||||
popRootMarkerInstance(interruptedWork);
|
||||
}
|
||||
|
||||
popRootTransition(interruptedWork, root, renderLanes);
|
||||
popHostContainer(interruptedWork);
|
||||
popTopLevelLegacyContextObject(interruptedWork);
|
||||
resetMutableSourceWorkInProgressVersions();
|
||||
break;
|
||||
}
|
||||
case HostResource:
|
||||
case HostSingleton:
|
||||
case HostComponent: {
|
||||
popHostContext(interruptedWork);
|
||||
break;
|
||||
}
|
||||
case HostPortal:
|
||||
popHostContainer(interruptedWork);
|
||||
break;
|
||||
case SuspenseComponent:
|
||||
popSuspenseHandler(interruptedWork);
|
||||
break;
|
||||
case SuspenseListComponent:
|
||||
popSuspenseListContext(interruptedWork);
|
||||
break;
|
||||
case ContextProvider:
|
||||
const context: ReactContext<any> = interruptedWork.type._context;
|
||||
popProvider(context, interruptedWork);
|
||||
break;
|
||||
case OffscreenComponent:
|
||||
case LegacyHiddenComponent:
|
||||
popSuspenseHandler(interruptedWork);
|
||||
popHiddenContext(interruptedWork);
|
||||
popTransition(interruptedWork, current);
|
||||
break;
|
||||
case CacheComponent:
|
||||
if (enableCache) {
|
||||
const cache: Cache = interruptedWork.memoizedState.cache;
|
||||
popCacheProvider(interruptedWork, cache);
|
||||
}
|
||||
break;
|
||||
case TracingMarkerComponent:
|
||||
if (enableTransitionTracing) {
|
||||
const instance: TracingMarkerInstance | null =
|
||||
interruptedWork.stateNode;
|
||||
if (instance !== null) {
|
||||
popMarkerInstance(interruptedWork);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export {unwindWork, unwindInterruptedWork};
|
File diff suppressed because it is too large
Load Diff
|
@ -176,7 +176,10 @@ import {
|
|||
lowerEventPriority,
|
||||
lanesToEventPriority,
|
||||
} from './ReactEventPriorities.old';
|
||||
import {requestCurrentTransition, NoTransition} from './ReactFiberTransition';
|
||||
import {
|
||||
requestCurrentTransition,
|
||||
NoTransition,
|
||||
} from './ReactFiberTransition.old';
|
||||
import {
|
||||
SelectiveHydrationException,
|
||||
beginWork as originalBeginWork,
|
||||
|
|
|
@ -31,13 +31,11 @@ import type {
|
|||
SuspenseInstance,
|
||||
} from './ReactFiberHostConfig';
|
||||
import type {Cache} from './ReactFiberCacheComponent.old';
|
||||
// Doing this because there's a merge conflict because of the way sync-reconciler-fork
|
||||
// is implemented
|
||||
import type {
|
||||
TracingMarkerInstance,
|
||||
Transition,
|
||||
} from './ReactFiberTracingMarkerComponent.new';
|
||||
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates.new';
|
||||
} from './ReactFiberTracingMarkerComponent.old';
|
||||
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates.old';
|
||||
|
||||
// Unwind Circular: moved from ReactFiberHooks.old
|
||||
export type HookType =
|
||||
|
@ -416,8 +414,6 @@ export type Dispatcher = {
|
|||
useId(): string,
|
||||
useCacheRefresh?: () => <T>(?() => T, ?T) => void,
|
||||
useMemoCache?: (size: number) => Array<any>,
|
||||
|
||||
unstable_isNewReconciler?: boolean,
|
||||
};
|
||||
|
||||
export type CacheDispatcher = {
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 {MutableSource, MutableSourceVersion} from 'shared/ReactTypes';
|
||||
import type {FiberRoot} from './ReactInternalTypes';
|
||||
|
||||
import {isPrimaryRenderer} from './ReactFiberHostConfig';
|
||||
|
||||
// Work in progress version numbers only apply to a single render,
|
||||
// and should be reset before starting a new render.
|
||||
// This tracks which mutable sources need to be reset after a render.
|
||||
const workInProgressSources: Array<MutableSource<any>> = [];
|
||||
|
||||
let rendererSigil;
|
||||
if (__DEV__) {
|
||||
// Used to detect multiple renderers using the same mutable source.
|
||||
rendererSigil = {};
|
||||
}
|
||||
|
||||
export function markSourceAsDirty(mutableSource: MutableSource<any>): void {
|
||||
workInProgressSources.push(mutableSource);
|
||||
}
|
||||
|
||||
export function resetWorkInProgressVersions(): void {
|
||||
for (let i = 0; i < workInProgressSources.length; i++) {
|
||||
const mutableSource = workInProgressSources[i];
|
||||
if (isPrimaryRenderer) {
|
||||
mutableSource._workInProgressVersionPrimary = null;
|
||||
} else {
|
||||
mutableSource._workInProgressVersionSecondary = null;
|
||||
}
|
||||
}
|
||||
workInProgressSources.length = 0;
|
||||
}
|
||||
|
||||
export function getWorkInProgressVersion(
|
||||
mutableSource: MutableSource<any>,
|
||||
): null | MutableSourceVersion {
|
||||
if (isPrimaryRenderer) {
|
||||
return mutableSource._workInProgressVersionPrimary;
|
||||
} else {
|
||||
return mutableSource._workInProgressVersionSecondary;
|
||||
}
|
||||
}
|
||||
|
||||
export function setWorkInProgressVersion(
|
||||
mutableSource: MutableSource<any>,
|
||||
version: MutableSourceVersion,
|
||||
): void {
|
||||
if (isPrimaryRenderer) {
|
||||
mutableSource._workInProgressVersionPrimary = version;
|
||||
} else {
|
||||
mutableSource._workInProgressVersionSecondary = version;
|
||||
}
|
||||
workInProgressSources.push(mutableSource);
|
||||
}
|
||||
|
||||
export function warnAboutMultipleRenderersDEV(
|
||||
mutableSource: MutableSource<any>,
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
if (isPrimaryRenderer) {
|
||||
if (mutableSource._currentPrimaryRenderer == null) {
|
||||
mutableSource._currentPrimaryRenderer = rendererSigil;
|
||||
} else if (mutableSource._currentPrimaryRenderer !== rendererSigil) {
|
||||
console.error(
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
'same mutable source. This is currently unsupported.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (mutableSource._currentSecondaryRenderer == null) {
|
||||
mutableSource._currentSecondaryRenderer = rendererSigil;
|
||||
} else if (mutableSource._currentSecondaryRenderer !== rendererSigil) {
|
||||
console.error(
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
'same mutable source. This is currently unsupported.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eager reads the version of a mutable source and stores it on the root.
|
||||
// This ensures that the version used for server rendering matches the one
|
||||
// that is eventually read during hydration.
|
||||
// If they don't match there's a potential tear and a full deopt render is required.
|
||||
export function registerMutableSourceForHydration(
|
||||
root: FiberRoot,
|
||||
mutableSource: MutableSource<any>,
|
||||
): void {
|
||||
const getVersion = mutableSource._getVersion;
|
||||
const version = getVersion(mutableSource._source);
|
||||
|
||||
// TODO Clear this data once all pending hydration work is finished.
|
||||
// Retaining it forever may interfere with GC.
|
||||
if (root.mutableSourceEagerHydrationData == null) {
|
||||
root.mutableSourceEagerHydrationData = [mutableSource, version];
|
||||
} else {
|
||||
root.mutableSourceEagerHydrationData.push(mutableSource, version);
|
||||
}
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 './ReactInternalTypes';
|
||||
|
||||
import {
|
||||
enableProfilerCommitHooks,
|
||||
enableProfilerNestedUpdatePhase,
|
||||
enableProfilerTimer,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {HostRoot, Profiler} from './ReactWorkTags';
|
||||
|
||||
// Intentionally not named imports because Rollup would use dynamic dispatch for
|
||||
// CommonJS interop named imports.
|
||||
import * as Scheduler from 'scheduler';
|
||||
|
||||
const {unstable_now: now} = Scheduler;
|
||||
|
||||
export type ProfilerTimer = {
|
||||
getCommitTime(): number,
|
||||
isCurrentUpdateNested(): boolean,
|
||||
markNestedUpdateScheduled(): void,
|
||||
recordCommitTime(): void,
|
||||
startProfilerTimer(fiber: Fiber): void,
|
||||
stopProfilerTimerIfRunning(fiber: Fiber): void,
|
||||
stopProfilerTimerIfRunningAndRecordDelta(fiber: Fiber): void,
|
||||
syncNestedUpdateFlag(): void,
|
||||
...
|
||||
};
|
||||
|
||||
let commitTime: number = 0;
|
||||
let layoutEffectStartTime: number = -1;
|
||||
let profilerStartTime: number = -1;
|
||||
let passiveEffectStartTime: number = -1;
|
||||
|
||||
/**
|
||||
* Tracks whether the current update was a nested/cascading update (scheduled from a layout effect).
|
||||
*
|
||||
* The overall sequence is:
|
||||
* 1. render
|
||||
* 2. commit (and call `onRender`, `onCommit`)
|
||||
* 3. check for nested updates
|
||||
* 4. flush passive effects (and call `onPostCommit`)
|
||||
*
|
||||
* Nested updates are identified in step 3 above,
|
||||
* but step 4 still applies to the work that was just committed.
|
||||
* We use two flags to track nested updates then:
|
||||
* one tracks whether the upcoming update is a nested update,
|
||||
* and the other tracks whether the current update was a nested update.
|
||||
* The first value gets synced to the second at the start of the render phase.
|
||||
*/
|
||||
let currentUpdateIsNested: boolean = false;
|
||||
let nestedUpdateScheduled: boolean = false;
|
||||
|
||||
function isCurrentUpdateNested(): boolean {
|
||||
return currentUpdateIsNested;
|
||||
}
|
||||
|
||||
function markNestedUpdateScheduled(): void {
|
||||
if (enableProfilerNestedUpdatePhase) {
|
||||
nestedUpdateScheduled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function resetNestedUpdateFlag(): void {
|
||||
if (enableProfilerNestedUpdatePhase) {
|
||||
currentUpdateIsNested = false;
|
||||
nestedUpdateScheduled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function syncNestedUpdateFlag(): void {
|
||||
if (enableProfilerNestedUpdatePhase) {
|
||||
currentUpdateIsNested = nestedUpdateScheduled;
|
||||
nestedUpdateScheduled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getCommitTime(): number {
|
||||
return commitTime;
|
||||
}
|
||||
|
||||
function recordCommitTime(): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
commitTime = now();
|
||||
}
|
||||
|
||||
function startProfilerTimer(fiber: Fiber): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
profilerStartTime = now();
|
||||
|
||||
if (((fiber.actualStartTime: any): number) < 0) {
|
||||
fiber.actualStartTime = now();
|
||||
}
|
||||
}
|
||||
|
||||
function stopProfilerTimerIfRunning(fiber: Fiber): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
profilerStartTime = -1;
|
||||
}
|
||||
|
||||
function stopProfilerTimerIfRunningAndRecordDelta(
|
||||
fiber: Fiber,
|
||||
overrideBaseTime: boolean,
|
||||
): void {
|
||||
if (!enableProfilerTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (profilerStartTime >= 0) {
|
||||
const elapsedTime = now() - profilerStartTime;
|
||||
// $FlowFixMe[unsafe-addition] addition with possible null/undefined value
|
||||
fiber.actualDuration += elapsedTime;
|
||||
if (overrideBaseTime) {
|
||||
fiber.selfBaseDuration = elapsedTime;
|
||||
}
|
||||
profilerStartTime = -1;
|
||||
}
|
||||
}
|
||||
|
||||
function recordLayoutEffectDuration(fiber: Fiber): void {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (layoutEffectStartTime >= 0) {
|
||||
const elapsedTime = now() - layoutEffectStartTime;
|
||||
|
||||
layoutEffectStartTime = -1;
|
||||
|
||||
// Store duration on the next nearest Profiler ancestor
|
||||
// Or the root (for the DevTools Profiler to read)
|
||||
let parentFiber = fiber.return;
|
||||
while (parentFiber !== null) {
|
||||
switch (parentFiber.tag) {
|
||||
case HostRoot:
|
||||
const root = parentFiber.stateNode;
|
||||
root.effectDuration += elapsedTime;
|
||||
return;
|
||||
case Profiler:
|
||||
const parentStateNode = parentFiber.stateNode;
|
||||
parentStateNode.effectDuration += elapsedTime;
|
||||
return;
|
||||
}
|
||||
parentFiber = parentFiber.return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function recordPassiveEffectDuration(fiber: Fiber): void {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (passiveEffectStartTime >= 0) {
|
||||
const elapsedTime = now() - passiveEffectStartTime;
|
||||
|
||||
passiveEffectStartTime = -1;
|
||||
|
||||
// Store duration on the next nearest Profiler ancestor
|
||||
// Or the root (for the DevTools Profiler to read)
|
||||
let parentFiber = fiber.return;
|
||||
while (parentFiber !== null) {
|
||||
switch (parentFiber.tag) {
|
||||
case HostRoot:
|
||||
const root = parentFiber.stateNode;
|
||||
if (root !== null) {
|
||||
root.passiveEffectDuration += elapsedTime;
|
||||
}
|
||||
return;
|
||||
case Profiler:
|
||||
const parentStateNode = parentFiber.stateNode;
|
||||
if (parentStateNode !== null) {
|
||||
// Detached fibers have their state node cleared out.
|
||||
// In this case, the return pointer is also cleared out,
|
||||
// so we won't be able to report the time spent in this Profiler's subtree.
|
||||
parentStateNode.passiveEffectDuration += elapsedTime;
|
||||
}
|
||||
return;
|
||||
}
|
||||
parentFiber = parentFiber.return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startLayoutEffectTimer(): void {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return;
|
||||
}
|
||||
layoutEffectStartTime = now();
|
||||
}
|
||||
|
||||
function startPassiveEffectTimer(): void {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return;
|
||||
}
|
||||
passiveEffectStartTime = now();
|
||||
}
|
||||
|
||||
function transferActualDuration(fiber: Fiber): void {
|
||||
// Transfer time spent rendering these children so we don't lose it
|
||||
// after we rerender. This is used as a helper in special cases
|
||||
// where we should count the work of multiple passes.
|
||||
let child = fiber.child;
|
||||
while (child) {
|
||||
// $FlowFixMe[unsafe-addition] addition with possible null/undefined value
|
||||
fiber.actualDuration += child.actualDuration;
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getCommitTime,
|
||||
isCurrentUpdateNested,
|
||||
markNestedUpdateScheduled,
|
||||
recordCommitTime,
|
||||
recordLayoutEffectDuration,
|
||||
recordPassiveEffectDuration,
|
||||
resetNestedUpdateFlag,
|
||||
startLayoutEffectTimer,
|
||||
startPassiveEffectTimer,
|
||||
startProfilerTimer,
|
||||
stopProfilerTimerIfRunning,
|
||||
stopProfilerTimerIfRunningAndRecordDelta,
|
||||
syncNestedUpdateFlag,
|
||||
transferActualDuration,
|
||||
};
|
|
@ -15,5 +15,5 @@ export {
|
|||
ContinuousEventPriority,
|
||||
DefaultEventPriority,
|
||||
IdleEventPriority,
|
||||
} from './ReactEventPriorities';
|
||||
} from './ReactEventPriorities.old';
|
||||
export {ConcurrentRoot, LegacyRoot} from './ReactRootTags';
|
||||
|
|
|
@ -1,370 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 './ReactInternalTypes';
|
||||
|
||||
import {
|
||||
resetCurrentFiber as resetCurrentDebugFiberInDEV,
|
||||
setCurrentFiber as setCurrentDebugFiberInDEV,
|
||||
} from './ReactCurrentFiber';
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
import {StrictLegacyMode} from './ReactTypeOfMode';
|
||||
|
||||
type FiberArray = Array<Fiber>;
|
||||
type FiberToFiberComponentsMap = Map<Fiber, FiberArray>;
|
||||
|
||||
const ReactStrictModeWarnings = {
|
||||
recordUnsafeLifecycleWarnings: (fiber: Fiber, instance: any): void => {},
|
||||
flushPendingUnsafeLifecycleWarnings: (): void => {},
|
||||
recordLegacyContextWarning: (fiber: Fiber, instance: any): void => {},
|
||||
flushLegacyContextWarning: (): void => {},
|
||||
discardPendingWarnings: (): void => {},
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
const findStrictRoot = (fiber: Fiber): Fiber | null => {
|
||||
let maybeStrictRoot = null;
|
||||
|
||||
let node: null | Fiber = fiber;
|
||||
while (node !== null) {
|
||||
if (node.mode & StrictLegacyMode) {
|
||||
maybeStrictRoot = node;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
|
||||
return maybeStrictRoot;
|
||||
};
|
||||
|
||||
const setToSortedString = set => {
|
||||
const array = [];
|
||||
set.forEach(value => {
|
||||
array.push(value);
|
||||
});
|
||||
return array.sort().join(', ');
|
||||
};
|
||||
|
||||
let pendingComponentWillMountWarnings: Array<Fiber> = [];
|
||||
let pendingUNSAFE_ComponentWillMountWarnings: Array<Fiber> = [];
|
||||
let pendingComponentWillReceivePropsWarnings: Array<Fiber> = [];
|
||||
let pendingUNSAFE_ComponentWillReceivePropsWarnings: Array<Fiber> = [];
|
||||
let pendingComponentWillUpdateWarnings: Array<Fiber> = [];
|
||||
let pendingUNSAFE_ComponentWillUpdateWarnings: Array<Fiber> = [];
|
||||
|
||||
// Tracks components we have already warned about.
|
||||
const didWarnAboutUnsafeLifecycles = new Set();
|
||||
|
||||
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings = (
|
||||
fiber: Fiber,
|
||||
instance: any,
|
||||
) => {
|
||||
// Dedupe strategy: Warn once per component.
|
||||
if (didWarnAboutUnsafeLifecycles.has(fiber.type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof instance.componentWillMount === 'function' &&
|
||||
// Don't warn about react-lifecycles-compat polyfilled components.
|
||||
instance.componentWillMount.__suppressDeprecationWarning !== true
|
||||
) {
|
||||
pendingComponentWillMountWarnings.push(fiber);
|
||||
}
|
||||
|
||||
if (
|
||||
fiber.mode & StrictLegacyMode &&
|
||||
typeof instance.UNSAFE_componentWillMount === 'function'
|
||||
) {
|
||||
pendingUNSAFE_ComponentWillMountWarnings.push(fiber);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof instance.componentWillReceiveProps === 'function' &&
|
||||
instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
|
||||
) {
|
||||
pendingComponentWillReceivePropsWarnings.push(fiber);
|
||||
}
|
||||
|
||||
if (
|
||||
fiber.mode & StrictLegacyMode &&
|
||||
typeof instance.UNSAFE_componentWillReceiveProps === 'function'
|
||||
) {
|
||||
pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof instance.componentWillUpdate === 'function' &&
|
||||
instance.componentWillUpdate.__suppressDeprecationWarning !== true
|
||||
) {
|
||||
pendingComponentWillUpdateWarnings.push(fiber);
|
||||
}
|
||||
|
||||
if (
|
||||
fiber.mode & StrictLegacyMode &&
|
||||
typeof instance.UNSAFE_componentWillUpdate === 'function'
|
||||
) {
|
||||
pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber);
|
||||
}
|
||||
};
|
||||
|
||||
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => {
|
||||
// We do an initial pass to gather component names
|
||||
const componentWillMountUniqueNames = new Set();
|
||||
if (pendingComponentWillMountWarnings.length > 0) {
|
||||
pendingComponentWillMountWarnings.forEach(fiber => {
|
||||
componentWillMountUniqueNames.add(
|
||||
getComponentNameFromFiber(fiber) || 'Component',
|
||||
);
|
||||
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
||||
});
|
||||
pendingComponentWillMountWarnings = [];
|
||||
}
|
||||
|
||||
const UNSAFE_componentWillMountUniqueNames = new Set();
|
||||
if (pendingUNSAFE_ComponentWillMountWarnings.length > 0) {
|
||||
pendingUNSAFE_ComponentWillMountWarnings.forEach(fiber => {
|
||||
UNSAFE_componentWillMountUniqueNames.add(
|
||||
getComponentNameFromFiber(fiber) || 'Component',
|
||||
);
|
||||
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
||||
});
|
||||
pendingUNSAFE_ComponentWillMountWarnings = [];
|
||||
}
|
||||
|
||||
const componentWillReceivePropsUniqueNames = new Set();
|
||||
if (pendingComponentWillReceivePropsWarnings.length > 0) {
|
||||
pendingComponentWillReceivePropsWarnings.forEach(fiber => {
|
||||
componentWillReceivePropsUniqueNames.add(
|
||||
getComponentNameFromFiber(fiber) || 'Component',
|
||||
);
|
||||
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
||||
});
|
||||
|
||||
pendingComponentWillReceivePropsWarnings = [];
|
||||
}
|
||||
|
||||
const UNSAFE_componentWillReceivePropsUniqueNames = new Set();
|
||||
if (pendingUNSAFE_ComponentWillReceivePropsWarnings.length > 0) {
|
||||
pendingUNSAFE_ComponentWillReceivePropsWarnings.forEach(fiber => {
|
||||
UNSAFE_componentWillReceivePropsUniqueNames.add(
|
||||
getComponentNameFromFiber(fiber) || 'Component',
|
||||
);
|
||||
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
||||
});
|
||||
|
||||
pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
|
||||
}
|
||||
|
||||
const componentWillUpdateUniqueNames = new Set();
|
||||
if (pendingComponentWillUpdateWarnings.length > 0) {
|
||||
pendingComponentWillUpdateWarnings.forEach(fiber => {
|
||||
componentWillUpdateUniqueNames.add(
|
||||
getComponentNameFromFiber(fiber) || 'Component',
|
||||
);
|
||||
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
||||
});
|
||||
|
||||
pendingComponentWillUpdateWarnings = [];
|
||||
}
|
||||
|
||||
const UNSAFE_componentWillUpdateUniqueNames = new Set();
|
||||
if (pendingUNSAFE_ComponentWillUpdateWarnings.length > 0) {
|
||||
pendingUNSAFE_ComponentWillUpdateWarnings.forEach(fiber => {
|
||||
UNSAFE_componentWillUpdateUniqueNames.add(
|
||||
getComponentNameFromFiber(fiber) || 'Component',
|
||||
);
|
||||
didWarnAboutUnsafeLifecycles.add(fiber.type);
|
||||
});
|
||||
|
||||
pendingUNSAFE_ComponentWillUpdateWarnings = [];
|
||||
}
|
||||
|
||||
// Finally, we flush all the warnings
|
||||
// UNSAFE_ ones before the deprecated ones, since they'll be 'louder'
|
||||
if (UNSAFE_componentWillMountUniqueNames.size > 0) {
|
||||
const sortedNames = setToSortedString(
|
||||
UNSAFE_componentWillMountUniqueNames,
|
||||
);
|
||||
console.error(
|
||||
'Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' +
|
||||
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
|
||||
'* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
|
||||
'\nPlease update the following components: %s',
|
||||
sortedNames,
|
||||
);
|
||||
}
|
||||
|
||||
if (UNSAFE_componentWillReceivePropsUniqueNames.size > 0) {
|
||||
const sortedNames = setToSortedString(
|
||||
UNSAFE_componentWillReceivePropsUniqueNames,
|
||||
);
|
||||
console.error(
|
||||
'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended ' +
|
||||
'and may indicate bugs in your code. ' +
|
||||
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
|
||||
'* Move data fetching code or side effects to componentDidUpdate.\n' +
|
||||
"* If you're updating state whenever props change, " +
|
||||
'refactor your code to use memoization techniques or move it to ' +
|
||||
'static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state\n' +
|
||||
'\nPlease update the following components: %s',
|
||||
sortedNames,
|
||||
);
|
||||
}
|
||||
|
||||
if (UNSAFE_componentWillUpdateUniqueNames.size > 0) {
|
||||
const sortedNames = setToSortedString(
|
||||
UNSAFE_componentWillUpdateUniqueNames,
|
||||
);
|
||||
console.error(
|
||||
'Using UNSAFE_componentWillUpdate in strict mode is not recommended ' +
|
||||
'and may indicate bugs in your code. ' +
|
||||
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
|
||||
'* Move data fetching code or side effects to componentDidUpdate.\n' +
|
||||
'\nPlease update the following components: %s',
|
||||
sortedNames,
|
||||
);
|
||||
}
|
||||
|
||||
if (componentWillMountUniqueNames.size > 0) {
|
||||
const sortedNames = setToSortedString(componentWillMountUniqueNames);
|
||||
|
||||
console.warn(
|
||||
'componentWillMount has been renamed, and is not recommended for use. ' +
|
||||
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
|
||||
'* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
|
||||
'* Rename componentWillMount to UNSAFE_componentWillMount to suppress ' +
|
||||
'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
|
||||
'To rename all deprecated lifecycles to their new names, you can run ' +
|
||||
'`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
|
||||
'\nPlease update the following components: %s',
|
||||
sortedNames,
|
||||
);
|
||||
}
|
||||
|
||||
if (componentWillReceivePropsUniqueNames.size > 0) {
|
||||
const sortedNames = setToSortedString(
|
||||
componentWillReceivePropsUniqueNames,
|
||||
);
|
||||
|
||||
console.warn(
|
||||
'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
|
||||
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
|
||||
'* Move data fetching code or side effects to componentDidUpdate.\n' +
|
||||
"* If you're updating state whenever props change, refactor your " +
|
||||
'code to use memoization techniques or move it to ' +
|
||||
'static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state\n' +
|
||||
'* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress ' +
|
||||
'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
|
||||
'To rename all deprecated lifecycles to their new names, you can run ' +
|
||||
'`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
|
||||
'\nPlease update the following components: %s',
|
||||
sortedNames,
|
||||
);
|
||||
}
|
||||
|
||||
if (componentWillUpdateUniqueNames.size > 0) {
|
||||
const sortedNames = setToSortedString(componentWillUpdateUniqueNames);
|
||||
|
||||
console.warn(
|
||||
'componentWillUpdate has been renamed, and is not recommended for use. ' +
|
||||
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
|
||||
'* Move data fetching code or side effects to componentDidUpdate.\n' +
|
||||
'* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress ' +
|
||||
'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
|
||||
'To rename all deprecated lifecycles to their new names, you can run ' +
|
||||
'`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
|
||||
'\nPlease update the following components: %s',
|
||||
sortedNames,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map();
|
||||
|
||||
// Tracks components we have already warned about.
|
||||
const didWarnAboutLegacyContext = new Set();
|
||||
|
||||
ReactStrictModeWarnings.recordLegacyContextWarning = (
|
||||
fiber: Fiber,
|
||||
instance: any,
|
||||
) => {
|
||||
const strictRoot = findStrictRoot(fiber);
|
||||
if (strictRoot === null) {
|
||||
console.error(
|
||||
'Expected to find a StrictMode component in a strict mode tree. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Dedup strategy: Warn once per component.
|
||||
if (didWarnAboutLegacyContext.has(fiber.type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let warningsForRoot = pendingLegacyContextWarning.get(strictRoot);
|
||||
|
||||
if (
|
||||
fiber.type.contextTypes != null ||
|
||||
fiber.type.childContextTypes != null ||
|
||||
(instance !== null && typeof instance.getChildContext === 'function')
|
||||
) {
|
||||
if (warningsForRoot === undefined) {
|
||||
warningsForRoot = [];
|
||||
pendingLegacyContextWarning.set(strictRoot, warningsForRoot);
|
||||
}
|
||||
warningsForRoot.push(fiber);
|
||||
}
|
||||
};
|
||||
|
||||
ReactStrictModeWarnings.flushLegacyContextWarning = () => {
|
||||
((pendingLegacyContextWarning: any): FiberToFiberComponentsMap).forEach(
|
||||
(fiberArray: FiberArray, strictRoot) => {
|
||||
if (fiberArray.length === 0) {
|
||||
return;
|
||||
}
|
||||
const firstFiber = fiberArray[0];
|
||||
|
||||
const uniqueNames = new Set();
|
||||
fiberArray.forEach(fiber => {
|
||||
uniqueNames.add(getComponentNameFromFiber(fiber) || 'Component');
|
||||
didWarnAboutLegacyContext.add(fiber.type);
|
||||
});
|
||||
|
||||
const sortedNames = setToSortedString(uniqueNames);
|
||||
|
||||
try {
|
||||
setCurrentDebugFiberInDEV(firstFiber);
|
||||
console.error(
|
||||
'Legacy context API has been detected within a strict-mode tree.' +
|
||||
'\n\nThe old API will be supported in all 16.x releases, but applications ' +
|
||||
'using it should migrate to the new version.' +
|
||||
'\n\nPlease update the following components: %s' +
|
||||
'\n\nLearn more about this warning here: https://reactjs.org/link/legacy-context',
|
||||
sortedNames,
|
||||
);
|
||||
} finally {
|
||||
resetCurrentDebugFiberInDEV();
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
ReactStrictModeWarnings.discardPendingWarnings = () => {
|
||||
pendingComponentWillMountWarnings = [];
|
||||
pendingUNSAFE_ComponentWillMountWarnings = [];
|
||||
pendingComponentWillReceivePropsWarnings = [];
|
||||
pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
|
||||
pendingComponentWillUpdateWarnings = [];
|
||||
pendingUNSAFE_ComponentWillUpdateWarnings = [];
|
||||
pendingLegacyContextWarning = new Map();
|
||||
};
|
||||
}
|
||||
|
||||
export default ReactStrictModeWarnings;
|
|
@ -24,7 +24,7 @@ describe('ReactFiberHostContext', () => {
|
|||
ReactFiberReconciler = require('react-reconciler');
|
||||
ConcurrentRoot = require('react-reconciler/src/ReactRootTags')
|
||||
.ConcurrentRoot;
|
||||
DefaultEventPriority = require('react-reconciler/src/ReactEventPriorities')
|
||||
DefaultEventPriority = require('react-reconciler/src/ReactEventPriorities.old')
|
||||
.DefaultEventPriority;
|
||||
});
|
||||
|
||||
|
|
|
@ -21,10 +21,7 @@ describe('ReactIncrementalErrorReplay-test', () => {
|
|||
|
||||
// This is the method we're going to test.
|
||||
// If this is no longer used, you can delete this test file.;
|
||||
|
||||
const assignFiberPropertiesInDEV = gate(flags => flags.new)
|
||||
? require('../ReactFiber.new').assignFiberPropertiesInDEV
|
||||
: require('../ReactFiber.old').assignFiberPropertiesInDEV;
|
||||
const {assignFiberPropertiesInDEV} = require('../ReactFiber.old');
|
||||
|
||||
// Get a real fiber.
|
||||
const realFiber = ReactTestRenderer.create(<div />).root._currentFiber();
|
||||
|
|
|
@ -84,10 +84,6 @@ describe('updaters', () => {
|
|||
'react-reconciler/src/ReactFiberDevToolsHook.old',
|
||||
() => mockDevToolsHook,
|
||||
);
|
||||
jest.mock(
|
||||
'react-reconciler/src/ReactFiberDevToolsHook.new',
|
||||
() => mockDevToolsHook,
|
||||
);
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
|
|
|
@ -16,7 +16,7 @@ import type {
|
|||
ScheduleRoot,
|
||||
FindHostInstancesForRefresh,
|
||||
SetRefreshHandler,
|
||||
} from 'react-reconciler/src/ReactFiberHotReloading';
|
||||
} from 'react-reconciler/src/ReactFiberHotReloading.old';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
|
||||
import {REACT_MEMO_TYPE, REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import isArray from 'shared/isArray';
|
||||
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
|
||||
|
||||
export type Type = string;
|
||||
export type Props = Object;
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
flushSync,
|
||||
injectIntoDevTools,
|
||||
batchedUpdates,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
} from 'react-reconciler/src/ReactFiberReconciler.old';
|
||||
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||
import {
|
||||
Fragment,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import type {BatchConfigTransition} from 'react-reconciler/src/ReactFiberTracingMarkerComponent.new';
|
||||
import type {BatchConfigTransition} from 'react-reconciler/src/ReactFiberTracingMarkerComponent.old';
|
||||
|
||||
type BatchConfig = {
|
||||
transition: BatchConfigTransition | null,
|
||||
|
|
|
@ -68,10 +68,6 @@ export const enableScopeAPI = false;
|
|||
// Experimental Create Event Handle API.
|
||||
export const enableCreateEventHandleAPI = false;
|
||||
|
||||
// This controls whether you get the `.old` modules or the `.new` modules in
|
||||
// the react-reconciler package.
|
||||
export const enableNewReconciler = false;
|
||||
|
||||
// Support legacy Primer support on internal FB www
|
||||
export const enableLegacyFBSupport = false;
|
||||
|
||||
|
|
|
@ -64,7 +64,6 @@ export const disableNativeComponentFrames = false;
|
|||
export const skipUnmountedBoundaries = false;
|
||||
export const deletedTreeCleanUpLevel = 3;
|
||||
export const enableGetInspectorDataForInstanceInProduction = true;
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = false;
|
||||
|
||||
export const createRootStrictEffectsByDefault = false;
|
||||
|
|
|
@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
|
|||
export const skipUnmountedBoundaries = false;
|
||||
export const deletedTreeCleanUpLevel = 3;
|
||||
export const enableGetInspectorDataForInstanceInProduction = false;
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = false;
|
||||
|
||||
export const createRootStrictEffectsByDefault = false;
|
||||
|
|
|
@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
|
|||
export const skipUnmountedBoundaries = false;
|
||||
export const deletedTreeCleanUpLevel = 3;
|
||||
export const enableGetInspectorDataForInstanceInProduction = false;
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = false;
|
||||
|
||||
export const createRootStrictEffectsByDefault = false;
|
||||
|
|
|
@ -46,7 +46,6 @@ export const disableNativeComponentFrames = false;
|
|||
export const skipUnmountedBoundaries = false;
|
||||
export const deletedTreeCleanUpLevel = 3;
|
||||
export const enableGetInspectorDataForInstanceInProduction = false;
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = false;
|
||||
export const enableSuspenseAvoidThisFallback = false;
|
||||
export const enableSuspenseAvoidThisFallbackFizz = false;
|
||||
|
|
|
@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
|
|||
export const skipUnmountedBoundaries = false;
|
||||
export const deletedTreeCleanUpLevel = 3;
|
||||
export const enableGetInspectorDataForInstanceInProduction = false;
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = false;
|
||||
|
||||
export const createRootStrictEffectsByDefault = false;
|
||||
|
|
|
@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
|
|||
export const skipUnmountedBoundaries = false;
|
||||
export const deletedTreeCleanUpLevel = 3;
|
||||
export const enableGetInspectorDataForInstanceInProduction = false;
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = false;
|
||||
|
||||
export const createRootStrictEffectsByDefault = false;
|
||||
|
|
|
@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
|
|||
export const skipUnmountedBoundaries = true;
|
||||
export const deletedTreeCleanUpLevel = 3;
|
||||
export const enableGetInspectorDataForInstanceInProduction = false;
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = false;
|
||||
|
||||
export const createRootStrictEffectsByDefault = false;
|
||||
|
|
|
@ -95,11 +95,6 @@ export const enableComponentStackLocations = true;
|
|||
|
||||
export const disableTextareaChildren = __EXPERIMENTAL__;
|
||||
|
||||
// Enable forked reconciler. Piggy-backing on the "variant" global so that we
|
||||
// don't have to add another test dimension. The build system will compile this
|
||||
// to the correct value.
|
||||
export const enableNewReconciler = __VARIANT__;
|
||||
|
||||
export const allowConcurrentByDefault = true;
|
||||
|
||||
export const deletedTreeCleanUpLevel = 3;
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const rule = require('../no-cross-fork-imports');
|
||||
const {RuleTester} = require('eslint');
|
||||
const ruleTester = new RuleTester({
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
sourceType: 'module',
|
||||
},
|
||||
});
|
||||
|
||||
ruleTester.run('eslint-rules/no-cross-fork-imports', rule, {
|
||||
valid: [
|
||||
{
|
||||
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop';",
|
||||
filename: 'ReactFiberWorkLoop.js',
|
||||
},
|
||||
{
|
||||
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new';",
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
},
|
||||
{
|
||||
code:
|
||||
"import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new.js';",
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
},
|
||||
{
|
||||
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old';",
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
},
|
||||
{
|
||||
code:
|
||||
"import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old.js';",
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new';",
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'A module that belongs to the old fork cannot import a module ' +
|
||||
'from the new fork.',
|
||||
},
|
||||
],
|
||||
output: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old';",
|
||||
},
|
||||
{
|
||||
code:
|
||||
"import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new.js';",
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'A module that belongs to the old fork cannot import a module ' +
|
||||
'from the new fork.',
|
||||
},
|
||||
],
|
||||
output: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old';",
|
||||
},
|
||||
{
|
||||
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old';",
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'A module that belongs to the new fork cannot import a module ' +
|
||||
'from the old fork.',
|
||||
},
|
||||
],
|
||||
output: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new';",
|
||||
},
|
||||
{
|
||||
code:
|
||||
"import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old.js';",
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'A module that belongs to the new fork cannot import a module ' +
|
||||
'from the old fork.',
|
||||
},
|
||||
],
|
||||
output: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new';",
|
||||
},
|
||||
{
|
||||
code: "export {DiscreteEventPriority} from './ReactFiberLane.old.js';",
|
||||
filename: 'ReactFiberReconciler.new.js',
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'A module that belongs to the new fork cannot import a module ' +
|
||||
'from the old fork.',
|
||||
},
|
||||
],
|
||||
output: "export {DiscreteEventPriority} from './ReactFiberLane.new';",
|
||||
},
|
||||
{
|
||||
code: "export {DiscreteEventPriority} from './ReactFiberLane.new.js';",
|
||||
filename: 'ReactFiberReconciler.old.js',
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'A module that belongs to the old fork cannot import a module ' +
|
||||
'from the new fork.',
|
||||
},
|
||||
],
|
||||
output: "export {DiscreteEventPriority} from './ReactFiberLane.old';",
|
||||
},
|
||||
],
|
||||
});
|
|
@ -1,89 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const rule = require('../no-cross-fork-types');
|
||||
const {RuleTester} = require('eslint');
|
||||
const ruleTester = new RuleTester({
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
sourceType: 'module',
|
||||
},
|
||||
});
|
||||
|
||||
const newAccessWarning =
|
||||
'Field cannot be accessed inside the old reconciler fork, only the ' +
|
||||
'new fork.';
|
||||
|
||||
const oldAccessWarning =
|
||||
'Field cannot be accessed inside the new reconciler fork, only the ' +
|
||||
'old fork.';
|
||||
|
||||
ruleTester.run('eslint-rules/no-cross-fork-types', rule, {
|
||||
valid: [
|
||||
{
|
||||
code: `
|
||||
const a = obj.key_old;
|
||||
const b = obj.key_new;
|
||||
const {key_old, key_new} = obj;
|
||||
`,
|
||||
filename: 'ReactFiberWorkLoop.js',
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const a = obj.key_old;
|
||||
const {key_old} = obj;
|
||||
`,
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const a = obj.key_new;
|
||||
const {key_new} = obj;
|
||||
`,
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: 'const a = obj.key_new;',
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
errors: [{message: newAccessWarning}],
|
||||
},
|
||||
{
|
||||
code: 'const a = obj.key_old;',
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
errors: [{message: oldAccessWarning}],
|
||||
},
|
||||
|
||||
{
|
||||
code: 'const {key_new} = obj;',
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
errors: [{message: newAccessWarning}],
|
||||
},
|
||||
{
|
||||
code: 'const {key_old} = obj;',
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
errors: [{message: oldAccessWarning}],
|
||||
},
|
||||
{
|
||||
code: 'const subtreeFlags = obj.subtreeFlags;',
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
options: [{new: ['subtreeFlags']}],
|
||||
errors: [{message: newAccessWarning}],
|
||||
},
|
||||
{
|
||||
code: 'const firstEffect = obj.firstEffect;',
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
options: [{old: ['firstEffect']}],
|
||||
errors: [{message: oldAccessWarning}],
|
||||
},
|
||||
],
|
||||
});
|
|
@ -7,8 +7,6 @@ module.exports = {
|
|||
'warning-args': require('./warning-args'),
|
||||
'prod-error-codes': require('./prod-error-codes'),
|
||||
'no-production-logging': require('./no-production-logging'),
|
||||
'no-cross-fork-imports': require('./no-cross-fork-imports'),
|
||||
'no-cross-fork-types': require('./no-cross-fork-types'),
|
||||
'safe-string-coercion': require('./safe-string-coercion'),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function isOldFork(filename) {
|
||||
return filename.endsWith('.old.js') || filename.endsWith('.old');
|
||||
}
|
||||
|
||||
function isNewFork(filename) {
|
||||
return filename.endsWith('.new.js') || filename.endsWith('.new');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
fixable: 'code',
|
||||
},
|
||||
create(context) {
|
||||
const sourceFilename = context.getFilename();
|
||||
|
||||
if (isOldFork(sourceFilename)) {
|
||||
const visitor = node => {
|
||||
const sourceNode = node.source;
|
||||
if (sourceNode === null) {
|
||||
return;
|
||||
}
|
||||
const filename = sourceNode.value;
|
||||
if (isNewFork(filename)) {
|
||||
context.report({
|
||||
node: sourceNode,
|
||||
message:
|
||||
'A module that belongs to the old fork cannot import a module ' +
|
||||
'from the new fork.',
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(
|
||||
sourceNode,
|
||||
`'${filename.replace(/\.new(\.js)?$/, '.old')}'`
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
ImportDeclaration: visitor,
|
||||
ExportNamedDeclaration: visitor,
|
||||
};
|
||||
}
|
||||
|
||||
if (isNewFork(sourceFilename)) {
|
||||
const visitor = node => {
|
||||
const sourceNode = node.source;
|
||||
if (sourceNode === null) {
|
||||
return;
|
||||
}
|
||||
const filename = sourceNode.value;
|
||||
if (isOldFork(filename)) {
|
||||
context.report({
|
||||
node: sourceNode,
|
||||
message:
|
||||
'A module that belongs to the new fork cannot import a module ' +
|
||||
'from the old fork.',
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(
|
||||
sourceNode,
|
||||
`'${filename.replace(/\.old(\.js)?$/, '.new')}'`
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
ImportDeclaration: visitor,
|
||||
ExportNamedDeclaration: visitor,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue