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