@@ -16,169 +16,208 @@ 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 {
23+ NoLane ,
24+ NoLanes ,
25+ mergeLanes ,
26+ isSubsetOfLanes ,
27+ } from './ReactFiberLane.new' ;
2328import { NoFlags , Placement , Hydrating } from './ReactFiberFlags' ;
2429import { HostRoot } from './ReactWorkTags' ;
2530
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+ } ;
3334
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;
4345
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 ;
6471 }
72+ queue . pending = update ;
73+ }
74+
75+ if ( lane !== NoLane ) {
76+ lanes = mergeLanes ( lanes , lane ) ;
77+ markUpdateLaneFromFiberToRoot ( fiber , lane ) ;
6578 }
66- concurrentQueues = null ;
6779 }
80+
81+ return lanes ;
6882}
6983
70- export function enqueueConcurrentHookUpdate < S , A > (
84+ function enqueueUpdate (
7185 fiber : Fiber ,
72- queue : HookQueue < S , A > ,
73- update : HookUpdate < S , A > ,
86+ queue : ConcurrentQueue | null ,
87+ update : ConcurrentUpdate | null ,
7488 lane : Lane ,
7589) {
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 ) ;
86104 }
87- queue . interleaved = update ;
105+ }
88106
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 ) ;
90117}
91118
92119export function enqueueConcurrentHookUpdateAndEagerlyBailout < S , A > (
93120 fiber : Fiber ,
94121 queue : HookQueue < S , A > ,
95122 update : HookUpdate < S , A > ,
96- lane : Lane ,
97123) : 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 ) ;
110131}
111132
112133export function enqueueConcurrentClassUpdate < State > (
113134 fiber : Fiber ,
114135 queue : ClassQueue < State > ,
115136 update : ClassUpdate < State > ,
116137 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 ) ;
132143}
133144
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 ) ;
136151}
137152
138153// Calling this function outside this module should only be done for backwards
139154// compatibility and should always be accompanied by a warning.
140- export const unsafe_markUpdateLaneFromFiberToRoot = markUpdateLaneFromFiberToRoot ;
141-
142- function markUpdateLaneFromFiberToRoot (
155+ export function unsafe_markUpdateLaneFromFiberToRoot (
143156 sourceFiber : Fiber ,
144157 lane : Lane ,
145158) : FiberRoot | null {
159+ markUpdateLaneFromFiberToRoot ( sourceFiber , lane ) ;
160+ return getRootForUpdatedFiber ( sourceFiber ) ;
161+ }
162+
163+ function markUpdateLaneFromFiberToRoot ( sourceFiber : Fiber , lane : Lane ) : void {
146164 // Update the source fiber's lanes
147165 sourceFiber . lanes = mergeLanes ( sourceFiber . lanes , lane ) ;
148166 let alternate = sourceFiber . alternate ;
149167 if ( alternate !== null ) {
150168 alternate . lanes = mergeLanes ( alternate . lanes , lane ) ;
151169 }
152- if ( __DEV__ ) {
153- if (
154- alternate === null &&
155- ( sourceFiber . flags & ( Placement | Hydrating ) ) !== NoFlags
156- ) {
157- warnAboutUpdateOnNotYetMountedFiberInDEV ( sourceFiber ) ;
158- }
159- }
160170 // Walk the parent path to the root and update the child lanes.
161- let node = sourceFiber ;
162171 let parent = sourceFiber . return ;
163172 while ( parent !== null ) {
164- parent . childLanes = mergeLanes ( parent . childLanes , lane ) ;
165173 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+ }
168184 } 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 ) ;
173188 }
174189 }
175- node = parent ;
176190 parent = parent . return ;
177191 }
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+ }
183222 }
184223}
0 commit comments