Skip to content
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

Add ReactDOM.unstable_createSyncRoot #15504

Merged
merged 2 commits into from
May 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
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
130 changes: 71 additions & 59 deletions packages/react-dom/src/client/ReactDOM.js
Original file line number Diff line number Diff line change
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 @@ -159,11 +159,11 @@ setRestoreImplementation(restoreControlledState);

export type DOMContainer =
| (Element & {
_reactRootContainer: ?Root,
_reactRootContainer: ?(_ReactRoot | _ReactSyncRoot),
_reactHasBeenPassedToCreateRootDEV: ?boolean,
})
| (Document & {
_reactRootContainer: ?Root,
_reactRootContainer: ?(_ReactRoot | _ReactSyncRoot),
_reactHasBeenPassedToCreateRootDEV: ?boolean,
});

Expand All @@ -175,28 +175,26 @@ type Batch = FiberRootBatch & {
// The ReactRoot constructor is hoisted but the prototype methods are not. If
// we move ReactRoot to be above ReactBatch, the inverse error occurs.
// $FlowFixMe Hoisting issue.
_root: Root,
_root: _ReactRoot | _ReactSyncRoot,
_hasChildren: boolean,
_children: ReactNodeList,

_callbacks: Array<() => mixed> | null,
_didComplete: boolean,
};

type Root = {
type _ReactSyncRoot = {
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 _ReactRoot = _ReactSyncRoot & {
createBatch(): Batch,
};

function ReactBatch(root: _ReactRoot | _ReactSyncRoot) {
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 @@ -492,7 +489,7 @@ let warnedAboutHydrateAPI = false;
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
): _ReactSyncRoot {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
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 root: _ReactSyncRoot = (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,10 @@ type RootOptions = {
hydrate?: boolean,
};

function createRoot(container: DOMContainer, options?: RootOptions): ReactRoot {
function createRoot(
container: DOMContainer,
options?: RootOptions,
): _ReactRoot {
const functionName = enableStableConcurrentModeAPIs
? 'createRoot'
: 'unstable_createRoot';
Expand All @@ -835,6 +826,29 @@ 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 ReactRoot(container, hydrate);
}

function createSyncRoot(
container: DOMContainer,
options?: RootOptions,
): _ReactSyncRoot {
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 ReactSyncRoot(container, BatchedRoot, hydrate);
}

function warnIfReactDOMContainerInDEV(container) {
if (__DEV__) {
warningWithoutStack(
!container._reactRootContainer,
Expand All @@ -844,13 +858,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
Loading