@@ -16,169 +16,208 @@ import type {
16
16
SharedQueue as ClassQueue ,
17
17
Update as ClassUpdate ,
18
18
} from './ReactFiberClassUpdateQueue.new' ;
19
- import type { Lane } from './ReactFiberLane.new' ;
19
+ import type { Lane , Lanes } from './ReactFiberLane.new' ;
20
20
21
21
import { warnAboutUpdateOnNotYetMountedFiberInDEV } from './ReactFiberWorkLoop.new' ;
22
- import { mergeLanes } from './ReactFiberLane.new' ;
22
+ import {
23
+ NoLane ,
24
+ NoLanes ,
25
+ mergeLanes ,
26
+ isSubsetOfLanes ,
27
+ } from './ReactFiberLane.new' ;
23
28
import { NoFlags , Placement , Hydrating } from './ReactFiberFlags' ;
24
29
import { HostRoot } from './ReactWorkTags' ;
25
30
26
- // An array of all update queues that received updates during the current
27
- // render. When this render exits, either because it finishes or because it is
28
- // interrupted, the interleaved updates will be transferred onto the main part
29
- // of the queue.
30
- let concurrentQueues : Array <
31
- HookQueue < any , any > | ClassQueue < any > ,
32
- > | null = null ;
31
+ type ConcurrentUpdate = {
32
+ next : ConcurrentUpdate ,
33
+ } ;
33
34
34
- export function pushConcurrentUpdateQueue (
35
- queue : HookQueue < any , any > | ClassQueue < any > ,
36
- ) {
37
- if ( concurrentQueues === null ) {
38
- concurrentQueues = [ queue ] ;
39
- } else {
40
- concurrentQueues . push ( queue ) ;
41
- }
42
- }
35
+ type ConcurrentQueue = {
36
+ pending : ConcurrentUpdate | null ,
37
+ } ;
38
+
39
+ // If a render is in progress, and we receive an update from a concurrent event,
40
+ // we wait until the current render is over (either finished or interrupted)
41
+ // before adding it to the fiber/hook queue. Push to this array so we can
42
+ // access the queue, fiber, update, et al later.
43
+ const concurrentQueues : Array < any > = [];
44
+ let concurrentQueuesIndex = 0;
43
45
44
- export function finishQueueingConcurrentUpdates ( ) {
45
- // Transfer the interleaved updates onto the main queue. Each queue has a
46
- // `pending` field and an `interleaved` field. When they are not null, they
47
- // point to the last node in a circular linked list. We need to append the
48
- // interleaved list to the end of the pending list by joining them into a
49
- // single, circular list.
50
- if ( concurrentQueues !== null ) {
51
- for ( let i = 0 ; i < concurrentQueues . length ; i ++ ) {
52
- const queue = concurrentQueues [ i ] ;
53
- const lastInterleavedUpdate = queue . interleaved ;
54
- if ( lastInterleavedUpdate !== null ) {
55
- queue . interleaved = null ;
56
- const firstInterleavedUpdate = lastInterleavedUpdate . next ;
57
- const lastPendingUpdate = queue . pending ;
58
- if ( lastPendingUpdate !== null ) {
59
- const firstPendingUpdate = lastPendingUpdate . next ;
60
- lastPendingUpdate . next = ( firstInterleavedUpdate : any ) ;
61
- lastInterleavedUpdate . next = ( firstPendingUpdate : any ) ;
62
- }
63
- queue . pending = ( lastInterleavedUpdate : any ) ;
46
+ export function finishQueueingConcurrentUpdates(): Lanes {
47
+ const endIndex = concurrentQueuesIndex ;
48
+ concurrentQueuesIndex = 0 ;
49
+
50
+ let lanes = NoLanes ;
51
+
52
+ let i = 0 ;
53
+ while ( i < endIndex ) {
54
+ const fiber : Fiber = concurrentQueues [ i ] ;
55
+ concurrentQueues [ i ++ ] = null ;
56
+ const queue : ConcurrentQueue = concurrentQueues [ i ] ;
57
+ concurrentQueues [ i ++ ] = null ;
58
+ const update : ConcurrentUpdate = concurrentQueues [ i ] ;
59
+ concurrentQueues [ i ++ ] = null ;
60
+ const lane : Lane = concurrentQueues [ i ] ;
61
+ concurrentQueues [ i ++ ] = null ;
62
+
63
+ if ( queue !== null && update !== null ) {
64
+ const pending = queue . pending ;
65
+ if ( pending === null ) {
66
+ // This is the first update. Create a circular list.
67
+ update . next = update ;
68
+ } else {
69
+ update . next = pending . next ;
70
+ pending . next = update ;
64
71
}
72
+ queue . pending = update ;
73
+ }
74
+
75
+ if ( lane !== NoLane ) {
76
+ lanes = mergeLanes ( lanes , lane ) ;
77
+ markUpdateLaneFromFiberToRoot ( fiber , lane ) ;
65
78
}
66
- concurrentQueues = null ;
67
79
}
80
+
81
+ return lanes ;
68
82
}
69
83
70
- export function enqueueConcurrentHookUpdate < S , A > (
84
+ function enqueueUpdate (
71
85
fiber : Fiber ,
72
- queue : HookQueue < S , A > ,
73
- update : HookUpdate < S , A > ,
86
+ queue : ConcurrentQueue | null ,
87
+ update : ConcurrentUpdate | null ,
74
88
lane : Lane ,
75
89
) {
76
- const interleaved = queue . interleaved ;
77
- if ( interleaved === null ) {
78
- // This is the first update. Create a circular list.
79
- update . next = update ;
80
- // At the end of the current render, this queue's interleaved updates will
81
- // be transferred to the pending queue.
82
- pushConcurrentUpdateQueue ( queue ) ;
83
- } else {
84
- update . next = interleaved . next ;
85
- interleaved . next = update ;
90
+ // Don't update the `childLanes` on the return path yet. If we already in
91
+ // the middle of rendering, wait until after it has completed.
92
+ concurrentQueues [ concurrentQueuesIndex ++ ] = fiber ;
93
+ concurrentQueues [ concurrentQueuesIndex ++ ] = queue ;
94
+ concurrentQueues [ concurrentQueuesIndex ++ ] = update ;
95
+ concurrentQueues [ concurrentQueuesIndex ++ ] = lane ;
96
+
97
+ // The fiber's `lane` field is used in some places to check if any work is
98
+ // scheduled, to perform an eager bailout, so we need to update it immediately.
99
+ // TODO: We should probably move this to the "shared" queue instead.
100
+ fiber . lanes = mergeLanes ( fiber . lanes , lane ) ;
101
+ const alternate = fiber . alternate ;
102
+ if ( alternate !== null ) {
103
+ alternate . lanes = mergeLanes ( alternate . lanes , lane ) ;
86
104
}
87
- queue . interleaved = update ;
105
+ }
88
106
89
- return markUpdateLaneFromFiberToRoot ( fiber , lane ) ;
107
+ export function enqueueConcurrentHookUpdate < S , A > (
108
+ fiber : Fiber ,
109
+ queue : HookQueue < S , A > ,
110
+ update : HookUpdate < S , A > ,
111
+ lane : Lane ,
112
+ ) : FiberRoot | null {
113
+ const concurrentQueue : ConcurrentQueue = ( queue : any ) ;
114
+ const concurrentUpdate : ConcurrentUpdate = ( update : any ) ;
115
+ enqueueUpdate ( fiber , concurrentQueue , concurrentUpdate , lane ) ;
116
+ return getRootForUpdatedFiber ( fiber ) ;
90
117
}
91
118
92
119
export function enqueueConcurrentHookUpdateAndEagerlyBailout < S , A > (
93
120
fiber : Fiber ,
94
121
queue : HookQueue < S , A > ,
95
122
update : HookUpdate < S , A > ,
96
- lane : Lane ,
97
123
) : void {
98
- const interleaved = queue . interleaved ;
99
- if ( interleaved === null ) {
100
- // This is the first update. Create a circular list.
101
- update . next = update ;
102
- // At the end of the current render, this queue's interleaved updates will
103
- // be transferred to the pending queue.
104
- pushConcurrentUpdateQueue ( queue ) ;
105
- } else {
106
- update . next = interleaved . next ;
107
- interleaved . next = update ;
108
- }
109
- queue . interleaved = update ;
124
+ // This function is used to queue an update that doesn't need a rerender. The
125
+ // only reason we queue it is in case there's a subsequent higher priority
126
+ // update that causes it to be rebased.
127
+ const lane = NoLane ;
128
+ const concurrentQueue : ConcurrentQueue = ( queue : any ) ;
129
+ const concurrentUpdate : ConcurrentUpdate = ( update : any ) ;
130
+ enqueueUpdate ( fiber , concurrentQueue , concurrentUpdate , lane ) ;
110
131
}
111
132
112
133
export function enqueueConcurrentClassUpdate < State > (
113
134
fiber : Fiber ,
114
135
queue : ClassQueue < State > ,
115
136
update : ClassUpdate < State > ,
116
137
lane : Lane ,
117
- ) {
118
- const interleaved = queue . interleaved ;
119
- if ( interleaved === null ) {
120
- // This is the first update. Create a circular list.
121
- update . next = update ;
122
- // At the end of the current render, this queue's interleaved updates will
123
- // be transferred to the pending queue.
124
- pushConcurrentUpdateQueue ( queue ) ;
125
- } else {
126
- update . next = interleaved . next ;
127
- interleaved . next = update ;
128
- }
129
- queue . interleaved = update ;
130
-
131
- return markUpdateLaneFromFiberToRoot ( fiber , lane ) ;
138
+ ) : FiberRoot | null {
139
+ const concurrentQueue : ConcurrentQueue = ( queue : any ) ;
140
+ const concurrentUpdate : ConcurrentUpdate = ( update : any ) ;
141
+ enqueueUpdate ( fiber , concurrentQueue , concurrentUpdate , lane ) ;
142
+ return getRootForUpdatedFiber ( fiber ) ;
132
143
}
133
144
134
- export function enqueueConcurrentRenderForLane ( fiber : Fiber , lane : Lane ) {
135
- return markUpdateLaneFromFiberToRoot ( fiber , lane ) ;
145
+ export function enqueueConcurrentRenderForLane (
146
+ fiber : Fiber ,
147
+ lane : Lane ,
148
+ ) : FiberRoot | null {
149
+ enqueueUpdate ( fiber , null , null , lane ) ;
150
+ return getRootForUpdatedFiber ( fiber ) ;
136
151
}
137
152
138
153
// Calling this function outside this module should only be done for backwards
139
154
// compatibility and should always be accompanied by a warning.
140
- export const unsafe_markUpdateLaneFromFiberToRoot = markUpdateLaneFromFiberToRoot ;
141
-
142
- function markUpdateLaneFromFiberToRoot (
155
+ export function unsafe_markUpdateLaneFromFiberToRoot (
143
156
sourceFiber : Fiber ,
144
157
lane : Lane ,
145
158
) : FiberRoot | null {
159
+ markUpdateLaneFromFiberToRoot ( sourceFiber , lane ) ;
160
+ return getRootForUpdatedFiber ( sourceFiber ) ;
161
+ }
162
+
163
+ function markUpdateLaneFromFiberToRoot ( sourceFiber : Fiber , lane : Lane ) : void {
146
164
// Update the source fiber's lanes
147
165
sourceFiber . lanes = mergeLanes ( sourceFiber . lanes , lane ) ;
148
166
let alternate = sourceFiber . alternate ;
149
167
if ( alternate !== null ) {
150
168
alternate . lanes = mergeLanes ( alternate . lanes , lane ) ;
151
169
}
152
- if ( __DEV__ ) {
153
- if (
154
- alternate === null &&
155
- ( sourceFiber . flags & ( Placement | Hydrating ) ) !== NoFlags
156
- ) {
157
- warnAboutUpdateOnNotYetMountedFiberInDEV ( sourceFiber ) ;
158
- }
159
- }
160
170
// Walk the parent path to the root and update the child lanes.
161
- let node = sourceFiber ;
162
171
let parent = sourceFiber . return ;
163
172
while ( parent !== null ) {
164
- parent . childLanes = mergeLanes ( parent . childLanes , lane ) ;
165
173
alternate = parent . alternate ;
166
- if ( alternate !== null ) {
167
- alternate . childLanes = mergeLanes ( alternate . childLanes , lane ) ;
174
+ if ( isSubsetOfLanes ( parent . childLanes , lane ) ) {
175
+ if ( alternate === null || isSubsetOfLanes ( alternate . childLanes , lane ) ) {
176
+ // Both fibers already have sufficient priority. Don't need to update
177
+ // the rest of the return path. This is a helpful optimization in the
178
+ // case where the same component is updated many times in rapid
179
+ // succession, or even in the same event.
180
+ return ;
181
+ } else {
182
+ alternate . childLanes = mergeLanes ( alternate . childLanes , lane ) ;
183
+ }
168
184
} else {
169
- if ( __DEV__ ) {
170
- if ( ( parent . flags & ( Placement | Hydrating ) ) !== NoFlags ) {
171
- warnAboutUpdateOnNotYetMountedFiberInDEV ( sourceFiber ) ;
172
- }
185
+ parent . childLanes = mergeLanes ( parent . childLanes , lane ) ;
186
+ if ( alternate !== null ) {
187
+ alternate . childLanes = mergeLanes ( alternate . childLanes , lane ) ;
173
188
}
174
189
}
175
- node = parent ;
176
190
parent = parent . return ;
177
191
}
178
- if ( node . tag === HostRoot ) {
179
- const root : FiberRoot = node . stateNode ;
180
- return root ;
181
- } else {
182
- return null ;
192
+ }
193
+
194
+ function getRootForUpdatedFiber ( sourceFiber : Fiber ) : FiberRoot | null {
195
+ // When a setState happens, we must ensure the root is scheduled. Because
196
+ // update queues do not have a backpointer to the root, the only way to do
197
+ // this currently is to walk up the return path. This used to not be a big
198
+ // deal because we would have to walk up the return path to set
199
+ // the `childLanes`, anyway, but now those two traversals happen at
200
+ // different times.
201
+ // TODO: Consider adding a `root` backpointer on the update queue.
202
+ detectUpdateOnUnmountedFiber ( sourceFiber , sourceFiber ) ;
203
+ let node = sourceFiber ;
204
+ let parent = node . return ;
205
+ while ( parent !== null ) {
206
+ detectUpdateOnUnmountedFiber ( sourceFiber , node ) ;
207
+ node = parent ;
208
+ parent = node . return ;
209
+ }
210
+ return node . tag === HostRoot ? ( node . stateNode : FiberRoot ) : null ;
211
+ }
212
+
213
+ function detectUpdateOnUnmountedFiber ( sourceFiber : Fiber , parent : Fiber ) {
214
+ if ( __DEV__ ) {
215
+ const alternate = parent . alternate ;
216
+ if (
217
+ alternate === null &&
218
+ ( parent . flags & ( Placement | Hydrating ) ) !== NoFlags
219
+ ) {
220
+ warnAboutUpdateOnNotYetMountedFiberInDEV ( sourceFiber ) ;
221
+ }
183
222
}
184
223
}
0 commit comments