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