@samitouri / QOS-React-2 / commits / 175111de72

Lazily initialize dependencies object (#15944)

Most fibers do not have events or context, so we save memory lazily initializing this container node. Follow-up from #15927

Andrew Clark committed Jun 20, 2019 at 20:12 UTC 175111de723979161b73c2751c3396d22f405dfa
4 files changed +65 -43
packages/react-dom/src/events/DOMEventResponderSystem.js
+14 -12
@@ -709,8 +709,7 @@ function getTargetEventResponderInstances(
709 while (node !== null) {
710 // Traverse up the fiber tree till we find event component fibers.
711 const tag = node.tag;
712 - const events = node.dependencies.events;
713 -
712 + const dependencies = node.dependencies;
713 if (tag === EventComponent) {
714 const eventComponentInstance = node.stateNode;
715 // Switch to the current fiber tree
@@ -721,16 +720,19 @@ function getTargetEventResponderInstances(
720 eventResponderInstances,
721 eventComponentResponders,
722 );
724 - } else if (tag === FunctionComponent && events !== null) {
725 - for (let i = 0; i < events.length; i++) {
726 - const eventComponentInstance = events[i];
727 - if (eventComponentResponders.has(eventComponentInstance.responder)) {
728 - storeTargetEventResponderInstance(
729 - listeningName,
730 - eventComponentInstance,
731 - eventResponderInstances,
732 - null,
733 - );
723 + } else if (tag === FunctionComponent && dependencies !== null) {
724 + const events = dependencies.events;
725 + if (events !== null) {
726 + for (let i = 0; i < events.length; i++) {
727 + const eventComponentInstance = events[i];
728 + if (eventComponentResponders.has(eventComponentInstance.responder)) {
729 + storeTargetEventResponderInstance(
730 + listeningName,
731 + eventComponentInstance,
732 + eventResponderInstances,
733 + null,
734 + );
735 + }
736 }
737 }
738 }
packages/react-reconciler/src/ReactFiber.js
+15 -8
@@ -169,8 +169,8 @@ export type Fiber = {|
169 // The state used to create the output
170 memoizedState: any,
171
172 - // An object of dependencies for this fiber
173 - dependencies: Dependencies,
172 + // Dependencies (contexts, events) for this fiber, if it has any
173 + dependencies: Dependencies | null,
174
175 // Bitfield that describes properties about the fiber and its subtree. E.g.
176 // the ConcurrentMode flag indicates whether the subtree should be async-by-
@@ -270,11 +270,7 @@ function FiberNode(
270 this.memoizedProps = null;
271 this.updateQueue = null;
272 this.memoizedState = null;
273 - this.dependencies = {
274 - expirationTime: 0,
275 - firstContext: null,
276 - events: null,
277 - };
273 + this.dependencies = null;
274
275 this.mode = mode;
276
@@ -443,7 +439,18 @@ export function createWorkInProgress(
439 workInProgress.memoizedProps = current.memoizedProps;
440 workInProgress.memoizedState = current.memoizedState;
441 workInProgress.updateQueue = current.updateQueue;
446 - workInProgress.dependencies = current.dependencies;
442 +
443 + // Clone the dependencies object. This is mutated during the render phase, so
444 + // it cannot be shared with the current fiber.
445 + const currentDependencies = current.dependencies;
446 + workInProgress.dependencies =
447 + currentDependencies === null
448 + ? null
449 + : {
450 + expirationTime: currentDependencies.expirationTime,
451 + firstContext: currentDependencies.firstContext,
452 + events: currentDependencies.events,
453 + };
454
455 // These will be overridden during the parent's reconciliation
456 workInProgress.sibling = current.sibling;
packages/react-reconciler/src/ReactFiberEvents.js
+17 -5
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {Fiber} from './ReactFiber';
10 +import type {Fiber, Dependencies} from './ReactFiber';
11 import type {ReactEventComponentInstance} from 'shared/ReactTypes';
12 import type {EventResponder} from 'react-reconciler/src/ReactFiberHostConfig';
13
@@ -18,6 +18,7 @@ import {
18 SuspenseComponent,
19 Fragment,
20 } from 'shared/ReactWorkTags';
21 +import {NoWork} from './ReactFiberExpirationTime';
22 import invariant from 'shared/invariant';
23
24 let currentlyRenderingFiber: null | Fiber = null;
@@ -37,10 +38,21 @@ export function updateEventComponentInstance(
38 'The "%s" event responder cannot be used via the "useEvent" hook.',
39 responder.displayName,
40 );
40 - const dependencies = ((currentlyRenderingFiber: any): Fiber).dependencies;
41 - let events = dependencies.events;
42 - if (events === null) {
43 - dependencies.events = events = [];
41 + let events;
42 + let dependencies: Dependencies | null = ((currentlyRenderingFiber: any): Fiber)
43 + .dependencies;
44 + if (dependencies === null) {
45 + events = [];
46 + dependencies = ((currentlyRenderingFiber: any): Fiber).dependencies = {
47 + expirationTime: NoWork,
48 + firstContext: null,
49 + events,
50 + };
51 + } else {
52 + events = dependencies.events;
53 + if (events === null) {
54 + dependencies.events = events = [];
55 + }
56 }
57 if (currentEventComponentInstanceIndex === events.length) {
58 let responderState = null;
packages/react-reconciler/src/ReactFiberNewContext.js
+19 -18
@@ -196,12 +196,11 @@ export function propagateContextChange(
196 let nextFiber;
197
198 // Visit this fiber.
199 - const dependencies = fiber.dependencies;
200 - const list = dependencies.firstContext;
199 + const list = fiber.dependencies;
200 if (list !== null) {
201 nextFiber = fiber.child;
202
204 - let dependency = list;
203 + let dependency = list.firstContext;
204 while (dependency !== null) {
205 // Check if the context matches.
206 if (
@@ -235,8 +234,8 @@ export function propagateContextChange(
234 scheduleWorkOnParentPath(fiber.return, renderExpirationTime);
235
236 // Mark the expiration time on the list, too.
238 - if (dependencies.expirationTime < renderExpirationTime) {
239 - dependencies.expirationTime = renderExpirationTime;
237 + if (list.expirationTime < renderExpirationTime) {
238 + list.expirationTime = renderExpirationTime;
239 }
240
241 // Since we already found a match, we can stop traversing the
@@ -312,17 +311,17 @@ export function prepareToReadContext(
311 lastContextWithAllBitsObserved = null;
312
313 const dependencies = workInProgress.dependencies;
315 - const firstContext = dependencies.firstContext;
316 - if (
317 - firstContext !== null &&
318 - dependencies.expirationTime >= renderExpirationTime
319 - ) {
320 - // Context list has a pending update. Mark that this fiber performed work.
321 - markWorkInProgressReceivedUpdate();
314 + if (dependencies !== null) {
315 + const firstContext = dependencies.firstContext;
316 + if (firstContext !== null) {
317 + if (dependencies.expirationTime >= renderExpirationTime) {
318 + // Context list has a pending update. Mark that this fiber performed work.
319 + markWorkInProgressReceivedUpdate();
320 + }
321 + // Reset the work-in-progress list
322 + dependencies.firstContext = null;
323 + }
324 }
323 -
324 - // Reset the work-in-progress list
325 - dependencies.firstContext = null;
325 }
326
327 export function readContext<T>(
@@ -375,9 +374,11 @@ export function readContext<T>(
374
375 // This is the first dependency for this component. Create a new list.
376 lastContextDependency = contextItem;
378 - const dependencies = currentlyRenderingFiber.dependencies;
379 - dependencies.expirationTime = NoWork;
380 - dependencies.firstContext = contextItem;
377 + currentlyRenderingFiber.dependencies = {
378 + expirationTime: NoWork,
379 + firstContext: contextItem,
380 + events: null,
381 + };
382 } else {
383 // Append a new context item.
384 lastContextDependency = lastContextDependency.next = contextItem;