From 175111de723979161b73c2751c3396d22f405dfa Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 20 Jun 2019 20:12:22 -0700 Subject: [PATCH] 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 --- .../src/events/DOMEventResponderSystem.js | 26 +++++++------ packages/react-reconciler/src/ReactFiber.js | 23 ++++++++---- .../react-reconciler/src/ReactFiberEvents.js | 22 ++++++++--- .../src/ReactFiberNewContext.js | 37 ++++++++++--------- 4 files changed, 65 insertions(+), 43 deletions(-) diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index c6abdb5c1a..a6b8706f49 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -709,8 +709,7 @@ function getTargetEventResponderInstances( while (node !== null) { // Traverse up the fiber tree till we find event component fibers. const tag = node.tag; - const events = node.dependencies.events; - + const dependencies = node.dependencies; if (tag === EventComponent) { const eventComponentInstance = node.stateNode; // Switch to the current fiber tree @@ -721,16 +720,19 @@ function getTargetEventResponderInstances( eventResponderInstances, eventComponentResponders, ); - } else if (tag === FunctionComponent && events !== null) { - for (let i = 0; i < events.length; i++) { - const eventComponentInstance = events[i]; - if (eventComponentResponders.has(eventComponentInstance.responder)) { - storeTargetEventResponderInstance( - listeningName, - eventComponentInstance, - eventResponderInstances, - null, - ); + } else if (tag === FunctionComponent && dependencies !== null) { + const events = dependencies.events; + if (events !== null) { + for (let i = 0; i < events.length; i++) { + const eventComponentInstance = events[i]; + if (eventComponentResponders.has(eventComponentInstance.responder)) { + storeTargetEventResponderInstance( + listeningName, + eventComponentInstance, + eventResponderInstances, + null, + ); + } } } } diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 116f4c4d72..12ff5ee55e 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -169,8 +169,8 @@ export type Fiber = {| // The state used to create the output memoizedState: any, - // An object of dependencies for this fiber - dependencies: Dependencies, + // Dependencies (contexts, events) for this fiber, if it has any + dependencies: Dependencies | null, // Bitfield that describes properties about the fiber and its subtree. E.g. // the ConcurrentMode flag indicates whether the subtree should be async-by- @@ -270,11 +270,7 @@ function FiberNode( this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; - this.dependencies = { - expirationTime: 0, - firstContext: null, - events: null, - }; + this.dependencies = null; this.mode = mode; @@ -443,7 +439,18 @@ export function createWorkInProgress( workInProgress.memoizedProps = current.memoizedProps; workInProgress.memoizedState = current.memoizedState; workInProgress.updateQueue = current.updateQueue; - workInProgress.dependencies = current.dependencies; + + // 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 + : { + expirationTime: currentDependencies.expirationTime, + firstContext: currentDependencies.firstContext, + events: currentDependencies.events, + }; // These will be overridden during the parent's reconciliation workInProgress.sibling = current.sibling; diff --git a/packages/react-reconciler/src/ReactFiberEvents.js b/packages/react-reconciler/src/ReactFiberEvents.js index ff4f78a914..2c9a9bdef9 100644 --- a/packages/react-reconciler/src/ReactFiberEvents.js +++ b/packages/react-reconciler/src/ReactFiberEvents.js @@ -7,7 +7,7 @@ * @flow */ -import type {Fiber} from './ReactFiber'; +import type {Fiber, Dependencies} from './ReactFiber'; import type {ReactEventComponentInstance} from 'shared/ReactTypes'; import type {EventResponder} from 'react-reconciler/src/ReactFiberHostConfig'; @@ -18,6 +18,7 @@ import { SuspenseComponent, Fragment, } from 'shared/ReactWorkTags'; +import {NoWork} from './ReactFiberExpirationTime'; import invariant from 'shared/invariant'; let currentlyRenderingFiber: null | Fiber = null; @@ -37,10 +38,21 @@ export function updateEventComponentInstance( 'The "%s" event responder cannot be used via the "useEvent" hook.', responder.displayName, ); - const dependencies = ((currentlyRenderingFiber: any): Fiber).dependencies; - let events = dependencies.events; - if (events === null) { - dependencies.events = events = []; + let events; + let dependencies: Dependencies | null = ((currentlyRenderingFiber: any): Fiber) + .dependencies; + if (dependencies === null) { + events = []; + dependencies = ((currentlyRenderingFiber: any): Fiber).dependencies = { + expirationTime: NoWork, + firstContext: null, + events, + }; + } else { + events = dependencies.events; + if (events === null) { + dependencies.events = events = []; + } } if (currentEventComponentInstanceIndex === events.length) { let responderState = null; diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index 6837235751..a77377696f 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -196,12 +196,11 @@ export function propagateContextChange( let nextFiber; // Visit this fiber. - const dependencies = fiber.dependencies; - const list = dependencies.firstContext; + const list = fiber.dependencies; if (list !== null) { nextFiber = fiber.child; - let dependency = list; + let dependency = list.firstContext; while (dependency !== null) { // Check if the context matches. if ( @@ -235,8 +234,8 @@ export function propagateContextChange( scheduleWorkOnParentPath(fiber.return, renderExpirationTime); // Mark the expiration time on the list, too. - if (dependencies.expirationTime < renderExpirationTime) { - dependencies.expirationTime = renderExpirationTime; + if (list.expirationTime < renderExpirationTime) { + list.expirationTime = renderExpirationTime; } // Since we already found a match, we can stop traversing the @@ -312,17 +311,17 @@ export function prepareToReadContext( lastContextWithAllBitsObserved = null; const dependencies = workInProgress.dependencies; - const firstContext = dependencies.firstContext; - if ( - firstContext !== null && - dependencies.expirationTime >= renderExpirationTime - ) { - // Context list has a pending update. Mark that this fiber performed work. - markWorkInProgressReceivedUpdate(); + if (dependencies !== null) { + const firstContext = dependencies.firstContext; + if (firstContext !== null) { + if (dependencies.expirationTime >= renderExpirationTime) { + // Context list has a pending update. Mark that this fiber performed work. + markWorkInProgressReceivedUpdate(); + } + // Reset the work-in-progress list + dependencies.firstContext = null; + } } - - // Reset the work-in-progress list - dependencies.firstContext = null; } export function readContext( @@ -375,9 +374,11 @@ export function readContext( // This is the first dependency for this component. Create a new list. lastContextDependency = contextItem; - const dependencies = currentlyRenderingFiber.dependencies; - dependencies.expirationTime = NoWork; - dependencies.firstContext = contextItem; + currentlyRenderingFiber.dependencies = { + expirationTime: NoWork, + firstContext: contextItem, + events: null, + }; } else { // Append a new context item. lastContextDependency = lastContextDependency.next = contextItem;