@samitouri / QOS-React-2 / commits / d7f89b8681

Don't rely on commit phase effect to clear updates

Instead clear updates on the work-in-progress during the begin phase. Aborted updates are recovered by cloning from the current fiber.

Andrew Clark committed Dec 12, 2016 at 23:53 UTC d7f89b86818c267083e9841a830031438e0d0185
9 files changed +217 -240
examples/fiber/index.html
+1 -3
@@ -115,14 +115,12 @@
115 SierpinskiTriangle.shouldComponentUpdate = function(oldProps, newProps) {
116 var o = oldProps;
117 var n = newProps;
118 - const res = !(
118 + return !(
119 o.x === n.x &&
120 o.y === n.y &&
121 o.s === n.s &&
122 o.children === n.children
123 );
124 - // console.log('shouldComponentUpdate', res);
125 - return res;
124 };
125
126 class ExampleApplication extends React.Component {
src/renderers/noop/ReactNoop.js
+5 -10
@@ -286,23 +286,18 @@ var ReactNoop = {
286 log(
287 ' '.repeat(depth + 1) + 'QUEUED UPDATES'
288 );
289 - let firstPendingUpdate;
290 - if (updateQueue.lastProgressedUpdate) {
291 - firstPendingUpdate = updateQueue.lastProgressedUpdate.next;
292 - } else {
293 - firstPendingUpdate = updateQueue.first;
294 - }
295 - if (!firstPendingUpdate) {
289 + const firstUpdate = updateQueue.first;
290 + if (!firstUpdate) {
291 return;
292 }
293
294 log(
295 ' '.repeat(depth + 1) + '~',
301 - firstPendingUpdate && firstPendingUpdate.partialState,
302 - firstPendingUpdate.callback ? 'with callback' : ''
296 + firstUpdate && firstUpdate.partialState,
297 + firstUpdate.callback ? 'with callback' : ''
298 );
299 var next;
305 - while (next = firstPendingUpdate.next) {
300 + while (next = firstUpdate.next) {
301 log(
302 ' '.repeat(depth + 1) + '~',
303 next.partialState,
src/renderers/shared/fiber/ReactFiber.js
+8 -1
@@ -43,6 +43,10 @@ var {
43 NoEffect,
44 } = require('ReactTypeOfSideEffect');
45
46 +var {
47 + cloneUpdateQueue,
48 +} = require('ReactFiberUpdateQueue');
49 +
50 var invariant = require('invariant');
51
52 // A Fiber is work on a Component that needs to be done or was done. There can
@@ -103,6 +107,8 @@ export type Fiber = {
107
108 // A queue of state updates and callbacks.
109 updateQueue: UpdateQueue | null,
110 + // A list of callbacks that should be called during the next commit.
111 + callbackList: UpdateQueue | null,
112 // The state used to create the output
113 memoizedState: any,
114
@@ -193,6 +199,7 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
199 pendingProps: null,
200 memoizedProps: null,
201 updateQueue: null,
202 + callbackList: null,
203 memoizedState: null,
204
205 effectTag: NoEffect,
@@ -268,7 +275,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
275 // pendingProps is here for symmetry but is unnecessary in practice for now.
276 // TODO: Pass in the new pendingProps as an argument maybe?
277 alt.pendingProps = fiber.pendingProps;
271 - alt.updateQueue = fiber.updateQueue;
278 + cloneUpdateQueue(alt, fiber);
279 alt.pendingWorkPriority = priorityLevel;
280
281 alt.memoizedProps = fiber.memoizedProps;
src/renderers/shared/fiber/ReactFiberClassComponent.js
+4 -9
@@ -19,7 +19,6 @@ var {
19 getMaskedContext,
20 } = require('ReactFiberContext');
21 var {
22 - ensureUpdateQueue,
22 addUpdate,
23 addReplaceUpdate,
24 addForceUpdate,
@@ -46,30 +45,26 @@ module.exports = function(
45 isMounted,
46 enqueueSetState(instance, partialState) {
47 const fiber = ReactInstanceMap.get(instance);
49 - const queue = ensureUpdateQueue(fiber);
48 const priorityLevel = getPriorityContext();
51 - addUpdate(queue, partialState, priorityLevel);
49 + addUpdate(fiber, partialState, priorityLevel);
50 scheduleUpdateAtPriority(fiber, priorityLevel);
51 },
52 enqueueReplaceState(instance, state) {
53 const fiber = ReactInstanceMap.get(instance);
56 - const queue = ensureUpdateQueue(fiber);
54 const priorityLevel = getPriorityContext();
58 - addReplaceUpdate(queue, state, priorityLevel);
55 + addReplaceUpdate(fiber, state, priorityLevel);
56 scheduleUpdateAtPriority(fiber, priorityLevel);
57 },
58 enqueueForceUpdate(instance) {
59 const fiber = ReactInstanceMap.get(instance);
63 - const queue = ensureUpdateQueue(fiber);
60 const priorityLevel = getPriorityContext();
65 - addForceUpdate(queue, priorityLevel);
61 + addForceUpdate(fiber, priorityLevel);
62 scheduleUpdateAtPriority(fiber, priorityLevel);
63 },
64 enqueueCallback(instance, callback) {
65 const fiber = ReactInstanceMap.get(instance);
70 - const queue = ensureUpdateQueue(fiber);
66 const priorityLevel = getPriorityContext();
72 - addCallback(queue, callback, priorityLevel);
67 + addCallback(fiber, callback, priorityLevel);
68 scheduleUpdateAtPriority(fiber, priorityLevel);
69 },
70 };
src/renderers/shared/fiber/ReactFiberCommitWork.js
+7 -6
@@ -25,7 +25,7 @@ var {
25 HostPortal,
26 CoroutineComponent,
27 } = ReactTypeOfWork;
28 -var { commitUpdateQueue } = require('ReactFiberUpdateQueue');
28 +var { commitCallbacks } = require('ReactFiberUpdateQueue');
29
30 var {
31 Placement,
@@ -417,16 +417,17 @@ module.exports = function<T, P, I, TI, C, CX>(
417 }
418 attachRef(current, finishedWork, instance);
419 }
420 - if (finishedWork.updateQueue) {
421 - commitUpdateQueue(finishedWork, finishedWork.updateQueue, instance);
420 + const callbackList = finishedWork.callbackList;
421 + if (callbackList) {
422 + commitCallbacks(finishedWork, callbackList, instance);
423 }
424 return;
425 }
426 case HostRoot: {
426 - const updateQueue = finishedWork.updateQueue;
427 - if (updateQueue) {
427 + const callbackList = finishedWork.callbackList;
428 + if (callbackList) {
429 const instance = finishedWork.child && finishedWork.child.stateNode;
429 - commitUpdateQueue(finishedWork, updateQueue, instance);
430 + commitCallbacks(finishedWork, callbackList, instance);
431 }
432 return;
433 }
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+5 -4
@@ -173,7 +173,7 @@ module.exports = function<T, P, I, TI, C, CX>(
173 case FunctionalComponent:
174 workInProgress.memoizedProps = workInProgress.pendingProps;
175 return null;
176 - case ClassComponent:
176 + case ClassComponent: {
177 // We are leaving this subtree, so pop context if any.
178 if (isContextProvider(workInProgress)) {
179 popContextProvider();
@@ -182,11 +182,12 @@ module.exports = function<T, P, I, TI, C, CX>(
182 // merged it and assigned it to the instance. Transfer it from there.
183 // Also need to transfer the props, because pendingProps will be null
184 // in the case of an update.
185 - const { state, props } = workInProgress.stateNode;
186 - workInProgress.memoizedState = state;
187 - workInProgress.memoizedProps = props;
185 + const instance = workInProgress.stateNode;
186 + workInProgress.memoizedState = instance.state;
187 + workInProgress.memoizedProps = instance.props;
188
189 return null;
190 + }
191 case HostRoot: {
192 workInProgress.memoizedProps = workInProgress.pendingProps;
193 const fiberRoot = (workInProgress.stateNode : FiberRoot);
src/renderers/shared/fiber/ReactFiberReconciler.js
+3 -13
@@ -24,7 +24,7 @@ var {
24 var { createFiberRoot } = require('ReactFiberRoot');
25 var ReactFiberScheduler = require('ReactFiberScheduler');
26
27 -var { ensureUpdateQueue, addCallback } = require('ReactFiberUpdateQueue');
27 +var { addCallback } = require('ReactFiberUpdateQueue');
28
29 if (__DEV__) {
30 var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
@@ -123,13 +123,8 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
123 current.alternate.pendingProps = element;
124 }
125 if (callback) {
126 - const queue = ensureUpdateQueue(current);
126 const priorityLevel = getPriorityContext();
128 - addCallback(queue, callback, priorityLevel);
129 - current.updateQueue = queue;
130 - if (current.alternate) {
131 - current.alternate.updateQueue = queue;
132 - }
127 + addCallback(current, callback, priorityLevel);
128 }
129
130 scheduleWork(root);
@@ -159,13 +154,8 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
154 current.alternate.pendingProps = element;
155 }
156 if (callback) {
162 - const queue = ensureUpdateQueue(current);
157 const priorityLevel = getPriorityContext();
164 - addCallback(queue, callback, priorityLevel);
165 - current.updateQueue = queue;
166 - if (current.alternate) {
167 - current.alternate.updateQueue = queue;
168 - }
158 + addCallback(current, callback, priorityLevel);
159 }
160
161 scheduleWork(root);
src/renderers/shared/fiber/ReactFiberUpdateQueue.js
+183 -193
@@ -41,29 +41,19 @@ type Update = {
41 next: Update | null,
42 };
43
44 -// Linked-list of updates
44 +// Singly linked-list of updates. When an update is scheduled, it is added to
45 +// the queue of the current fiber and the work-in-progress fiber. The two queues
46 +// are separate but they share a persistent structure.
47 //
46 -// - A "pending" update is one that has been scheduled but not yet used
47 -// for reconciliation.
48 -// - A "progressed" update is an update that was used for reconciliation, but
49 -// has not yet been flushed.
48 +// During reconciliation, updates are removed from the work-in-progress fiber,
49 +// but they remain on the current fiber. That ensures that if a work-in-progress
50 +// is aborted, the aborted updates are recovered by cloning from current.
51 //
51 -// The queue maintains a pointer to the last progressed update in the list.
52 -// Updates that come after that pointer are pending. The pointer is set to the
53 -// end of the list during reconciliation.
52 +// The work-in-progress queue is always a subset of the current queue.
53 //
55 -// Pending updates are sorted by priority then insertion. Progressed updates
56 -// are sorted by the order in which they were applied during reconciliation,
57 -// which may not be by priority: if a component bails out before the updates are
58 -// committed, in the next render, the progressed updates are applied in the same
59 -// order that they were previously, even if a higher priority update comes in.
60 -//
61 -// Once a progressed update is flushed/committed, it's removed from the queue.
54 +// When the tree is committed, the work-in-progress becomes the current.
55 export type UpdateQueue = {
56 first: Update | null,
64 - // A pointer to the last progressed update in the queue. This may be null
65 - // even in a non-empty queue, if all the updates are pending.
66 - lastProgressedUpdate: Update | null,
57 last: Update | null,
58 };
59
@@ -84,98 +74,64 @@ function comparePriority(a : PriorityLevel, b : PriorityLevel) : number {
74 return a - b;
75 }
76
87 -function getFirstPendingUpdate(queue : UpdateQueue) : Update | null {
88 - if (queue.lastProgressedUpdate) {
89 - return queue.lastProgressedUpdate.next;
90 - }
91 - return queue.first;
92 -}
93 -
94 -function getFirstProgressedUpdate(queue : UpdateQueue) : Update | null {
95 - if (queue.lastProgressedUpdate) {
96 - return queue.first;
97 - }
98 - return null;
99 -}
100 -
77 function hasPendingUpdate(queue : UpdateQueue, priorityLevel : PriorityLevel) : boolean {
102 - const firstPendingUpdate = getFirstPendingUpdate(queue);
103 - if (!firstPendingUpdate) {
78 + if (!queue.first) {
79 return false;
80 }
81 // Return true if the first pending update has greater or equal priority.
107 - return comparePriority(firstPendingUpdate.priorityLevel, priorityLevel) <= 0;
82 + return comparePriority(queue.first.priorityLevel, priorityLevel) <= 0;
83 }
84 exports.hasPendingUpdate = hasPendingUpdate;
85
111 -// Ensures that a fiber and its alternate have an update queue, creating a new
112 -// one if needed. Returns the new or existing queue.
86 +// Ensures that a fiber has an update queue, creating a new one if needed.
87 +// Returns the new or existing queue.
88 function ensureUpdateQueue(fiber : Fiber) : UpdateQueue {
89 if (fiber.updateQueue) {
90 // We already have an update queue.
91 return fiber.updateQueue;
92 }
118 - const queue : UpdateQueue = {
93 + const queue = {
94 first: null,
120 - lastProgressedUpdate: null,
95 last: null,
96 };
97 fiber.updateQueue = queue;
124 - // Add queue to the alternate as well, because when we call setState we don't
125 - // know which tree is current.
126 - if (fiber.alternate) {
127 - fiber.alternate.updateQueue = queue;
128 - }
98 return queue;
99 }
131 -exports.ensureUpdateQueue = ensureUpdateQueue;
132 -
133 -function insertUpdateIntoQueue(queue : UpdateQueue, update : Update) : void {
134 - // Add a pending update to the end of the queue.
135 - if (!queue.last) {
136 - // The queue is empty.
137 - queue.first = queue.last = update;
138 - return;
139 - }
140 - // The queue is not empty. Insert the new update into the queue, sorted by
141 - // priority then insertion order.
142 - const firstPendingUpdate = getFirstPendingUpdate(queue);
143 - if (!firstPendingUpdate && queue.last) {
144 - // This is the first pending update. Add it to the end of the queue.
145 - queue.last.next = update;
146 - queue.last = update;
147 - return;
148 - }
100
150 - const priorityLevel = update.priorityLevel;
151 - const lastPendingUpdate = queue.last;
152 -
153 - let insertAfter;
154 - let insertBefore;
155 - if (queue.last && comparePriority(queue.last.priorityLevel, priorityLevel) <= 0) {
156 - // Fast path where the incoming update has equal or lower priority than the
157 - // last pending update. We can just append it to the end of the queue.
158 - insertAfter = lastPendingUpdate;
159 - insertBefore = null;
160 - } else {
161 - // Loop through the pending updates to find the first one with lower
162 - // priority than the incoming update. Insert the incoming update before
163 - // that one.
164 - insertAfter = queue.lastProgressedUpdate;
165 - insertBefore = firstPendingUpdate;
166 - while (
167 - insertBefore &&
168 - comparePriority(insertBefore.priorityLevel, priorityLevel) <= 0
169 - ) {
170 - insertAfter = insertBefore;
171 - insertBefore = insertBefore.next;
172 - }
101 +// Clones an update queue from a source fiber onto its alternate.
102 +function cloneUpdateQueue(alt : Fiber, fiber : Fiber) : UpdateQueue | null {
103 + const sourceQueue = fiber.updateQueue;
104 + if (!sourceQueue) {
105 + // The source fiber does not have an update queue.
106 + alt.updateQueue = null;
107 + return null;
108 }
109 + // If the alternate already has a queue, reuse the previous object.
110 + const altQueue = alt.updateQueue || {};
111 + altQueue.first = sourceQueue.first;
112 + altQueue.last = sourceQueue.last;
113 + alt.updateQueue = altQueue;
114 + return altQueue;
115 +}
116 +exports.cloneUpdateQueue = cloneUpdateQueue;
117 +
118 +function cloneUpdate(update : Update) : Update {
119 + return {
120 + priorityLevel: update.priorityLevel,
121 + partialState: update.partialState,
122 + callback: update.callback,
123 + isReplace: update.isReplace,
124 + isForced: update.isForced,
125 + next: null,
126 + };
127 +}
128
129 +function insertUpdateIntoQueue(queue, update, insertAfter, insertBefore) {
130 if (insertAfter) {
131 insertAfter.next = update;
132 } else {
133 // This is the first item in the queue.
134 + update.next = queue.first;
135 queue.first = update;
136 }
137
@@ -187,12 +143,84 @@ function insertUpdateIntoQueue(queue : UpdateQueue, update : Update) : void {
143 }
144 }
145
146 +// The work-in-progress queue is a subset of the current queue (if it exists).
147 +// We need to insert the incoming update into both lists. However, it's possible
148 +// that the correct position in one list will be different from the position in
149 +// the other. Consider the following case:
150 +//
151 +// Current: 3-5-6
152 +// Work-in-progress: 6
153 +//
154 +// Then we receive an update with priority 4 and insert it into each list:
155 +//
156 +// Current: 3-4-5-6
157 +// Work-in-progress: 4-6
158 +//
159 +// In the current queue, the new update's `next` pointer points to the update
160 +// with priority 5. But in the work-in-progress queue, the pointer points to the
161 +// update with priority 6. Because these two queues share the same persistent
162 +// data structure, this won't do. (This can only happen when the incoming update
163 +// has higher priority than all the updates in the work-in-progress queue.)
164 +//
165 +// To solve this, in the case where the incoming update needs to be inserted
166 +// into two different positions, we'll make a clone of the update and insert
167 +// each copy into a separate queue. This forks the list while maintaining a
168 +// persistent stucture, because the update that is added to the work-in-progress
169 +// is always added to the front of the list.
170 +//
171 +// However, if incoming update is inserted into the same position of both lists,
172 +// we shouldn't make a copy.
173 +function insertUpdate(fiber : Fiber, update : Update) : void {
174 + const queue1 = ensureUpdateQueue(fiber);
175 + const queue2 = fiber.alternate ? ensureUpdateQueue(fiber.alternate) : null;
176 +
177 + const priorityLevel = update.priorityLevel;
178 +
179 + // TODO: Fast path where last item in the queue has greater or equal priority.
180 +
181 + let insertAfter1 = null;
182 + let insertBefore1 = queue1.first;
183 + while (insertBefore1 && comparePriority(insertBefore1.priorityLevel, priorityLevel) <= 0) {
184 + insertAfter1 = insertBefore1;
185 + insertBefore1 = insertBefore1.next;
186 + }
187 + const update1 = update;
188 +
189 + let insertAfter2 = null;
190 + let insertBefore2 = null;
191 + let update2 = null;
192 + if (queue2) {
193 + insertBefore2 = queue2.first;
194 + while (insertBefore2 && comparePriority(insertBefore2.priorityLevel, priorityLevel) <= 0) {
195 + insertAfter2 = insertBefore2;
196 + insertBefore2 = insertBefore2.next;
197 + }
198 +
199 + if (insertBefore1 === insertBefore2) {
200 + // The update is inserted into the same position of both lists. There's no
201 + // need to clone the update.
202 + update2 = update1;
203 + } else {
204 + // The update is inserted into two separate positions. Make a clone of the
205 + // update and insert it twice. One or the other will be dropped the next
206 + // time we commit.
207 + update2 = cloneUpdate(update1);
208 + }
209 + }
210 +
211 + // Now we're ready to insert the updates into their queues.
212 + insertUpdateIntoQueue(queue1, update1, insertAfter1, insertBefore1);
213 + if (queue2 && update2) {
214 + insertUpdateIntoQueue(queue2, update2, insertAfter2, insertBefore2);
215 + }
216 +}
217 +
218 function addUpdate(
191 - queue : UpdateQueue,
219 + fiber : Fiber,
220 partialState : PartialState<any, any> | null,
221 priorityLevel : PriorityLevel
222 ) : void {
195 - const update : Update = {
223 + const update = {
224 priorityLevel,
225 partialState,
226 callback: null,
@@ -200,16 +228,16 @@ function addUpdate(
228 isForced: false,
229 next: null,
230 };
203 - insertUpdateIntoQueue(queue, update);
231 + insertUpdate(fiber, update);
232 }
233 exports.addUpdate = addUpdate;
234
235 function addReplaceUpdate(
208 - queue : UpdateQueue,
236 + fiber : Fiber,
237 state : any | null,
238 priorityLevel : PriorityLevel
239 ) : void {
212 - const update : Update = {
240 + const update = {
241 priorityLevel,
242 partialState: state,
243 callback: null,
@@ -217,29 +245,12 @@ function addReplaceUpdate(
245 isForced: false,
246 next: null,
247 };
220 - // Add a pending update to the end of the queue.
221 - if (!queue.last) {
222 - // The queue is empty.
223 - queue.first = queue.last = update;
224 - return;
225 - }
226 - // The queue is not empty. Insert the new update into the queue, sorted by
227 - // priority then insertion order. Since this is a replace, drop all pending
228 - // updates with equal priority. We can't drop updates with higher priority,
229 - // because they might be flushed in an earlier commit. We'll drop them during
230 - // the commit phase if necessary.
231 - const firstPendingUpdate = getFirstPendingUpdate(queue);
232 - if (!firstPendingUpdate && queue.last) {
233 - // This is the first pending update. Add it to the end of the queue.
234 - queue.last.next = update;
235 - queue.last = update;
236 - return;
237 - }
248
239 - // Find the last pending update with equal priority.
240 - let replaceAfter = queue.lastProgressedUpdate;
241 - let replaceBefore = firstPendingUpdate;
242 - if (replaceBefore) {
249 + // Drop all updates with equal priority
250 + let queue = ensureUpdateQueue(fiber);
251 + for (let i = 0; queue && i < 2; i++) {
252 + let replaceAfter = null;
253 + let replaceBefore = queue.first;
254 let comparison = Infinity;
255 while (replaceBefore &&
256 (comparison = comparePriority(replaceBefore.priorityLevel, priorityLevel)) <= 0) {
@@ -248,26 +259,30 @@ function addReplaceUpdate(
259 }
260 replaceBefore = replaceBefore.next;
261 }
251 - }
262
253 - if (replaceAfter) {
254 - replaceAfter.next = update;
255 - } else {
256 - // This is the first item in the queue.
257 - queue.first = update;
258 - }
263 + if (replaceAfter) {
264 + replaceAfter.next = replaceBefore;
265 + } else {
266 + queue.first = replaceBefore;
267 + }
268
260 - if (replaceBefore) {
261 - update.next = replaceBefore;
262 - } else {
263 - // This is the last item in the queue.
264 - queue.last = update;
269 + if (!replaceBefore) {
270 + queue.last = replaceAfter;
271 + }
272 +
273 + if (fiber.alternate) {
274 + queue = ensureUpdateQueue(fiber.alternate);
275 + } else {
276 + queue = null;
277 + }
278 }
279 +
280 + insertUpdate(fiber, update);
281 }
282 exports.addReplaceUpdate = addReplaceUpdate;
283
269 -function addForceUpdate(queue : UpdateQueue, priorityLevel : PriorityLevel) : void {
270 - const update : Update = {
284 +function addForceUpdate(fiber : Fiber, priorityLevel : PriorityLevel) : void {
285 + const update = {
286 priorityLevel,
287 partialState: null,
288 callback: null,
@@ -275,23 +290,13 @@ function addForceUpdate(queue : UpdateQueue, priorityLevel : PriorityLevel) : vo
290 isForced: true,
291 next: null,
292 };
278 - insertUpdateIntoQueue(queue, update);
293 + insertUpdate(fiber, update);
294 }
295 exports.addForceUpdate = addForceUpdate;
296
297
283 -function addCallback(queue : UpdateQueue, callback: Callback, priorityLevel : PriorityLevel) : void {
284 - if (getFirstPendingUpdate(queue) &&
285 - queue.last &&
286 - (queue.last.priorityLevel === priorityLevel) &&
287 - !queue.last.callback) {
288 - // If pending updates already exist, and the last pending update does not
289 - // have a callback, and the priority levels are equal, we can add the
290 - // incoming callback to that update to avoid an extra allocation.
291 - queue.last.callback = callback;
292 - return;
293 - }
294 -
298 +function addCallback(fiber : Fiber, callback: Callback, priorityLevel : PriorityLevel) : void {
299 + // TODO: Fast path where last item in queue has equal priority.
300 const update : Update = {
301 priorityLevel,
302 partialState: null,
@@ -300,13 +305,12 @@ function addCallback(queue : UpdateQueue, callback: Callback, priorityLevel : Pr
305 isForced: false,
306 next: null,
307 };
303 - insertUpdateIntoQueue(queue, update);
308 + insertUpdate(fiber, update);
309 }
310 exports.addCallback = addCallback;
311
312 function getPendingPriority(queue : UpdateQueue) : PriorityLevel {
308 - const firstPendingUpdate = getFirstPendingUpdate(queue);
309 - return firstPendingUpdate ? firstPendingUpdate.priorityLevel : NoWork;
313 + return queue.first ? queue.first.priorityLevel : NoWork;
314 }
315 exports.getPendingPriority = getPendingPriority;
316
@@ -329,24 +333,14 @@ function beginUpdateQueue(
333 priorityLevel : PriorityLevel
334 ) : any {
335 // Applies updates with matching priority to the previous state to create
332 - // a new state object. If an update was used previously but never flushed
333 - // due to a bail out, it's used again regardless of its priority.
334 -
335 - // Reset these flags. We'll update them while looping through the queue.
336 - workInProgress.effectTag &= ~ForceUpdate;
337 - workInProgress.effectTag &= ~CallbackEffect;
336 + // a new state object.
337
339 - const prevLastProgressedUpdate = queue.lastProgressedUpdate;
338 let state = prevState;
339 let dontMutatePrevState = true;
340 let isEmpty = true;
343 - let alreadyProgressedUpdate = Boolean(prevLastProgressedUpdate);
344 - let lastProgressedUpdate = null;
345 - let update : Update | null = queue.first;
346 - while (update && (
347 - alreadyProgressedUpdate ||
348 - comparePriority(update.priorityLevel, priorityLevel) <= 0
349 - )) {
341 + let callbackList = null;
342 + let update = queue.first;
343 + while (update && comparePriority(update.priorityLevel, priorityLevel) <= 0) {
344 let partialState;
345 if (update.isReplace) {
346 // A replace should drop all previous updates in the queue, so
@@ -370,56 +364,52 @@ function beginUpdateQueue(
364 workInProgress.effectTag |= ForceUpdate;
365 }
366 if (update.callback) {
367 + if (callbackList && callbackList.last) {
368 + callbackList.last.next = update;
369 + callbackList.last = update;
370 + } else {
371 + callbackList = {
372 + first: update,
373 + last: update,
374 + };
375 + }
376 workInProgress.effectTag |= CallbackEffect;
377 }
375 - if (update === prevLastProgressedUpdate) {
376 - alreadyProgressedUpdate = false;
377 - }
378 - lastProgressedUpdate = update;
378 update = update.next;
379 }
380
382 -
383 - // Mark the point in the queue where we stopped applying updates
384 - queue.lastProgressedUpdate = lastProgressedUpdate;
385 -
381 if (isEmpty) {
387 - // None of the updates contained state. Return the original state object.
388 - return prevState;
382 + // None of the updates contained state. Use the original state object.
383 + state = prevState;
384 + }
385 +
386 + if (update) {
387 + queue.first = update;
388 + } else {
389 + // Queue is now empty
390 + workInProgress.updateQueue = null;
391 }
392
393 + workInProgress.callbackList = callbackList;
394 + workInProgress.memoizedState = state;
395 +
396 return state;
397 }
398 exports.beginUpdateQueue = beginUpdateQueue;
399
395 -function commitUpdateQueue(finishedWork : Fiber, queue : UpdateQueue, context : mixed) {
396 -
397 - if (finishedWork.effectTag & CallbackEffect) {
398 - // Call the callbacks on all the non-pending updates.
399 - let update = getFirstProgressedUpdate(queue);
400 - while (update && update !== getFirstPendingUpdate(queue)) {
401 - const callback = update.callback;
402 - if (typeof callback === 'function') {
403 - callback.call(context);
404 - }
405 - update = update.next;
400 +function commitCallbacks(finishedWork : Fiber, callbackList : UpdateQueue, context : mixed) {
401 + const stopAfter = callbackList.last;
402 + let update = callbackList.first;
403 + while (update) {
404 + const callback = update.callback;
405 + if (typeof callback === 'function') {
406 + callback.call(context);
407 }
407 - }
408 -
409 - // Drop all comitted updates, leaving only the pending updates.
410 - queue.first = getFirstPendingUpdate(queue);
411 - queue.lastProgressedUpdate = null;
412 - if (!queue.first) {
413 - queue.last = queue.lastProgressedUpdate = null;
414 -
415 - // If the list is now empty, we can remove it from the finished work
416 - finishedWork.updateQueue = null;
417 - if (finishedWork.alternate) {
418 - // Normally we don't mutate the current tree, but we do for updates.
419 - // The queue on the work in progress is always the same as the queue
420 - // on the current.
421 - finishedWork.alternate.updateQueue = null;
408 + if (update === stopAfter) {
409 + break;
410 }
411 + update = update.next;
412 }
413 + finishedWork.callbackList = null;
414 }
425 -exports.commitUpdateQueue = commitUpdateQueue;
415 +exports.commitCallbacks = commitCallbacks;
src/renderers/shared/fiber/__tests__/ReactIncrementalUpdates-test.js
+1 -1
@@ -211,7 +211,7 @@ describe('ReactIncrementalUpdates', () => {
211 // Because e is a replaceState, d gets dropped.
212 ReactNoop.flush();
213 expect(ReactNoop.getChildren()).toEqual([span('efg')]);
214 - // Ensure that update d is not progressed before being replaced.
214 + // Ensure that updater function d is never called.
215 expect(progressedUpdates).toEqual(['e', 'f', 'g']);
216 });
217