@samitouri / QOS-React-2 / commits / 2a646f73e4

Convert snapshot phase to depth-first traversal (#20622)

Andrew Clark committed Jan 19, 2021 at 17:37 UTC 2a646f73e4f64000fcd1a483cec1c08614f20e9c
3 files changed +167 -111
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+141 -29
@@ -75,9 +75,10 @@ import {
75 Hydrating,
76 HydratingAndUpdate,
77 Passive,
78 + BeforeMutationMask,
79 MutationMask,
79 - PassiveMask,
80 LayoutMask,
81 + PassiveMask,
82 PassiveUnmountPendingDev,
83 } from './ReactFiberFlags';
84 import getComponentName from 'shared/getComponentName';
@@ -128,6 +129,8 @@ import {
129 commitHydratedSuspenseInstance,
130 clearContainer,
131 prepareScopeUpdate,
132 + prepareForCommit,
133 + beforeActiveInstanceBlur,
134 } from './ReactFiberHostConfig';
135 import {
136 captureCommitPhaseError,
@@ -144,6 +147,7 @@ import {
147 Passive as HookPassive,
148 } from './ReactHookEffectTags';
149 import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
150 +import {doesFiberContain} from './ReactFiberTreeReflection';
151
152 let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
153 if (__DEV__) {
@@ -259,18 +263,114 @@ function safelyCallDestroy(current: Fiber, destroy: () => void) {
263 }
264 }
265
262 -function commitBeforeMutationLifeCycles(
263 - current: Fiber | null,
264 - finishedWork: Fiber,
265 -): void {
266 - switch (finishedWork.tag) {
267 - case FunctionComponent:
268 - case ForwardRef:
269 - case SimpleMemoComponent: {
266 +let focusedInstanceHandle: null | Fiber = null;
267 +let shouldFireAfterActiveInstanceBlur: boolean = false;
268 +
269 +export function commitBeforeMutationEffects(
270 + root: FiberRoot,
271 + firstChild: Fiber,
272 +) {
273 + focusedInstanceHandle = prepareForCommit(root.containerInfo);
274 +
275 + nextEffect = firstChild;
276 + commitBeforeMutationEffects_begin();
277 +
278 + // We no longer need to track the active instance fiber
279 + const shouldFire = shouldFireAfterActiveInstanceBlur;
280 + shouldFireAfterActiveInstanceBlur = false;
281 + focusedInstanceHandle = null;
282 +
283 + return shouldFire;
284 +}
285 +
286 +function commitBeforeMutationEffects_begin() {
287 + while (nextEffect !== null) {
288 + const fiber = nextEffect;
289 +
290 + // TODO: Should wrap this in flags check, too, as optimization
291 + const deletions = fiber.deletions;
292 + if (deletions !== null) {
293 + for (let i = 0; i < deletions.length; i++) {
294 + const deletion = deletions[i];
295 + commitBeforeMutationEffectsDeletion(deletion);
296 + }
297 + }
298 +
299 + const child = fiber.child;
300 + if (
301 + (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
302 + child !== null
303 + ) {
304 + ensureCorrectReturnPointer(child, fiber);
305 + nextEffect = child;
306 + } else {
307 + commitBeforeMutationEffects_complete();
308 + }
309 + }
310 +}
311 +
312 +function commitBeforeMutationEffects_complete() {
313 + while (nextEffect !== null) {
314 + const fiber = nextEffect;
315 + if (__DEV__) {
316 + setCurrentDebugFiberInDEV(fiber);
317 + invokeGuardedCallback(
318 + null,
319 + commitBeforeMutationEffectsOnFiber,
320 + null,
321 + fiber,
322 + );
323 + if (hasCaughtError()) {
324 + const error = clearCaughtError();
325 + captureCommitPhaseError(fiber, error);
326 + }
327 + resetCurrentDebugFiberInDEV();
328 + } else {
329 + try {
330 + commitBeforeMutationEffectsOnFiber(fiber);
331 + } catch (error) {
332 + captureCommitPhaseError(fiber, error);
333 + }
334 + }
335 +
336 + const sibling = fiber.sibling;
337 + if (sibling !== null) {
338 + ensureCorrectReturnPointer(sibling, fiber.return);
339 + nextEffect = sibling;
340 return;
341 }
272 - case ClassComponent: {
273 - if (finishedWork.flags & Snapshot) {
342 +
343 + nextEffect = fiber.return;
344 + }
345 +}
346 +
347 +function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
348 + const current = finishedWork.alternate;
349 + const flags = finishedWork.flags;
350 +
351 + if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
352 + // Check to see if the focused element was inside of a hidden (Suspense) subtree.
353 + // TODO: Move this out of the hot path using a dedicated effect tag.
354 + if (
355 + finishedWork.tag === SuspenseComponent &&
356 + isSuspenseBoundaryBeingHidden(current, finishedWork) &&
357 + doesFiberContain(finishedWork, focusedInstanceHandle)
358 + ) {
359 + shouldFireAfterActiveInstanceBlur = true;
360 + beforeActiveInstanceBlur(finishedWork);
361 + }
362 + }
363 +
364 + if ((flags & Snapshot) !== NoFlags) {
365 + setCurrentDebugFiberInDEV(finishedWork);
366 +
367 + switch (finishedWork.tag) {
368 + case FunctionComponent:
369 + case ForwardRef:
370 + case SimpleMemoComponent: {
371 + break;
372 + }
373 + case ClassComponent: {
374 if (current !== null) {
375 const prevProps = current.memoizedProps;
376 const prevState = current.memoizedState;
@@ -324,30 +424,43 @@ function commitBeforeMutationLifeCycles(
424 }
425 instance.__reactInternalSnapshotBeforeUpdate = snapshot;
426 }
427 + break;
428 }
328 - return;
329 - }
330 - case HostRoot: {
331 - if (supportsMutation) {
332 - if (finishedWork.flags & Snapshot) {
429 + case HostRoot: {
430 + if (supportsMutation) {
431 const root = finishedWork.stateNode;
432 clearContainer(root.containerInfo);
433 }
434 + break;
435 + }
436 + case HostComponent:
437 + case HostText:
438 + case HostPortal:
439 + case IncompleteClassComponent:
440 + // Nothing to do for these component types
441 + break;
442 + default: {
443 + invariant(
444 + false,
445 + 'This unit of work tag should not have side-effects. This error is ' +
446 + 'likely caused by a bug in React. Please file an issue.',
447 + );
448 }
337 - return;
449 }
339 - case HostComponent:
340 - case HostText:
341 - case HostPortal:
342 - case IncompleteClassComponent:
343 - // Nothing to do for these component types
344 - return;
450 +
451 + resetCurrentDebugFiberInDEV();
452 + }
453 +}
454 +
455 +function commitBeforeMutationEffectsDeletion(deletion: Fiber) {
456 + // TODO (effects) It would be nice to avoid calling doesFiberContain()
457 + // Maybe we can repurpose one of the subtreeFlags positions for this instead?
458 + // Use it to store which part of the tree the focused instance is in?
459 + // This assumes we can safely determine that instance during the "render" phase.
460 + if (doesFiberContain(deletion, ((focusedInstanceHandle: any): Fiber))) {
461 + shouldFireAfterActiveInstanceBlur = true;
462 + beforeActiveInstanceBlur(deletion);
463 }
346 - invariant(
347 - false,
348 - 'This unit of work tag should not have side-effects. This error is ' +
349 - 'likely caused by a bug in React. Please file an issue.',
350 - );
464 }
465
466 function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
@@ -2353,7 +2466,6 @@ function ensureCorrectReturnPointer(fiber, expectedReturnFiber) {
2466 }
2467
2468 export {
2356 - commitBeforeMutationLifeCycles,
2469 commitResetTextContent,
2470 commitPlacement,
2471 commitDeletion,
packages/react-reconciler/src/ReactFiberFlags.js
+3
@@ -60,6 +60,9 @@ export const MountPassiveDev = /* */ 0b10000000000000000000;
60 // don't contain effects, by checking subtreeFlags.
61
62 export const BeforeMutationMask =
63 + // TODO: Remove Update flag from before mutation phase by re-landing Visiblity
64 + // flag logic (see #20043)
65 + Update |
66 Snapshot |
67 (enableCreateEventHandleAPI
68 ? // createEventHandle needs to visit deleted and hidden trees to
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+23 -82
@@ -83,13 +83,11 @@ import * as Scheduler from 'scheduler';
83 import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
84
85 import {
86 - prepareForCommit,
86 resetAfterCommit,
87 scheduleTimeout,
88 cancelTimeout,
89 noTimeout,
90 warnsIfNotActing,
92 - beforeActiveInstanceBlur,
91 afterActiveInstanceBlur,
92 clearContainer,
93 } from './ReactFiberHostConfig';
@@ -122,9 +120,7 @@ import {
120 NoFlags,
121 PerformedWork,
122 Placement,
125 - Deletion,
123 ChildDeletion,
127 - Snapshot,
124 Passive,
125 PassiveStatic,
126 Incomplete,
@@ -181,11 +177,10 @@ import {
177 createClassErrorUpdate,
178 } from './ReactFiberThrow.new';
179 import {
184 - commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
180 + commitBeforeMutationEffects,
181 commitLayoutEffects,
182 commitMutationEffects,
183 commitPassiveEffectDurations,
188 - isSuspenseBoundaryBeingHidden,
184 commitPassiveMountEffects,
185 commitPassiveUnmountEffects,
186 detachFiberAfterEffects,
@@ -232,7 +227,6 @@ import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
227
228 // Used by `act`
229 import enqueueTask from 'shared/enqueueTask';
235 -import {doesFiberContain} from './ReactFiberTreeReflection';
230
231 const ceil = Math.ceil;
232
@@ -363,9 +357,6 @@ let currentEventPendingLanes: Lanes = NoLanes;
357 // We warn about state updates for unmounted components differently in this case.
358 let isFlushingPassiveEffects = false;
359
366 -let focusedInstanceHandle: null | Fiber = null;
367 -let shouldFireAfterActiveInstanceBlur: boolean = false;
368 -
360 export function getWorkInProgressRoot(): FiberRoot | null {
361 return workInProgressRoot;
362 }
@@ -1941,6 +1932,24 @@ function commitRootImpl(root, renderPriorityLevel) {
1932 firstEffect = finishedWork.firstEffect;
1933 }
1934
1935 + // If there are pending passive effects, schedule a callback to process them.
1936 + // Do this as early as possible, so it is queued before anything else that
1937 + // might get scheduled in the commit phase. (See #16714.)
1938 + // TODO: Delete all other places that schedule the passive effect callback
1939 + // They're redundant.
1940 + if (
1941 + (finishedWork.subtreeFlags & Passive) !== NoFlags ||
1942 + (finishedWork.flags & Passive) !== NoFlags
1943 + ) {
1944 + if (!rootDoesHavePassiveEffects) {
1945 + rootDoesHavePassiveEffects = true;
1946 + scheduleCallback(NormalSchedulerPriority, () => {
1947 + flushPassiveEffects();
1948 + return null;
1949 + });
1950 + }
1951 + }
1952 +
1953 if (firstEffect !== null) {
1954 let previousLanePriority;
1955 if (decoupleUpdatePriorityFromScheduler) {
@@ -1962,32 +1971,10 @@ function commitRootImpl(root, renderPriorityLevel) {
1971 // The first phase a "before mutation" phase. We use this phase to read the
1972 // state of the host tree right before we mutate it. This is where
1973 // getSnapshotBeforeUpdate is called.
1965 - focusedInstanceHandle = prepareForCommit(root.containerInfo);
1966 - shouldFireAfterActiveInstanceBlur = false;
1967 -
1968 - nextEffect = firstEffect;
1969 - do {
1970 - if (__DEV__) {
1971 - invokeGuardedCallback(null, commitBeforeMutationEffects, null);
1972 - if (hasCaughtError()) {
1973 - invariant(nextEffect !== null, 'Should be working on an effect.');
1974 - const error = clearCaughtError();
1975 - captureCommitPhaseError(nextEffect, error);
1976 - nextEffect = nextEffect.nextEffect;
1977 - }
1978 - } else {
1979 - try {
1980 - commitBeforeMutationEffects();
1981 - } catch (error) {
1982 - invariant(nextEffect !== null, 'Should be working on an effect.');
1983 - captureCommitPhaseError(nextEffect, error);
1984 - nextEffect = nextEffect.nextEffect;
1985 - }
1986 - }
1987 - } while (nextEffect !== null);
1988 -
1989 - // We no longer need to track the active instance fiber
1990 - focusedInstanceHandle = null;
1974 + const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
1975 + root,
1976 + finishedWork,
1977 + );
1978
1979 if (enableProfilerTimer) {
1980 // Mark the current commit time to be shared by all Profilers in this
@@ -2204,52 +2191,6 @@ function commitRootImpl(root, renderPriorityLevel) {
2191 return null;
2192 }
2193
2207 -function commitBeforeMutationEffects() {
2208 - while (nextEffect !== null) {
2209 - const current = nextEffect.alternate;
2210 -
2211 - if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
2212 - if ((nextEffect.flags & Deletion) !== NoFlags) {
2213 - if (doesFiberContain(nextEffect, focusedInstanceHandle)) {
2214 - shouldFireAfterActiveInstanceBlur = true;
2215 - beforeActiveInstanceBlur(nextEffect);
2216 - }
2217 - } else {
2218 - // TODO: Move this out of the hot path using a dedicated effect tag.
2219 - if (
2220 - nextEffect.tag === SuspenseComponent &&
2221 - isSuspenseBoundaryBeingHidden(current, nextEffect) &&
2222 - doesFiberContain(nextEffect, focusedInstanceHandle)
2223 - ) {
2224 - shouldFireAfterActiveInstanceBlur = true;
2225 - beforeActiveInstanceBlur(nextEffect);
2226 - }
2227 - }
2228 - }
2229 -
2230 - const flags = nextEffect.flags;
2231 - if ((flags & Snapshot) !== NoFlags) {
2232 - setCurrentDebugFiberInDEV(nextEffect);
2233 -
2234 - commitBeforeMutationEffectOnFiber(current, nextEffect);
2235 -
2236 - resetCurrentDebugFiberInDEV();
2237 - }
2238 - if ((flags & Passive) !== NoFlags) {
2239 - // If there are passive effects, schedule a callback to flush at
2240 - // the earliest opportunity.
2241 - if (!rootDoesHavePassiveEffects) {
2242 - rootDoesHavePassiveEffects = true;
2243 - scheduleCallback(NormalSchedulerPriority, () => {
2244 - flushPassiveEffects();
2245 - return null;
2246 - });
2247 - }
2248 - }
2249 - nextEffect = nextEffect.nextEffect;
2250 - }
2251 -}
2252 -
2194 export function flushPassiveEffects(): boolean {
2195 // Returns whether passive effects were flushed.
2196 if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {