-
Notifications
You must be signed in to change notification settings - Fork 47.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API for prerendering a top-level update and deferring the commit #11232
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
'use strict'; | ||
|
||
import type {ReactNodeList} from 'ReactTypes'; | ||
import type {ExpirationTime} from 'ReactFiberExpirationTime'; | ||
|
||
require('checkReact'); | ||
var DOMNamespaces = require('DOMNamespaces'); | ||
|
@@ -757,8 +758,27 @@ function createPortal( | |
return ReactPortal.createPortal(children, container, null, key); | ||
} | ||
|
||
type WorkNode = { | ||
commit(): void, | ||
|
||
_reactRootContainer: *, | ||
_expirationTime: ExpirationTime, | ||
}; | ||
|
||
function Work(root: *, expirationTime: ExpirationTime) { | ||
this._reactRootContainer = root; | ||
this._expirationTime = expirationTime; | ||
} | ||
Work.prototype.commit = function() { | ||
const root = this._reactRootContainer; | ||
const expirationTime = this._expirationTime; | ||
DOMRenderer.unblockRoot(root, expirationTime); | ||
DOMRenderer.flushRoot(root, expirationTime); | ||
}; | ||
|
||
type ReactRootNode = { | ||
render(children: ReactNodeList, callback: ?() => mixed): void, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this new API really should return a Thenable since that's what people have been asking for with these callback APIs. I noticed because the prerender API doesn't have it. |
||
prerender(children: ReactNodeList): WorkNode, | ||
unmount(callback: ?() => mixed): void, | ||
|
||
_reactRootContainer: *, | ||
|
@@ -779,6 +799,11 @@ ReactRoot.prototype.render = function( | |
const root = this._reactRootContainer; | ||
DOMRenderer.updateContainer(children, root, null, callback); | ||
}; | ||
ReactRoot.prototype.prerender = function(children: ReactNodeList): WorkNode { | ||
const root = this._reactRootContainer; | ||
const expirationTime = DOMRenderer.updateRoot(children, root, null, null); | ||
return new Work(root, expirationTime); | ||
}; | ||
ReactRoot.prototype.unmount = function(callback) { | ||
const root = this._reactRootContainer; | ||
DOMRenderer.updateContainer(null, root, null, callback); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
import type {Fiber} from 'ReactFiber'; | ||
import type {FiberRoot} from 'ReactFiberRoot'; | ||
import type {ReactNodeList} from 'ReactTypes'; | ||
import type {ExpirationTime} from 'ReactFiberExpirationTime'; | ||
|
||
var ReactFeatureFlags = require('ReactFeatureFlags'); | ||
var { | ||
|
@@ -226,6 +227,14 @@ export type Reconciler<C, I, TI> = { | |
parentComponent: ?React$Component<any, any>, | ||
callback: ?Function, | ||
): void, | ||
updateRoot( | ||
element: ReactNodeList, | ||
container: OpaqueRoot, | ||
parentComponent: ?React$Component<any, any>, | ||
callback: ?Function, | ||
): ExpirationTime, | ||
unblockRoot(root: OpaqueRoot, expirationTime: ExpirationTime): void, | ||
flushRoot(root: OpaqueRoot, expirationTime: ExpirationTime): void, | ||
batchedUpdates<A>(fn: () => A): A, | ||
unbatchedUpdates<A>(fn: () => A): A, | ||
flushSync<A>(fn: () => A): A, | ||
|
@@ -266,6 +275,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>( | |
computeAsyncExpiration, | ||
computeExpirationForFiber, | ||
scheduleWork, | ||
expireWork, | ||
batchedUpdates, | ||
unbatchedUpdates, | ||
flushSync, | ||
|
@@ -275,8 +285,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>( | |
function scheduleTopLevelUpdate( | ||
current: Fiber, | ||
element: ReactNodeList, | ||
isBlocked: boolean, | ||
callback: ?Function, | ||
) { | ||
): ExpirationTime { | ||
if (__DEV__) { | ||
if ( | ||
ReactDebugCurrentFiber.phase === 'render' && | ||
|
@@ -323,15 +334,15 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>( | |
|
||
const update = { | ||
expirationTime, | ||
partialState: {element}, | ||
partialState: {element, isBlocked}, | ||
callback, | ||
isReplace: false, | ||
isForced: false, | ||
nextCallback: null, | ||
next: null, | ||
}; | ||
insertUpdateIntoFiber(current, update); | ||
scheduleWork(current, expirationTime); | ||
return expirationTime; | ||
} | ||
|
||
return { | ||
|
@@ -367,7 +378,61 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>( | |
container.pendingContext = context; | ||
} | ||
|
||
scheduleTopLevelUpdate(current, element, callback); | ||
scheduleTopLevelUpdate(current, element, false, callback); | ||
}, | ||
|
||
// Like updateContainer, but blocks the root from committing. Returns an | ||
// expiration time. | ||
// TODO: Can this be unified with updateContainer? Or is it incompatible | ||
// with the existing semantics of ReactDOM.render? | ||
updateRoot( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to be stricter with naming of these things. I'm not sure what makes most sense here since Root is otherwise just an internal concept. I think this still make sense to refer to Container here. From the outside perspective there is no root. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Especially since the comment eve says that this might not even be a root. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't we just add an API called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also I think that comment is outdated post-portals, but I wasn't sure enough to delete it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have the createRoot for persistent updates, HostRoot fiber and createRoot in the ReactDOM renderer. Neither of which are the same and neither is this. |
||
element: ReactNodeList, | ||
container: OpaqueRoot, | ||
parentComponent: ?React$Component<any, any>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is an new API we don't have to support legacy apis like renderSubtreeIntoContainer. |
||
callback: ?Function, | ||
): ExpirationTime { | ||
// TODO: If this is a nested container, this won't be the root. | ||
const current = container.current; | ||
|
||
if (__DEV__) { | ||
if (ReactFiberInstrumentation.debugTool) { | ||
if (current.alternate === null) { | ||
ReactFiberInstrumentation.debugTool.onMountContainer(container); | ||
} else if (element === null) { | ||
ReactFiberInstrumentation.debugTool.onUnmountContainer(container); | ||
} else { | ||
ReactFiberInstrumentation.debugTool.onUpdateContainer(container); | ||
} | ||
} | ||
} | ||
|
||
const context = getContextForSubtree(parentComponent); | ||
if (container.context === null) { | ||
container.context = context; | ||
} else { | ||
container.pendingContext = context; | ||
} | ||
|
||
return scheduleTopLevelUpdate(current, element, true, callback); | ||
}, | ||
|
||
unblockRoot(root: OpaqueRoot, expirationTime: ExpirationTime) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need these to be separate? This is inherent complexity to support where as the current API only lets you do both at once. I think we should try again to simply defer all commits until explicitly called since we have so many cases where that seems to matter. And the legacy mode is simply a wrapper that does that eagerly. In that case that's the normal mode so the concept of being blocked doesn't make sense and unblocking doesn't as well. |
||
const current = root.current; | ||
const partialState = {isBlocked: false}; | ||
const update = { | ||
expirationTime, | ||
partialState, | ||
callback: null, | ||
isReplace: false, | ||
isForced: false, | ||
next: null, | ||
}; | ||
insertUpdateIntoFiber(current, update); | ||
scheduleWork(current, expirationTime); | ||
}, | ||
|
||
flushRoot(root: OpaqueRoot, expirationTime: ExpirationTime) { | ||
expireWork(root, expirationTime); | ||
}, | ||
|
||
batchedUpdates, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we use an existential type here instead of something explicit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I was lazy and didn't want to import it :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:D