Batch nested updates when in sync mode
Almost working...
Andrew Clark committed
Oct 28, 2016 at 13:26 UTC
ecf468e5567586192d61b0e426398878cb5cefcb
4 files changed
+46
-23
src/renderers/dom/fiber/ReactDOMFiber.js
+2
@@ -112,6 +112,8 @@ var DOMRenderer = ReactFiberReconciler({
112
113
scheduleDeferredCallback: window.requestIdleCallback,
114
115
+ useSyncScheduling: true,
116
+
117
});
118
119
var warned = false;
src/renderers/shared/fiber/ReactFiberClassComponent.js
+3
-3
@@ -130,7 +130,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
130
// process them now.
131
const updateQueue = workInProgress.updateQueue;
132
if (updateQueue) {
133
- instance.state = mergeUpdateQueue(updateQueue, state, props);
133
+ instance.state = mergeUpdateQueue(updateQueue, instance, state, props);
134
}
135
}
136
}
@@ -174,7 +174,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
174
// process them now.
175
const newUpdateQueue = workInProgress.updateQueue;
176
if (newUpdateQueue) {
177
- newInstance.state = mergeUpdateQueue(newUpdateQueue, newState, newProps);
177
+ newInstance.state = mergeUpdateQueue(newUpdateQueue, newInstance, newState, newProps);
178
}
179
}
180
return true;
@@ -211,7 +211,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
211
// TODO: Previous state can be null.
212
let newState;
213
if (updateQueue) {
214
- newState = mergeUpdateQueue(updateQueue, previousState, newProps);
214
+ newState = mergeUpdateQueue(updateQueue, instance, previousState, newProps);
215
} else {
216
newState = previousState;
217
}
src/renderers/shared/fiber/ReactFiberScheduler.js
+39
-18
@@ -71,6 +71,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
71
SynchronousPriority :
72
LowPriority;
73
74
+ // Whether updates should be batched. Only applies when using sync scheduling.
75
+ let shouldBatchUpdates : boolean = false;
76
+
77
// The next work in progress fiber that we're currently working on.
78
let nextUnitOfWork : ?Fiber = null;
79
let nextPriorityLevel : PriorityLevel = NoWork;
@@ -490,9 +493,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
493
while (nextUnitOfWork &&
494
nextPriorityLevel === SynchronousPriority) {
495
nextUnitOfWork = performUnitOfWork(nextUnitOfWork, false);
493
- // If there's no nextUnitOfWork, we don't need to search for more
494
- // because it shouldn't be possible to schedule sync work without
495
- // immediately performing it
496
+
497
+ if (!nextUnitOfWork) {
498
+ nextUnitOfWork = findNextUnitOfWork();
499
+ }
500
}
501
if (nextUnitOfWork) {
502
if (nextPriorityLevel > AnimationPriority) {
@@ -504,7 +508,35 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
508
}
509
510
function performSynchronousWork() {
511
+ if (useSyncScheduling) {
512
+ // Start batching updates
513
+ shouldBatchUpdates = true;
514
+ }
515
performAndHandleErrors(performSynchronousWorkUnsafe);
516
+ shouldBatchUpdates = false;
517
+ }
518
+
519
+ function scheduleSynchronousWork(root : FiberRoot) {
520
+ root.current.pendingWorkPriority = SynchronousPriority;
521
+
522
+ if (root.isScheduled) {
523
+ // If we're already scheduled, we can bail out.
524
+ return;
525
+ }
526
+ root.isScheduled = true;
527
+ if (lastScheduledRoot) {
528
+ // Schedule ourselves to the end.
529
+ lastScheduledRoot.nextScheduledRoot = root;
530
+ lastScheduledRoot = root;
531
+ } else {
532
+ // We're the only work scheduled.
533
+ nextScheduledRoot = root;
534
+ lastScheduledRoot = root;
535
+
536
+ if (!shouldBatchUpdates) {
537
+ performSynchronousWork();
538
+ }
539
+ }
540
}
541
542
function performAndHandleErrors<A>(fn: (a: A) => void, a: A) {
@@ -560,10 +592,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
592
593
// We will process an update caused by each error boundary synchronously.
594
affectedBoundaries.forEach(boundary => {
563
- // FIXME: We only specify LowPriority here so that setState() calls from the error
564
- // boundaries are respected. Instead we should set default priority level or something
565
- // like this. Reconsider this piece when synchronous scheduling is in place.
566
- const priority = LowPriority;
595
+ const priority = priorityContext !== null ?
596
+ priorityContext :
597
+ defaultPriorityContext;
598
const root = scheduleErrorBoundaryWork(boundary, priority);
599
// This should use findNextUnitOfWork() when synchronous scheduling is implemented.
600
let fiber = cloneFiber(root.current, priority);
@@ -604,8 +635,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
635
}
636
637
if (priorityLevel === SynchronousPriority) {
607
- root.current.pendingWorkPriority = SynchronousPriority;
608
- performSynchronousWork();
638
+ scheduleSynchronousWork(root);
639
}
640
641
if (priorityLevel === NoWork) {
@@ -626,15 +656,6 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
656
defaultPriorityContext;
657
}
658
629
- // Don't bother bubbling the priority to the root if it is synchronous. Just
630
- // perform it now.
631
- if (priorityLevel === SynchronousPriority) {
632
- fiber.pendingWorkPriority = SynchronousPriority;
633
- nextUnitOfWork = fiber;
634
- performSynchronousWork();
635
- return;
636
- }
637
-
659
while (true) {
660
if (fiber.pendingWorkPriority === NoWork ||
661
fiber.pendingWorkPriority >= priorityLevel) {
src/renderers/shared/fiber/ReactFiberUpdateQueue.js
+2
-2
@@ -73,14 +73,14 @@ exports.callCallbacks = function(queue : UpdateQueue, context : any) {
73
}
74
};
75
76
-exports.mergeUpdateQueue = function(queue : UpdateQueue, prevState : any, props : any) : any {
76
+exports.mergeUpdateQueue = function(queue : UpdateQueue, instance : any, prevState : any, props : any) : any {
77
let node : ?UpdateQueueNode = queue;
78
let state = queue.isReplace ? null : Object.assign({}, prevState);
79
while (node) {
80
let partialState;
81
if (typeof node.partialState === 'function') {
82
const updateFn = node.partialState;
83
- partialState = updateFn(state, props);
83
+ partialState = updateFn.call(instance, state, props);
84
} else {
85
partialState = node.partialState;
86
}