Skip to content

Commit

Permalink
Add ReactDOM.unstable_createSyncRoot
Browse files Browse the repository at this point in the history
- `ReactDOM.unstable_createRoot` creates a Concurrent Mode root.
- `ReactDOM.unstable_createSyncRoot` creates a Batched Mode root. It
does not support `createBatch`.
- `ReactDOM.render` creates a Legacy Mode root. It will eventually be
deprecated and possibly moved to a separate entry point, like
`react-dom/legacy`.
  • Loading branch information
acdlite committed Apr 29, 2019
1 parent e4fdd20 commit 5a627c4
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 107 deletions.
Expand Up @@ -661,6 +661,37 @@ describe('ReactDOMFiberAsync', () => {
});
});

describe('createSyncRoot', () => {
it('updates flush without yielding in the next event', () => {
const root = ReactDOM.unstable_createSyncRoot(container);

function Text(props) {
Scheduler.yieldValue(props.text);
return props.text;
}

root.render(
<React.Fragment>
<Text text="A" />
<Text text="B" />
<Text text="C" />
</React.Fragment>,
);

// Nothing should have rendered yet
expect(container.textContent).toEqual('');

// Everything should render immediately in the next event
expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
expect(container.textContent).toEqual('ABC');
});

it('does not support createBatch', () => {
const root = ReactDOM.unstable_createSyncRoot(container);
expect(root.createBatch).toBe(undefined);
});
});

describe('Disable yielding', () => {
beforeEach(() => {
jest.resetModules();
Expand Down
115 changes: 62 additions & 53 deletions packages/react-dom/src/client/ReactDOM.js
Expand Up @@ -53,7 +53,7 @@ import {
accumulateTwoPhaseDispatches,
accumulateDirectDispatches,
} from 'events/EventPropagators';
import {LegacyRoot, ConcurrentRoot} from 'shared/ReactRootTags';
import {LegacyRoot, ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';
import {has as hasInstance} from 'shared/ReactInstanceMap';
import ReactVersion from 'shared/ReactVersion';
import ReactSharedInternals from 'shared/ReactSharedInternals';
Expand Down Expand Up @@ -186,17 +186,15 @@ type Batch = FiberRootBatch & {
type Root = {
render(children: ReactNodeList, callback: ?() => mixed): Work,
unmount(callback: ?() => mixed): Work,
legacy_renderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
callback: ?() => mixed,
): Work,
createBatch(): Batch,

_internalRoot: FiberRoot,
};

function ReactBatch(root: ReactRoot) {
type RootWithBatchingAPI = Root & {
createBatch(): Batch,
};

function ReactBatch(root: Root) {
const expirationTime = computeUniqueAsyncExpiration();
this._expirationTime = expirationTime;
this._root = root;
Expand Down Expand Up @@ -363,11 +361,22 @@ ReactWork.prototype._onCommit = function(): void {
}
};

function ReactRoot(container: DOMContainer, tag: RootTag, hydrate: boolean) {
function ReactSyncRoot(
container: DOMContainer,
tag: RootTag,
hydrate: boolean,
) {
// Tag is either LegacyRoot or Concurrent Root
const root = createContainer(container, tag, hydrate);
this._internalRoot = root;
}
ReactRoot.prototype.render = function(

function ReactRoot(container: DOMContainer, hydrate: boolean) {
const root = createContainer(container, ConcurrentRoot, hydrate);
this._internalRoot = root;
}

ReactRoot.prototype.render = ReactSyncRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
Expand All @@ -383,22 +392,8 @@ ReactRoot.prototype.render = function(
updateContainer(children, root, null, work._onCommit);
return work;
};
ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'render');
}
if (callback !== null) {
work.then(callback);
}
updateContainer(null, root, null, work._onCommit);
return work;
};
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,

ReactRoot.prototype.unmount = ReactSyncRoot.prototype.unmount = function(
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
Expand All @@ -410,9 +405,11 @@ ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
if (callback !== null) {
work.then(callback);
}
updateContainer(children, root, parentComponent, work._onCommit);
updateContainer(null, root, null, work._onCommit);
return work;
};

// Sync roots cannot create batches. Only concurrent ones.
ReactRoot.prototype.createBatch = function(): Batch {
const batch = new ReactBatch(this);
const expirationTime = batch._expirationTime;
Expand Down Expand Up @@ -529,7 +526,9 @@ function legacyCreateRootFromDOMContainer(
);
}
}
return new ReactRoot(container, LegacyRoot, shouldHydrate);

// Legacy roots are not batched.
return new ReactSyncRoot(container, LegacyRoot, shouldHydrate);
}

function legacyRenderSubtreeIntoContainer(
Expand All @@ -541,56 +540,44 @@ function legacyRenderSubtreeIntoContainer(
) {
if (__DEV__) {
topLevelUpdateWarnings(container);
warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
}

// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
let root: Root = (container._reactRootContainer: any);
let fiberRoot;
if (!root) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
unbatchedUpdates(() => {
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(root._internalRoot);
return getPublicRootInstance(fiberRoot);
}

function createPortal(
Expand Down Expand Up @@ -800,6 +787,7 @@ const ReactDOM: Object = {
flushSync: flushSync,

unstable_createRoot: createRoot,
unstable_createSyncRoot: createSyncRoot,
unstable_flushControlled: flushControlled,

__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
Expand All @@ -826,7 +814,24 @@ type RootOptions = {
hydrate?: boolean,
};

function createRoot(container: DOMContainer, options?: RootOptions): ReactRoot {
function createRoot(
container: DOMContainer,
options?: RootOptions,
): RootWithBatchingAPI {
const functionName = enableStableConcurrentModeAPIs
? 'createRoot'
: 'unstable_createRoot';
invariant(
isValidContainer(container),
'%s(...): Target container is not a DOM element.',
functionName,
);
warnIfReactDOMContainerInDEV(container);
const hydrate = options != null && options.hydrate === true;
return new ReactRoot(container, hydrate);
}

function createSyncRoot(container: DOMContainer, options?: RootOptions): Root {
const functionName = enableStableConcurrentModeAPIs
? 'createRoot'
: 'unstable_createRoot';
Expand All @@ -835,6 +840,12 @@ function createRoot(container: DOMContainer, options?: RootOptions): ReactRoot {
'%s(...): Target container is not a DOM element.',
functionName,
);
warnIfReactDOMContainerInDEV(container);
const hydrate = options != null && options.hydrate === true;
return new ReactSyncRoot(container, BatchedRoot, hydrate);
}

function warnIfReactDOMContainerInDEV(container) {
if (__DEV__) {
warningWithoutStack(
!container._reactRootContainer,
Expand All @@ -844,13 +855,11 @@ function createRoot(container: DOMContainer, options?: RootOptions): ReactRoot {
);
container._reactHasBeenPassedToCreateRootDEV = true;
}
const hydrate = options != null && options.hydrate === true;
return new ReactRoot(container, ConcurrentRoot, hydrate);
}

if (enableStableConcurrentModeAPIs) {
ReactDOM.createRoot = createRoot;
ReactDOM.unstable_createRoot = undefined;
ReactDOM.createSyncRoot = createSyncRoot;
}

const foundDevTools = injectIntoDevTools({
Expand Down

0 comments on commit 5a627c4

Please sign in to comment.