@@ -16,169 +16,191 @@ import type {
1616 SharedQueue as ClassQueue ,
1717 Update as ClassUpdate ,
1818} from './ReactFiberClassUpdateQueue.new' ;
19- import type { Lane } from './ReactFiberLane.new' ;
19+ import type { Lane , Lanes } from './ReactFiberLane.new' ;
2020
2121import { warnAboutUpdateOnNotYetMountedFiberInDEV } from './ReactFiberWorkLoop.new' ;
22- import { mergeLanes } from './ReactFiberLane.new' ;
22+ import { NoLane , NoLanes , mergeLanes } from './ReactFiberLane.new' ;
2323import { NoFlags , Placement , Hydrating } from './ReactFiberFlags' ;
2424import { HostRoot } from './ReactWorkTags' ;
2525
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+ } ;
3329
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;
4340
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 ;
6466 }
67+ queue . pending = update ;
68+ }
69+
70+ if ( lane !== NoLane ) {
71+ lanes = mergeLanes ( lanes , lane ) ;
72+ markUpdateLaneFromFiberToRoot ( fiber , lane ) ;
6573 }
66- concurrentQueues = null ;
6774 }
75+
76+ return lanes ;
6877}
6978
70- export function enqueueConcurrentHookUpdate < S , A > (
79+ function enqueueUpdate (
7180 fiber : Fiber ,
72- queue : HookQueue < S , A > ,
73- update : HookUpdate < S , A > ,
81+ queue : ConcurrentQueue | null ,
82+ update : ConcurrentUpdate | null ,
7483 lane : Lane ,
7584) {
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 ) ;
8699 }
87- queue . interleaved = update ;
100+ }
88101
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 ) ;
90112}
91113
92114export function enqueueConcurrentHookUpdateAndEagerlyBailout < S , A > (
93115 fiber : Fiber ,
94116 queue : HookQueue < S , A > ,
95117 update : HookUpdate < S , A > ,
96- lane : Lane ,
97118) : 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 ) ;
110126}
111127
112128export function enqueueConcurrentClassUpdate < State > (
113129 fiber : Fiber ,
114130 queue : ClassQueue < State > ,
115131 update : ClassUpdate < State > ,
116132 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 ) ;
132138}
133139
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 ) ;
136146}
137147
138148// Calling this function outside this module should only be done for backwards
139149// compatibility and should always be accompanied by a warning.
140- export const unsafe_markUpdateLaneFromFiberToRoot = markUpdateLaneFromFiberToRoot ;
141-
142- function markUpdateLaneFromFiberToRoot (
150+ export function unsafe_markUpdateLaneFromFiberToRoot (
143151 sourceFiber : Fiber ,
144152 lane : Lane ,
145153) : FiberRoot | null {
154+ markUpdateLaneFromFiberToRoot ( sourceFiber , lane ) ;
155+ return getRootForUpdatedFiber ( sourceFiber ) ;
156+ }
157+
158+ function markUpdateLaneFromFiberToRoot ( sourceFiber : Fiber , lane : Lane ) : void {
146159 // Update the source fiber's lanes
147160 sourceFiber . lanes = mergeLanes ( sourceFiber . lanes , lane ) ;
148161 let alternate = sourceFiber . alternate ;
149162 if ( alternate !== null ) {
150163 alternate . lanes = mergeLanes ( alternate . lanes , lane ) ;
151164 }
152- if ( __DEV__ ) {
153- if (
154- alternate === null &&
155- ( sourceFiber . flags & ( Placement | Hydrating ) ) !== NoFlags
156- ) {
157- warnAboutUpdateOnNotYetMountedFiberInDEV ( sourceFiber ) ;
158- }
159- }
160165 // Walk the parent path to the root and update the child lanes.
161- let node = sourceFiber ;
162166 let parent = sourceFiber . return ;
163167 while ( parent !== null ) {
164168 parent . childLanes = mergeLanes ( parent . childLanes , lane ) ;
165169 alternate = parent . alternate ;
166170 if ( alternate !== null ) {
167171 alternate . childLanes = mergeLanes ( alternate . childLanes , lane ) ;
168- } else {
169- if ( __DEV__ ) {
170- if ( ( parent . flags & ( Placement | Hydrating ) ) !== NoFlags ) {
171- warnAboutUpdateOnNotYetMountedFiberInDEV ( sourceFiber ) ;
172- }
173- }
174172 }
175- node = parent ;
176173 parent = parent . return ;
177174 }
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+ }
183205 }
184206}
0 commit comments