Split performWork into renderRoot and commitRoot (#11264)

* Split performWork into renderRoot and commitRoot

It turns out that the scheduler is too coupled to how the DOM renderer
works. Specifically, the requestIdleCallback model, and how roots are
committed immediately after completing. Other renderers have different
constraints for when to yield and when to commit work.

We're moving towards a model where the scheduler only works on a single
root at a time, and the render phase and commit phase are split into
distinct entry points. This gives the renderer more control over when
roots are committed, coordinating multiple roots, deferring the commit
phase, batching updates, when to yield execution, and so on.

In this initial commit, I've left the renderers alone and only changed
the scheduler. Mostly, this involved extracting logic related to
multiple roots and moving it into its own section at the bottom of the
file. The idea is that this section can be lifted pretty much as-is
into the renderers. I'll do that next.

* Remove FiberRoot scheduleAt

Isn't actually used anywhere

* Make the root schedule a linked list again

Since this still lives inside the renderer, let's just use the
FiberRoot type. The FiberRoot concept will likely be lifted out
eventually, anyway.

* commitRoot should accept a HostRoot

This way it's less reliant on the alternate model

* Unify branches

* Remove dead branch

onUncaughtError is only called while we're working on a root.

* remainingWork -> remainingExpirationTime

I was wary of leaking NoWork but mixing numbers and null is worse so
let's just do it until we think of something better.

* Rename stuff
This commit is contained in:
Andrew Clark
2017-10-20 13:22:29 -07:00
committed by GitHub
parent c7d28a0136
commit 2e4663f616
5 changed files with 670 additions and 653 deletions
@@ -48,25 +48,3 @@ function computeExpirationBucket(
);
}
exports.computeExpirationBucket = computeExpirationBucket;
// Given the current clock time and an expiration time, returns the
// relative expiration time. Possible values include NoWork, Sync, Task, and
// Never. All other values represent an async expiration time.
function relativeExpirationTime(
currentTime: ExpirationTime,
expirationTime: ExpirationTime,
): ExpirationTime {
switch (expirationTime) {
case NoWork:
case Sync:
case Task:
case Never:
return expirationTime;
}
const delta = expirationTime - currentTime;
if (delta <= 0) {
return Task;
}
return msToExpirationTime(delta);
}
exports.relativeExpirationTime = relativeExpirationTime;
+17 -6
View File
@@ -11,8 +11,10 @@
'use strict';
import type {Fiber} from 'ReactFiber';
import type {ExpirationTime} from 'ReactFiberExpirationTime';
const {createHostRootFiber} = require('ReactFiber');
const {NoWork} = require('ReactFiberExpirationTime');
export type FiberRoot = {
// Any additional information from the host associated with this root.
@@ -21,15 +23,22 @@ export type FiberRoot = {
pendingChildren: any,
// The currently active root fiber. This is the mutable root of the tree.
current: Fiber,
// Determines if this root has already been added to the schedule for work.
isScheduled: boolean,
// The work schedule is a linked list.
nextScheduledRoot: FiberRoot | null,
// Remaining expiration time on this root.
remainingExpirationTime: ExpirationTime,
// Determines if this root can be committed.
isReadyForCommit: boolean,
// A finished work-in-progress HostRoot that's ready to be committed.
// TODO: The reason this is separate from isReadyForCommit is because the
// FiberRoot concept will likely be lifted out of the reconciler and into
// the renderer.
finishedWork: Fiber | null,
// Top context object, used by renderSubtreeIntoContainer
context: Object | null,
pendingContext: Object | null,
// Determines if we should attempt to hydrate on the initial mount
+hydrate: boolean,
// Linked-list of roots
nextScheduledRoot: FiberRoot | null,
};
exports.createFiberRoot = function(
@@ -43,11 +52,13 @@ exports.createFiberRoot = function(
current: uninitializedFiber,
containerInfo: containerInfo,
pendingChildren: null,
isScheduled: false,
nextScheduledRoot: null,
remainingExpirationTime: NoWork,
isReadyForCommit: false,
finishedWork: null,
context: null,
pendingContext: null,
hydrate,
nextScheduledRoot: null,
};
uninitializedFiber.stateNode = root;
return root;
File diff suppressed because it is too large Load Diff
@@ -6,10 +6,11 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
⚛ AllLifecycles [mount]
⚛ AllLifecycles.componentWillMount
⚛ AllLifecycles.getChildContext
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⚛ AllLifecycles.componentDidMount
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⚛ AllLifecycles.componentDidMount
// Update
⚛ (React Tree Reconciliation)
@@ -18,17 +19,19 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
⚛ AllLifecycles.shouldComponentUpdate
⚛ AllLifecycles.componentWillUpdate
⚛ AllLifecycles.getChildContext
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
⚛ AllLifecycles.componentDidUpdate
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
⚛ AllLifecycles.componentDidUpdate
// Unmount
⚛ (React Tree Reconciliation)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ AllLifecycles.componentWillUnmount
(Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
AllLifecycles.componentWillUnmount
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
@@ -40,44 +43,53 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc
⚛ B [update]
⚛ A [update]
⚛ B [update]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 9 Total)
⚛ (Calling Lifecycle Methods: 9 Total)
⚛ A.componentDidUpdate
B.componentDidUpdate
⚛ (Committing Changes)
⚛ (Committing Host Effects: 9 Total)
⚛ (Calling Lifecycle Methods: 9 Total)
A.componentDidUpdate
⚛ B.componentDidUpdate
// Because of deduplication, we don't know B was cascading,
// but we should still see the warning for the commit phase.
(React Tree Reconciliation) Warning: There were cascading updates
(React Tree Reconciliation)
⚛ Parent [update]
⚛ A [update]
⚛ B [update]
⚛ A [update]
⚛ B [update]
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
(Committing Host Effects: 9 Total)
⚛ (Calling Lifecycle Methods: 9 Total)
⚛ A.componentDidUpdate
B.componentDidUpdate
(Committing Changes) Warning: Lifecycle hook scheduled a cascading update
⚛ (Committing Host Effects: 9 Total)
⚛ (Calling Lifecycle Methods: 9 Total)
A.componentDidUpdate
⚛ B.componentDidUpdate
⚛ (React Tree Reconciliation)
⚛ B [update]
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 3 Total)
⚛ B.componentDidUpdate
⚛ (Committing Changes)
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 3 Total)
⚛ B.componentDidUpdate
"
`;
exports[`ReactDebugFiberPerf does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
" (React Tree Reconciliation) Warning: There were cascading updates
" (React Tree Reconciliation)
⚛ Component [mount]
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
(Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⛔ Component.componentDidMount Warning: Scheduled a cascading update
(Committing Changes) Warning: Lifecycle hook scheduled a cascading update
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⛔ Component.componentDidMount Warning: Scheduled a cascading update
⚛ (React Tree Reconciliation)
⚛ Component [update]
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
"
`;
@@ -87,18 +99,20 @@ exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascadi
⚛ Parent [mount]
⚛ NotCascading [mount]
⚛ NotCascading.componentWillMount
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
// Should not print a warning
⚛ (React Tree Reconciliation)
⚛ Parent [update]
⚛ NotCascading [update]
⚛ NotCascading.componentWillReceiveProps
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
"
`;
@@ -107,23 +121,26 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
// Update
⚛ (React Tree Reconciliation)
⚛ Parent [update]
⚛ Child [update]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
// Unmount
⚛ (React Tree Reconciliation)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
@@ -144,9 +161,10 @@ exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
⚛ Parent [mount]
⚛ B [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
@@ -154,35 +172,41 @@ exports[`ReactDebugFiberPerf measures deprioritized work 1`] = `
"// Flush the parent
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
// Flush the child
⚛ (React Tree Reconciliation)
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
"
`;
exports[`ReactDebugFiberPerf recovers from caught errors 1`] = `
"// Stop on Baddie and restart from Boundary
(React Tree Reconciliation) Warning: There were cascading updates
(React Tree Reconciliation)
⚛ Parent [mount]
⛔ Boundary [mount] Warning: An error was thrown inside this error boundary
⚛ Parent [mount]
⚛ Baddie [mount]
⚛ Boundary [mount]
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
(Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
(Committing Changes) Warning: Lifecycle hook scheduled a cascading update
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⚛ (React Tree Reconciliation)
⚛ Boundary [update]
⚛ ErrorReport [mount]
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
"
`;
@@ -191,17 +215,19 @@ exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = `
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ Baddie [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
// Will reconcile from a clean state
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
@@ -210,9 +236,10 @@ exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
⚛ (React Tree Reconciliation)
⚛ A [update]
⚛ B [update]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 6 Total)
⚛ (Calling Lifecycle Methods: 6 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 6 Total)
⚛ (Calling Lifecycle Methods: 6 Total)
"
`;
@@ -226,9 +253,10 @@ exports[`ReactDebugFiberPerf supports coroutines 1`] = `
⚛ CoChild [mount]
⚛ Continuation [mount]
⚛ Continuation [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
@@ -236,40 +264,49 @@ exports[`ReactDebugFiberPerf supports portals 1`] = `
"⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = `
"// Should print a warning
(React Tree Reconciliation) Warning: There were cascading updates
(React Tree Reconciliation)
⚛ Parent [mount]
⚛ Cascading [mount]
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
(Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
(Committing Changes) Warning: Lifecycle hook scheduled a cascading update
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
⚛ (React Tree Reconciliation)
⚛ Cascading [update]
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
"
`;
exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = `
"// Rendering the first root
(React Tree Reconciliation) Warning: There were cascading updates
(React Tree Reconciliation)
⚛ Cascading [mount]
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
(Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
// Scheduling another root from componentDidMount
(Committing Changes) Warning: Lifecycle hook scheduled a cascading update
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
// Scheduling another root from componentDidMount
⚛ (React Tree Reconciliation)
⚛ Child [mount]
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
+74 -74
View File
@@ -17,36 +17,36 @@
"gzip": 2364
},
"React-dev.js (FB_DEV)": {
"size": 43016,
"gzip": 11325
"size": 43033,
"gzip": 11331
},
"React-prod.js (FB_PROD)": {
"size": 24831,
"gzip": 6707
"size": 24848,
"gzip": 6713
},
"react-dom.development.js (UMD_DEV)": {
"size": 631104,
"gzip": 144685
"size": 629078,
"gzip": 144076
},
"react-dom.production.min.js (UMD_PROD)": {
"size": 100038,
"gzip": 31532
"size": 101020,
"gzip": 31886
},
"react-dom.development.js (NODE_DEV)": {
"size": 593383,
"gzip": 135883
"size": 591361,
"gzip": 135305
},
"react-dom.production.min.js (NODE_PROD)": {
"size": 106647,
"gzip": 33435
"size": 107254,
"gzip": 33591
},
"ReactDOMFiber-dev.js (FB_DEV)": {
"size": 590572,
"gzip": 135237
"size": 588633,
"gzip": 134646
},
"ReactDOMFiber-prod.js (FB_PROD)": {
"size": 419911,
"gzip": 93354
"size": 418791,
"gzip": 93017
},
"react-dom-test-utils.development.js (NODE_DEV)": {
"size": 41743,
@@ -73,96 +73,96 @@
"gzip": 4510
},
"ReactDOMUnstableNativeDependencies-dev.js (FB_DEV)": {
"size": 80190,
"gzip": 19882
"size": 80207,
"gzip": 19890
},
"ReactDOMUnstableNativeDependencies-prod.js (FB_PROD)": {
"size": 65164,
"gzip": 15538
"size": 65181,
"gzip": 15545
},
"react-dom-server.browser.development.js (UMD_DEV)": {
"size": 124910,
"gzip": 32260
"size": 124902,
"gzip": 32255
},
"react-dom-server.browser.production.min.js (UMD_PROD)": {
"size": 15345,
"gzip": 5968
"size": 15337,
"gzip": 5967
},
"react-dom-server.browser.development.js (NODE_DEV)": {
"size": 94830,
"gzip": 25139
"size": 94822,
"gzip": 25134
},
"react-dom-server.browser.production.min.js (NODE_PROD)": {
"size": 15071,
"gzip": 5902
"size": 15063,
"gzip": 5899
},
"ReactDOMServer-dev.js (FB_DEV)": {
"size": 94490,
"gzip": 25063
"size": 94499,
"gzip": 25066
},
"ReactDOMServer-prod.js (FB_PROD)": {
"size": 42454,
"gzip": 11844
"size": 42463,
"gzip": 11846
},
"react-dom-server.node.development.js (NODE_DEV)": {
"size": 97092,
"gzip": 25688
"size": 97084,
"gzip": 25683
},
"react-dom-server.node.production.min.js (NODE_PROD)": {
"size": 15996,
"gzip": 6238
"size": 15988,
"gzip": 6235
},
"react-art.development.js (UMD_DEV)": {
"size": 378408,
"gzip": 83017
"size": 376390,
"gzip": 82374
},
"react-art.production.min.js (UMD_PROD)": {
"size": 82432,
"gzip": 25592
"size": 83413,
"gzip": 25876
},
"react-art.development.js (NODE_DEV)": {
"size": 302763,
"gzip": 63846
"size": 300749,
"gzip": 63250
},
"react-art.production.min.js (NODE_PROD)": {
"size": 53897,
"gzip": 16869
"size": 54504,
"gzip": 17048
},
"ReactARTFiber-dev.js (FB_DEV)": {
"size": 301605,
"gzip": 63774
"size": 299674,
"gzip": 63165
},
"ReactARTFiber-prod.js (FB_PROD)": {
"size": 225472,
"gzip": 46455
"size": 224360,
"gzip": 46067
},
"ReactNativeFiber-dev.js (RN_DEV)": {
"size": 285948,
"gzip": 49225
"size": 286193,
"gzip": 49282
},
"ReactNativeFiber-prod.js (RN_PROD)": {
"size": 223676,
"gzip": 38463
"size": 223893,
"gzip": 38520
},
"react-test-renderer.development.js (NODE_DEV)": {
"size": 306451,
"gzip": 64239
"size": 304437,
"gzip": 63667
},
"ReactTestRendererFiber-dev.js (FB_DEV)": {
"size": 305254,
"gzip": 64170
"size": 303323,
"gzip": 63582
},
"react-test-renderer-shallow.development.js (NODE_DEV)": {
"size": 9364,
"gzip": 2335
},
"ReactShallowRenderer-dev.js (FB_DEV)": {
"size": 9020,
"gzip": 2254
"size": 9037,
"gzip": 2262
},
"react-noop-renderer.development.js (NODE_DEV)": {
"size": 295660,
"gzip": 61436
"size": 293646,
"gzip": 60851
},
"react-dom-server.development.js (UMD_DEV)": {
"size": 120897,
@@ -189,16 +189,16 @@
"gzip": 7520
},
"ReactNativeRTFiber-dev.js (RN_DEV)": {
"size": 217811,
"gzip": 36735
"size": 218056,
"gzip": 36811
},
"ReactNativeRTFiber-prod.js (RN_PROD)": {
"size": 165325,
"gzip": 27464
"size": 165542,
"gzip": 27540
},
"react-test-renderer.production.min.js (NODE_PROD)": {
"size": 55449,
"gzip": 17169
"size": 56061,
"gzip": 17256
},
"react-test-renderer-shallow.production.min.js (NODE_PROD)": {
"size": 4630,
@@ -209,20 +209,20 @@
"gzip": 4241
},
"react-reconciler.development.js (NODE_DEV)": {
"size": 281332,
"gzip": 58301
"size": 279318,
"gzip": 57691
},
"react-reconciler.production.min.js (NODE_PROD)": {
"size": 37658,
"gzip": 11762
"size": 38320,
"gzip": 11959
},
"ReactNativeCSFiber-dev.js (RN_DEV)": {
"size": 210232,
"gzip": 34986
"size": 210477,
"gzip": 35055
},
"ReactNativeCSFiber-prod.js (RN_PROD)": {
"size": 160320,
"gzip": 26275
"size": 160537,
"gzip": 26346
}
}
}