Skip to content

Commit

Permalink
ReactDOM.createRoot creates an async root
Browse files Browse the repository at this point in the history
Makes createRoot the opt-in API for async updates. Now we don't have
to check the top-level element to see if it's an async container.
  • Loading branch information
acdlite committed Dec 4, 2017
1 parent d7f6ece commit 67a12fd
Show file tree
Hide file tree
Showing 12 changed files with 60 additions and 61 deletions.
6 changes: 5 additions & 1 deletion packages/react-cs-renderer/src/ReactNativeCS.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,11 @@ const ReactCS = CSStatefulComponent({
let container = {
pendingChild: null,
};
let root = ReactNativeCSFiberRenderer.createContainer(container, false);
let root = ReactNativeCSFiberRenderer.createContainer(
container,
false,
false,
);
return {root, container};
},
render({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,26 +69,18 @@ describe('ReactDOMFiberAsync', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
container = document.createElement('div');
ReactFeatureFlags.enableAsyncSubtreeAPI = true;
ReactFeatureFlags.enableCreateRoot = true;
ReactDOM = require('react-dom');
});

it('AsyncComponent at the root makes the entire tree async', () => {
ReactDOM.render(
<AsyncComponent>
<div>Hi</div>
</AsyncComponent>,
container,
);
it('createRoot makes the entire tree async', () => {
const root = ReactDOM.createRoot(container);
root.render(<div>Hi</div>);
expect(container.textContent).toEqual('');
jest.runAllTimers();
expect(container.textContent).toEqual('Hi');

ReactDOM.render(
<AsyncComponent>
<div>Bye</div>
</AsyncComponent>,
container,
);
root.render(<div>Bye</div>);
expect(container.textContent).toEqual('Hi');
jest.runAllTimers();
expect(container.textContent).toEqual('Bye');
Expand All @@ -104,12 +96,8 @@ describe('ReactDOMFiberAsync', () => {
}
}

ReactDOM.render(
<AsyncComponent>
<Component />
</AsyncComponent>,
container,
);
const root = ReactDOM.createRoot(container);
root.render(<Component />);
expect(container.textContent).toEqual('');
jest.runAllTimers();
expect(container.textContent).toEqual('0');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,17 @@ describe('ReactDOMRoot', () => {
it('renders children', () => {
const root = ReactDOM.createRoot(container);
root.render(<div>Hi</div>);
flush();
expect(container.textContent).toEqual('Hi');
});

it('unmounts children', () => {
const root = ReactDOM.createRoot(container);
root.render(<div>Hi</div>);
flush();
expect(container.textContent).toEqual('Hi');
root.unmount();
flush();
expect(container.textContent).toEqual('');
});

Expand Down Expand Up @@ -138,6 +141,7 @@ describe('ReactDOMRoot', () => {
<span />
</div>,
);
flush();
if (__DEV__) {
expect(console.error.calls.count()).toBe(0);
}
Expand All @@ -151,6 +155,7 @@ describe('ReactDOMRoot', () => {
<span />
</div>,
);
flush();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toMatch('Extra attributes');
Expand All @@ -167,13 +172,15 @@ describe('ReactDOMRoot', () => {
<span>d</span>
</div>,
);
flush();
expect(container.textContent).toEqual('abcd');
root.render(
<div>
<span>d</span>
<span>c</span>
</div>,
);
flush();
expect(container.textContent).toEqual('abdc');
});

Expand Down
11 changes: 6 additions & 5 deletions packages/react-dom/src/client/ReactDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,8 @@ type Root = {
_internalRoot: FiberRoot,
};

function ReactRoot(container: Container, hydrate: boolean) {
const root = DOMRenderer.createContainer(container, hydrate);
function ReactRoot(container: Container, isAsync: boolean, hydrate: boolean) {
const root = DOMRenderer.createContainer(container, isAsync, hydrate);
this._internalRoot = root;
}
ReactRoot.prototype.render = function(
Expand Down Expand Up @@ -1031,8 +1031,9 @@ function legacyCreateRootFromDOMContainer(
);
}
}
const root: Root = new ReactRoot(container, shouldHydrate);
return root;
// Legacy roots are not async by default.
const isAsync = false;
return new ReactRoot(container, isAsync, shouldHydrate);
}

function legacyRenderSubtreeIntoContainer(
Expand Down Expand Up @@ -1300,7 +1301,7 @@ if (enableCreateRoot) {
options?: RootOptions,
): ReactRoot {
const hydrate = options != null && options.hydrate === true;
return new ReactRoot(container, hydrate);
return new ReactRoot(container, true, hydrate);
};
}

Expand Down
6 changes: 5 additions & 1 deletion packages/react-native-renderer/src/ReactNativeRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ const ReactNativeRenderer: ReactNativeType = {
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = ReactNativeFiberRenderer.createContainer(containerTag, false);
root = ReactNativeFiberRenderer.createContainer(
containerTag,
false,
false,
);
roots.set(containerTag, root);
}
ReactNativeFiberRenderer.updateContainer(element, root, null, callback);
Expand Down
4 changes: 2 additions & 2 deletions packages/react-noop-renderer/src/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ var ReactNoop = {
if (!root) {
const container = {rootID: rootID, children: []};
rootContainers.set(rootID, container);
root = NoopRenderer.createContainer(container, false);
root = NoopRenderer.createContainer(container, true, false);
roots.set(rootID, root);
}
NoopRenderer.updateContainer(element, root, null, callback);
Expand All @@ -361,7 +361,7 @@ var ReactNoop = {
if (!root) {
const container = {rootID: rootID, children: []};
rootContainers.set(rootID, container);
root = PersistentNoopRenderer.createContainer(container, false);
root = PersistentNoopRenderer.createContainer(container, true, false);
persistentRoots.set(rootID, root);
}
PersistentNoopRenderer.updateContainer(element, root, null, callback);
Expand Down
8 changes: 4 additions & 4 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
import getComponentName from 'shared/getComponentName';

import {NoWork} from './ReactFiberExpirationTime';
import {NoContext} from './ReactTypeOfInternalContext';
import {NoContext, AsyncUpdates} from './ReactTypeOfInternalContext';

if (__DEV__) {
var hasBadMapPolyfill = false;
Expand Down Expand Up @@ -288,9 +288,9 @@ export function createWorkInProgress(
return workInProgress;
}

export function createHostRootFiber(): Fiber {
const fiber = createFiber(HostRoot, null, NoContext);
return fiber;
export function createHostRootFiber(isAsync): Fiber {
const internalContextTag = isAsync ? AsyncUpdates : NoContext;
return createFiber(HostRoot, null, null, internalContextTag);
}

export function createFiberFromElement(
Expand Down
37 changes: 12 additions & 25 deletions packages/react-reconciler/src/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type {FiberRoot} from './ReactFiberRoot';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {ExpirationTime} from './ReactFiberExpirationTime';

import {enableAsyncSubtreeAPI} from 'shared/ReactFeatureFlags';
import {
findCurrentHostFiber,
findCurrentHostFiberWithNoPortals,
Expand Down Expand Up @@ -233,7 +232,11 @@ type DevToolsConfig<I, TI> = {|
|};

export type Reconciler<C, I, TI> = {
createContainer(containerInfo: C, hydrate: boolean): OpaqueRoot,
createContainer(
containerInfo: C,
isAsync: boolean,
hydrate: boolean,
): OpaqueRoot,
updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
Expand Down Expand Up @@ -288,7 +291,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
const {getPublicInstance} = config;

const {
computeAsyncExpiration,
computeUniqueAsyncExpiration,
computeExpirationForFiber,
scheduleWork,
Expand All @@ -300,25 +302,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
deferredUpdates,
} = ReactFiberScheduler(config);

function computeRootExpirationTime(current, element) {
let expirationTime;
// Check if the top-level element is an async wrapper component. If so,
// treat updates to the root as async. This is a bit weird but lets us
// avoid a separate `renderAsync` API.
if (
enableAsyncSubtreeAPI &&
element != null &&
(element: any).type != null &&
(element: any).type.prototype != null &&
(element: any).type.prototype.unstable_isAsyncReactComponent === true
) {
expirationTime = computeAsyncExpiration();
} else {
expirationTime = computeExpirationForFiber(current);
}
return expirationTime;
}

function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
Expand Down Expand Up @@ -408,8 +391,12 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
}

return {
createContainer(containerInfo: C, hydrate: boolean): OpaqueRoot {
return createFiberRoot(containerInfo, hydrate);
createContainer(
containerInfo: C,
isAsync: boolean,
hydrate: boolean,
): OpaqueRoot {
return createFiberRoot(containerInfo, isAsync, hydrate);
},

updateContainer(
Expand All @@ -419,7 +406,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
callback: ?Function,
): ExpirationTime {
const current = container.current;
const expirationTime = computeRootExpirationTime(current, element);
const expirationTime = computeExpirationForFiber(current);
return updateContainerAtExpirationTime(
element,
container,
Expand Down
3 changes: 2 additions & 1 deletion packages/react-reconciler/src/ReactFiberRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ export type FiberRoot = {

export function createFiberRoot(
containerInfo: any,
isAsync: boolean,
hydrate: boolean,
): FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber();
const uninitializedFiber = createHostRootFiber(isAsync);
const root = {
current: uninitializedFiber,
containerInfo: containerInfo,
Expand Down
1 change: 0 additions & 1 deletion packages/react-reconciler/src/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
}

return {
computeAsyncExpiration,
computeExpirationForFiber,
scheduleWork,
requestWork,
Expand Down
6 changes: 5 additions & 1 deletion packages/react-rt-renderer/src/ReactNativeRT.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ const ReactNativeRTFiber: ReactNativeRTType = {
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = ReactNativeRTFiberRenderer.createContainer(containerTag, false);
root = ReactNativeRTFiberRenderer.createContainer(
containerTag,
false,
false,
);
roots.set(containerTag, root);
}
ReactNativeRTFiberRenderer.updateContainer(element, root, null, callback);
Expand Down
6 changes: 5 additions & 1 deletion packages/react-test-renderer/src/ReactTestRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,11 @@ const ReactTestRendererFiber = {
createNodeMock,
tag: 'CONTAINER',
};
let root: FiberRoot | null = TestRenderer.createContainer(container, false);
let root: FiberRoot | null = TestRenderer.createContainer(
container,
false,
false,
);
invariant(root != null, 'something went wrong');
TestRenderer.updateContainer(element, root, null, null);

Expand Down

0 comments on commit 67a12fd

Please sign in to comment.