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;