1094 lines
29 KiB
JavaScript
1094 lines
29 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
/**
|
|
* This is a renderer of React that doesn't have a render target output.
|
|
* It is useful to demonstrate the internals of the reconciler in isolation
|
|
* and for testing semantics of reconciliation separate from the host
|
|
* environment.
|
|
*/
|
|
|
|
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
|
import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
|
|
import type {ReactNodeList} from 'shared/ReactTypes';
|
|
import type {RootTag} from 'react-reconciler/src/ReactRootTags';
|
|
|
|
import * as Scheduler from 'scheduler/unstable_mock';
|
|
import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
|
|
import {
|
|
ConcurrentRoot,
|
|
BlockingRoot,
|
|
LegacyRoot,
|
|
} from 'react-reconciler/src/ReactRootTags';
|
|
|
|
type Container = {
|
|
rootID: string,
|
|
children: Array<Instance | TextInstance>,
|
|
pendingChildren: Array<Instance | TextInstance>,
|
|
...
|
|
};
|
|
type Props = {
|
|
prop: any,
|
|
hidden: boolean,
|
|
children?: mixed,
|
|
bottom?: null | number,
|
|
left?: null | number,
|
|
right?: null | number,
|
|
top?: null | number,
|
|
...
|
|
};
|
|
type Instance = {|
|
|
type: string,
|
|
id: number,
|
|
children: Array<Instance | TextInstance>,
|
|
text: string | null,
|
|
prop: any,
|
|
hidden: boolean,
|
|
context: HostContext,
|
|
|};
|
|
type TextInstance = {|
|
|
text: string,
|
|
id: number,
|
|
hidden: boolean,
|
|
context: HostContext,
|
|
|};
|
|
type HostContext = Object;
|
|
|
|
const NO_CONTEXT = {};
|
|
const UPPERCASE_CONTEXT = {};
|
|
const UPDATE_SIGNAL = {};
|
|
if (__DEV__) {
|
|
Object.freeze(NO_CONTEXT);
|
|
Object.freeze(UPDATE_SIGNAL);
|
|
}
|
|
|
|
function createReactNoop(reconciler: Function, useMutation: boolean) {
|
|
let instanceCounter = 0;
|
|
let hostDiffCounter = 0;
|
|
let hostUpdateCounter = 0;
|
|
let hostCloneCounter = 0;
|
|
|
|
function appendChildToContainerOrInstance(
|
|
parentInstance: Container | Instance,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
const index = parentInstance.children.indexOf(child);
|
|
if (index !== -1) {
|
|
parentInstance.children.splice(index, 1);
|
|
}
|
|
parentInstance.children.push(child);
|
|
}
|
|
|
|
function appendChildToContainer(
|
|
parentInstance: Container,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
if (typeof parentInstance.rootID !== 'string') {
|
|
// Some calls to this aren't typesafe.
|
|
// This helps surface mistakes in tests.
|
|
throw new Error(
|
|
'appendChildToContainer() first argument is not a container.',
|
|
);
|
|
}
|
|
appendChildToContainerOrInstance(parentInstance, child);
|
|
}
|
|
|
|
function appendChild(
|
|
parentInstance: Instance,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
if (typeof (parentInstance: any).rootID === 'string') {
|
|
// Some calls to this aren't typesafe.
|
|
// This helps surface mistakes in tests.
|
|
throw new Error('appendChild() first argument is not an instance.');
|
|
}
|
|
appendChildToContainerOrInstance(parentInstance, child);
|
|
}
|
|
|
|
function insertInContainerOrInstanceBefore(
|
|
parentInstance: Container | Instance,
|
|
child: Instance | TextInstance,
|
|
beforeChild: Instance | TextInstance,
|
|
): void {
|
|
const index = parentInstance.children.indexOf(child);
|
|
if (index !== -1) {
|
|
parentInstance.children.splice(index, 1);
|
|
}
|
|
const beforeIndex = parentInstance.children.indexOf(beforeChild);
|
|
if (beforeIndex === -1) {
|
|
throw new Error('This child does not exist.');
|
|
}
|
|
parentInstance.children.splice(beforeIndex, 0, child);
|
|
}
|
|
|
|
function insertInContainerBefore(
|
|
parentInstance: Container,
|
|
child: Instance | TextInstance,
|
|
beforeChild: Instance | TextInstance,
|
|
) {
|
|
if (typeof parentInstance.rootID !== 'string') {
|
|
// Some calls to this aren't typesafe.
|
|
// This helps surface mistakes in tests.
|
|
throw new Error(
|
|
'insertInContainerBefore() first argument is not a container.',
|
|
);
|
|
}
|
|
insertInContainerOrInstanceBefore(parentInstance, child, beforeChild);
|
|
}
|
|
|
|
function insertBefore(
|
|
parentInstance: Instance,
|
|
child: Instance | TextInstance,
|
|
beforeChild: Instance | TextInstance,
|
|
) {
|
|
if (typeof (parentInstance: any).rootID === 'string') {
|
|
// Some calls to this aren't typesafe.
|
|
// This helps surface mistakes in tests.
|
|
throw new Error('insertBefore() first argument is not an instance.');
|
|
}
|
|
insertInContainerOrInstanceBefore(parentInstance, child, beforeChild);
|
|
}
|
|
|
|
function clearContainer(container: Container): void {
|
|
container.children.splice(0);
|
|
}
|
|
|
|
function removeChildFromContainerOrInstance(
|
|
parentInstance: Container | Instance,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
const index = parentInstance.children.indexOf(child);
|
|
if (index === -1) {
|
|
throw new Error('This child does not exist.');
|
|
}
|
|
parentInstance.children.splice(index, 1);
|
|
}
|
|
|
|
function removeChildFromContainer(
|
|
parentInstance: Container,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
if (typeof parentInstance.rootID !== 'string') {
|
|
// Some calls to this aren't typesafe.
|
|
// This helps surface mistakes in tests.
|
|
throw new Error(
|
|
'removeChildFromContainer() first argument is not a container.',
|
|
);
|
|
}
|
|
removeChildFromContainerOrInstance(parentInstance, child);
|
|
}
|
|
|
|
function removeChild(
|
|
parentInstance: Instance,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
if (typeof (parentInstance: any).rootID === 'string') {
|
|
// Some calls to this aren't typesafe.
|
|
// This helps surface mistakes in tests.
|
|
throw new Error('removeChild() first argument is not an instance.');
|
|
}
|
|
removeChildFromContainerOrInstance(parentInstance, child);
|
|
}
|
|
|
|
function cloneInstance(
|
|
instance: Instance,
|
|
updatePayload: null | Object,
|
|
type: string,
|
|
oldProps: Props,
|
|
newProps: Props,
|
|
internalInstanceHandle: Object,
|
|
keepChildren: boolean,
|
|
recyclableInstance: null | Instance,
|
|
): Instance {
|
|
const clone = {
|
|
id: instance.id,
|
|
type: type,
|
|
children: keepChildren ? instance.children : [],
|
|
text: shouldSetTextContent(type, newProps)
|
|
? computeText((newProps.children: any) + '', instance.context)
|
|
: null,
|
|
prop: newProps.prop,
|
|
hidden: !!newProps.hidden,
|
|
context: instance.context,
|
|
};
|
|
Object.defineProperty(clone, 'id', {
|
|
value: clone.id,
|
|
enumerable: false,
|
|
});
|
|
Object.defineProperty(clone, 'text', {
|
|
value: clone.text,
|
|
enumerable: false,
|
|
});
|
|
Object.defineProperty(clone, 'context', {
|
|
value: clone.context,
|
|
enumerable: false,
|
|
});
|
|
hostCloneCounter++;
|
|
return clone;
|
|
}
|
|
|
|
function shouldSetTextContent(type: string, props: Props): boolean {
|
|
if (type === 'errorInBeginPhase') {
|
|
throw new Error('Error in host config.');
|
|
}
|
|
return (
|
|
typeof props.children === 'string' || typeof props.children === 'number'
|
|
);
|
|
}
|
|
|
|
function computeText(rawText, hostContext) {
|
|
return hostContext === UPPERCASE_CONTEXT ? rawText.toUpperCase() : rawText;
|
|
}
|
|
|
|
const sharedHostConfig = {
|
|
getRootHostContext() {
|
|
return NO_CONTEXT;
|
|
},
|
|
|
|
getChildHostContext(
|
|
parentHostContext: HostContext,
|
|
type: string,
|
|
rootcontainerInstance: Container,
|
|
) {
|
|
if (type === 'uppercase') {
|
|
return UPPERCASE_CONTEXT;
|
|
}
|
|
return NO_CONTEXT;
|
|
},
|
|
|
|
getPublicInstance(instance) {
|
|
return instance;
|
|
},
|
|
|
|
createInstance(
|
|
type: string,
|
|
props: Props,
|
|
rootContainerInstance: Container,
|
|
hostContext: HostContext,
|
|
): Instance {
|
|
if (type === 'errorInCompletePhase') {
|
|
throw new Error('Error in host config.');
|
|
}
|
|
const inst = {
|
|
id: instanceCounter++,
|
|
type: type,
|
|
children: [],
|
|
text: shouldSetTextContent(type, props)
|
|
? computeText((props.children: any) + '', hostContext)
|
|
: null,
|
|
prop: props.prop,
|
|
hidden: !!props.hidden,
|
|
context: hostContext,
|
|
};
|
|
// Hide from unit tests
|
|
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
|
|
Object.defineProperty(inst, 'text', {
|
|
value: inst.text,
|
|
enumerable: false,
|
|
});
|
|
Object.defineProperty(inst, 'context', {
|
|
value: inst.context,
|
|
enumerable: false,
|
|
});
|
|
return inst;
|
|
},
|
|
|
|
appendInitialChild(
|
|
parentInstance: Instance,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
parentInstance.children.push(child);
|
|
},
|
|
|
|
finalizeInitialChildren(
|
|
domElement: Instance,
|
|
type: string,
|
|
props: Props,
|
|
): boolean {
|
|
return false;
|
|
},
|
|
|
|
prepareUpdate(
|
|
instance: Instance,
|
|
type: string,
|
|
oldProps: Props,
|
|
newProps: Props,
|
|
): null | {...} {
|
|
if (type === 'errorInCompletePhase') {
|
|
throw new Error('Error in host config.');
|
|
}
|
|
if (oldProps === null) {
|
|
throw new Error('Should have old props');
|
|
}
|
|
if (newProps === null) {
|
|
throw new Error('Should have new props');
|
|
}
|
|
hostDiffCounter++;
|
|
return UPDATE_SIGNAL;
|
|
},
|
|
|
|
shouldSetTextContent,
|
|
|
|
createTextInstance(
|
|
text: string,
|
|
rootContainerInstance: Container,
|
|
hostContext: Object,
|
|
internalInstanceHandle: Object,
|
|
): TextInstance {
|
|
if (hostContext === UPPERCASE_CONTEXT) {
|
|
text = text.toUpperCase();
|
|
}
|
|
const inst = {
|
|
text: text,
|
|
id: instanceCounter++,
|
|
hidden: false,
|
|
context: hostContext,
|
|
};
|
|
// Hide from unit tests
|
|
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
|
|
Object.defineProperty(inst, 'context', {
|
|
value: inst.context,
|
|
enumerable: false,
|
|
});
|
|
return inst;
|
|
},
|
|
|
|
scheduleTimeout: setTimeout,
|
|
cancelTimeout: clearTimeout,
|
|
noTimeout: -1,
|
|
|
|
prepareForCommit(): null | Object {
|
|
return null;
|
|
},
|
|
|
|
resetAfterCommit(): void {},
|
|
|
|
now: Scheduler.unstable_now,
|
|
|
|
isPrimaryRenderer: true,
|
|
warnsIfNotActing: true,
|
|
supportsHydration: false,
|
|
|
|
DEPRECATED_mountResponderInstance(): void {
|
|
// NO-OP
|
|
},
|
|
|
|
DEPRECATED_unmountResponderInstance(): void {
|
|
// NO-OP
|
|
},
|
|
|
|
getFundamentalComponentInstance(fundamentalInstance): Instance {
|
|
const {impl, props, state} = fundamentalInstance;
|
|
return impl.getInstance(null, props, state);
|
|
},
|
|
|
|
mountFundamentalComponent(fundamentalInstance): void {
|
|
const {impl, instance, props, state} = fundamentalInstance;
|
|
const onMount = impl.onUpdate;
|
|
if (onMount !== undefined) {
|
|
onMount(null, instance, props, state);
|
|
}
|
|
},
|
|
|
|
shouldUpdateFundamentalComponent(fundamentalInstance): boolean {
|
|
const {impl, instance, prevProps, props, state} = fundamentalInstance;
|
|
const shouldUpdate = impl.shouldUpdate;
|
|
if (shouldUpdate !== undefined) {
|
|
return shouldUpdate(null, instance, prevProps, props, state);
|
|
}
|
|
return true;
|
|
},
|
|
|
|
updateFundamentalComponent(fundamentalInstance): void {
|
|
const {impl, instance, prevProps, props, state} = fundamentalInstance;
|
|
const onUpdate = impl.onUpdate;
|
|
if (onUpdate !== undefined) {
|
|
onUpdate(null, instance, prevProps, props, state);
|
|
}
|
|
},
|
|
|
|
unmountFundamentalComponent(fundamentalInstance): void {
|
|
const {impl, instance, props, state} = fundamentalInstance;
|
|
const onUnmount = impl.onUnmount;
|
|
if (onUnmount !== undefined) {
|
|
onUnmount(null, instance, props, state);
|
|
}
|
|
},
|
|
|
|
cloneFundamentalInstance(fundamentalInstance): Instance {
|
|
const instance = fundamentalInstance.instance;
|
|
return {
|
|
children: [],
|
|
text: instance.text,
|
|
type: instance.type,
|
|
prop: instance.prop,
|
|
id: instance.id,
|
|
context: instance.context,
|
|
hidden: instance.hidden,
|
|
};
|
|
},
|
|
|
|
getInstanceFromNode() {
|
|
throw new Error('Not yet implemented.');
|
|
},
|
|
|
|
removeInstanceEventHandles(instance: any): void {
|
|
// NO-OP
|
|
},
|
|
|
|
beforeActiveInstanceBlur() {
|
|
// NO-OP
|
|
},
|
|
|
|
afterActiveInstanceBlur() {
|
|
// NO-OP
|
|
},
|
|
|
|
preparePortalMount() {
|
|
// NO-OP
|
|
},
|
|
|
|
prepareScopeUpdate() {},
|
|
|
|
removeScopeEventHandles() {},
|
|
|
|
getInstanceFromScope() {
|
|
throw new Error('Not yet implemented.');
|
|
},
|
|
};
|
|
|
|
const hostConfig = useMutation
|
|
? {
|
|
...sharedHostConfig,
|
|
|
|
supportsMutation: true,
|
|
supportsPersistence: false,
|
|
|
|
commitMount(instance: Instance, type: string, newProps: Props): void {
|
|
// Noop
|
|
},
|
|
|
|
commitUpdate(
|
|
instance: Instance,
|
|
updatePayload: Object,
|
|
type: string,
|
|
oldProps: Props,
|
|
newProps: Props,
|
|
): void {
|
|
if (oldProps === null) {
|
|
throw new Error('Should have old props');
|
|
}
|
|
hostUpdateCounter++;
|
|
instance.prop = newProps.prop;
|
|
instance.hidden = !!newProps.hidden;
|
|
if (shouldSetTextContent(type, newProps)) {
|
|
instance.text = computeText(
|
|
(newProps.children: any) + '',
|
|
instance.context,
|
|
);
|
|
}
|
|
},
|
|
|
|
commitTextUpdate(
|
|
textInstance: TextInstance,
|
|
oldText: string,
|
|
newText: string,
|
|
): void {
|
|
hostUpdateCounter++;
|
|
textInstance.text = computeText(newText, textInstance.context);
|
|
},
|
|
|
|
appendChild,
|
|
appendChildToContainer,
|
|
insertBefore,
|
|
insertInContainerBefore,
|
|
removeChild,
|
|
removeChildFromContainer,
|
|
clearContainer,
|
|
|
|
hideInstance(instance: Instance): void {
|
|
instance.hidden = true;
|
|
},
|
|
|
|
hideTextInstance(textInstance: TextInstance): void {
|
|
textInstance.hidden = true;
|
|
},
|
|
|
|
unhideInstance(instance: Instance, props: Props): void {
|
|
if (!props.hidden) {
|
|
instance.hidden = false;
|
|
}
|
|
},
|
|
|
|
unhideTextInstance(textInstance: TextInstance, text: string): void {
|
|
textInstance.hidden = false;
|
|
},
|
|
|
|
resetTextContent(instance: Instance): void {
|
|
instance.text = null;
|
|
},
|
|
}
|
|
: {
|
|
...sharedHostConfig,
|
|
supportsMutation: false,
|
|
supportsPersistence: true,
|
|
|
|
cloneInstance,
|
|
clearContainer,
|
|
|
|
createContainerChildSet(
|
|
container: Container,
|
|
): Array<Instance | TextInstance> {
|
|
return [];
|
|
},
|
|
|
|
appendChildToContainerChildSet(
|
|
childSet: Array<Instance | TextInstance>,
|
|
child: Instance | TextInstance,
|
|
): void {
|
|
childSet.push(child);
|
|
},
|
|
|
|
finalizeContainerChildren(
|
|
container: Container,
|
|
newChildren: Array<Instance | TextInstance>,
|
|
): void {
|
|
container.pendingChildren = newChildren;
|
|
if (
|
|
newChildren.length === 1 &&
|
|
newChildren[0].text === 'Error when completing root'
|
|
) {
|
|
// Trigger an error for testing purposes
|
|
throw Error('Error when completing root');
|
|
}
|
|
},
|
|
|
|
replaceContainerChildren(
|
|
container: Container,
|
|
newChildren: Array<Instance | TextInstance>,
|
|
): void {
|
|
container.children = newChildren;
|
|
},
|
|
|
|
cloneHiddenInstance(
|
|
instance: Instance,
|
|
type: string,
|
|
props: Props,
|
|
internalInstanceHandle: Object,
|
|
): Instance {
|
|
const clone = cloneInstance(
|
|
instance,
|
|
null,
|
|
type,
|
|
props,
|
|
props,
|
|
internalInstanceHandle,
|
|
true,
|
|
null,
|
|
);
|
|
clone.hidden = true;
|
|
return clone;
|
|
},
|
|
|
|
cloneHiddenTextInstance(
|
|
instance: TextInstance,
|
|
text: string,
|
|
internalInstanceHandle: Object,
|
|
): TextInstance {
|
|
const clone = {
|
|
text: instance.text,
|
|
id: instanceCounter++,
|
|
hidden: true,
|
|
context: instance.context,
|
|
};
|
|
// Hide from unit tests
|
|
Object.defineProperty(clone, 'id', {
|
|
value: clone.id,
|
|
enumerable: false,
|
|
});
|
|
Object.defineProperty(clone, 'context', {
|
|
value: clone.context,
|
|
enumerable: false,
|
|
});
|
|
return clone;
|
|
},
|
|
};
|
|
|
|
const NoopRenderer = reconciler(hostConfig);
|
|
|
|
const rootContainers = new Map();
|
|
const roots = new Map();
|
|
const DEFAULT_ROOT_ID = '<default>';
|
|
|
|
function childToJSX(child, text) {
|
|
if (text !== null) {
|
|
return text;
|
|
}
|
|
if (child === null) {
|
|
return null;
|
|
}
|
|
if (typeof child === 'string') {
|
|
return child;
|
|
}
|
|
if (Array.isArray(child)) {
|
|
if (child.length === 0) {
|
|
return null;
|
|
}
|
|
if (child.length === 1) {
|
|
return childToJSX(child[0], null);
|
|
}
|
|
// $FlowFixMe
|
|
const children = child.map(c => childToJSX(c, null));
|
|
if (children.every(c => typeof c === 'string' || typeof c === 'number')) {
|
|
return children.join('');
|
|
}
|
|
return children;
|
|
}
|
|
if (Array.isArray(child.children)) {
|
|
// This is an instance.
|
|
const instance: Instance = (child: any);
|
|
const children = childToJSX(instance.children, instance.text);
|
|
const props = ({prop: instance.prop}: any);
|
|
if (instance.hidden) {
|
|
props.hidden = true;
|
|
}
|
|
if (children !== null) {
|
|
props.children = children;
|
|
}
|
|
return {
|
|
$$typeof: REACT_ELEMENT_TYPE,
|
|
type: instance.type,
|
|
key: null,
|
|
ref: null,
|
|
props: props,
|
|
_owner: null,
|
|
_store: __DEV__ ? {} : undefined,
|
|
};
|
|
}
|
|
// This is a text instance
|
|
const textInstance: TextInstance = (child: any);
|
|
if (textInstance.hidden) {
|
|
return '';
|
|
}
|
|
return textInstance.text;
|
|
}
|
|
|
|
function getChildren(root) {
|
|
if (root) {
|
|
return root.children;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getPendingChildren(root) {
|
|
if (root) {
|
|
return root.pendingChildren;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getChildrenAsJSX(root) {
|
|
const children = childToJSX(getChildren(root), null);
|
|
if (children === null) {
|
|
return null;
|
|
}
|
|
if (Array.isArray(children)) {
|
|
return {
|
|
$$typeof: REACT_ELEMENT_TYPE,
|
|
type: REACT_FRAGMENT_TYPE,
|
|
key: null,
|
|
ref: null,
|
|
props: {children},
|
|
_owner: null,
|
|
_store: __DEV__ ? {} : undefined,
|
|
};
|
|
}
|
|
return children;
|
|
}
|
|
|
|
function getPendingChildrenAsJSX(root) {
|
|
const children = childToJSX(getChildren(root), null);
|
|
if (children === null) {
|
|
return null;
|
|
}
|
|
if (Array.isArray(children)) {
|
|
return {
|
|
$$typeof: REACT_ELEMENT_TYPE,
|
|
type: REACT_FRAGMENT_TYPE,
|
|
key: null,
|
|
ref: null,
|
|
props: {children},
|
|
_owner: null,
|
|
_store: __DEV__ ? {} : undefined,
|
|
};
|
|
}
|
|
return children;
|
|
}
|
|
|
|
let idCounter = 0;
|
|
|
|
const ReactNoop = {
|
|
_Scheduler: Scheduler,
|
|
|
|
getChildren(rootID: string = DEFAULT_ROOT_ID) {
|
|
const container = rootContainers.get(rootID);
|
|
return getChildren(container);
|
|
},
|
|
|
|
getPendingChildren(rootID: string = DEFAULT_ROOT_ID) {
|
|
const container = rootContainers.get(rootID);
|
|
return getPendingChildren(container);
|
|
},
|
|
|
|
getOrCreateRootContainer(rootID: string = DEFAULT_ROOT_ID, tag: RootTag) {
|
|
let root = roots.get(rootID);
|
|
if (!root) {
|
|
const container = {rootID: rootID, pendingChildren: [], children: []};
|
|
rootContainers.set(rootID, container);
|
|
root = NoopRenderer.createContainer(container, tag, false, null);
|
|
roots.set(rootID, root);
|
|
}
|
|
return root.current.stateNode.containerInfo;
|
|
},
|
|
|
|
// TODO: Replace ReactNoop.render with createRoot + root.render
|
|
createRoot() {
|
|
const container = {
|
|
rootID: '' + idCounter++,
|
|
pendingChildren: [],
|
|
children: [],
|
|
};
|
|
const fiberRoot = NoopRenderer.createContainer(
|
|
container,
|
|
ConcurrentRoot,
|
|
false,
|
|
null,
|
|
);
|
|
return {
|
|
_Scheduler: Scheduler,
|
|
render(children: ReactNodeList) {
|
|
NoopRenderer.updateContainer(children, fiberRoot, null, null);
|
|
},
|
|
getChildren() {
|
|
return getChildren(container);
|
|
},
|
|
getChildrenAsJSX() {
|
|
return getChildrenAsJSX(container);
|
|
},
|
|
};
|
|
},
|
|
|
|
createBlockingRoot() {
|
|
const container = {
|
|
rootID: '' + idCounter++,
|
|
pendingChildren: [],
|
|
children: [],
|
|
};
|
|
const fiberRoot = NoopRenderer.createContainer(
|
|
container,
|
|
BlockingRoot,
|
|
false,
|
|
null,
|
|
);
|
|
return {
|
|
_Scheduler: Scheduler,
|
|
render(children: ReactNodeList) {
|
|
NoopRenderer.updateContainer(children, fiberRoot, null, null);
|
|
},
|
|
getChildren() {
|
|
return getChildren(container);
|
|
},
|
|
getChildrenAsJSX() {
|
|
return getChildrenAsJSX(container);
|
|
},
|
|
};
|
|
},
|
|
|
|
createLegacyRoot() {
|
|
const container = {
|
|
rootID: '' + idCounter++,
|
|
pendingChildren: [],
|
|
children: [],
|
|
};
|
|
const fiberRoot = NoopRenderer.createContainer(
|
|
container,
|
|
LegacyRoot,
|
|
false,
|
|
null,
|
|
);
|
|
return {
|
|
_Scheduler: Scheduler,
|
|
render(children: ReactNodeList) {
|
|
NoopRenderer.updateContainer(children, fiberRoot, null, null);
|
|
},
|
|
getChildren() {
|
|
return getChildren(container);
|
|
},
|
|
getChildrenAsJSX() {
|
|
return getChildrenAsJSX(container);
|
|
},
|
|
};
|
|
},
|
|
|
|
getChildrenAsJSX(rootID: string = DEFAULT_ROOT_ID) {
|
|
const container = rootContainers.get(rootID);
|
|
return getChildrenAsJSX(container);
|
|
},
|
|
|
|
getPendingChildrenAsJSX(rootID: string = DEFAULT_ROOT_ID) {
|
|
const container = rootContainers.get(rootID);
|
|
return getPendingChildrenAsJSX(container);
|
|
},
|
|
|
|
createPortal(
|
|
children: ReactNodeList,
|
|
container: Container,
|
|
key: ?string = null,
|
|
) {
|
|
return NoopRenderer.createPortal(children, container, null, key);
|
|
},
|
|
|
|
// Shortcut for testing a single root
|
|
render(element: React$Element<any>, callback: ?Function) {
|
|
ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
|
|
},
|
|
|
|
renderLegacySyncRoot(element: React$Element<any>, callback: ?Function) {
|
|
const rootID = DEFAULT_ROOT_ID;
|
|
const container = ReactNoop.getOrCreateRootContainer(rootID, LegacyRoot);
|
|
const root = roots.get(container.rootID);
|
|
NoopRenderer.updateContainer(element, root, null, callback);
|
|
},
|
|
|
|
renderToRootWithID(
|
|
element: React$Element<any>,
|
|
rootID: string,
|
|
callback: ?Function,
|
|
) {
|
|
const container = ReactNoop.getOrCreateRootContainer(
|
|
rootID,
|
|
ConcurrentRoot,
|
|
);
|
|
const root = roots.get(container.rootID);
|
|
NoopRenderer.updateContainer(element, root, null, callback);
|
|
},
|
|
|
|
unmountRootWithID(rootID: string) {
|
|
const root = roots.get(rootID);
|
|
if (root) {
|
|
NoopRenderer.updateContainer(null, root, null, () => {
|
|
roots.delete(rootID);
|
|
rootContainers.delete(rootID);
|
|
});
|
|
}
|
|
},
|
|
|
|
findInstance(
|
|
componentOrElement: Element | ?React$Component<any, any>,
|
|
): null | Instance | TextInstance {
|
|
if (componentOrElement == null) {
|
|
return null;
|
|
}
|
|
// Unsound duck typing.
|
|
const component = (componentOrElement: any);
|
|
if (typeof component.id === 'number') {
|
|
return component;
|
|
}
|
|
if (__DEV__) {
|
|
return NoopRenderer.findHostInstanceWithWarning(
|
|
component,
|
|
'findInstance',
|
|
);
|
|
}
|
|
return NoopRenderer.findHostInstance(component);
|
|
},
|
|
|
|
flushNextYield(): Array<mixed> {
|
|
Scheduler.unstable_flushNumberOfYields(1);
|
|
return Scheduler.unstable_clearYields();
|
|
},
|
|
|
|
flushWithHostCounters(
|
|
fn: () => void,
|
|
):
|
|
| {|
|
|
hostDiffCounter: number,
|
|
hostUpdateCounter: number,
|
|
|}
|
|
| {|
|
|
hostDiffCounter: number,
|
|
hostCloneCounter: number,
|
|
|} {
|
|
hostDiffCounter = 0;
|
|
hostUpdateCounter = 0;
|
|
hostCloneCounter = 0;
|
|
try {
|
|
Scheduler.unstable_flushAll();
|
|
return useMutation
|
|
? {
|
|
hostDiffCounter,
|
|
hostUpdateCounter,
|
|
}
|
|
: {
|
|
hostDiffCounter,
|
|
hostCloneCounter,
|
|
};
|
|
} finally {
|
|
hostDiffCounter = 0;
|
|
hostUpdateCounter = 0;
|
|
hostCloneCounter = 0;
|
|
}
|
|
},
|
|
|
|
expire: Scheduler.unstable_advanceTime,
|
|
|
|
flushExpired(): Array<mixed> {
|
|
return Scheduler.unstable_flushExpired();
|
|
},
|
|
|
|
unstable_runWithPriority: NoopRenderer.runWithPriority,
|
|
|
|
batchedUpdates: NoopRenderer.batchedUpdates,
|
|
|
|
deferredUpdates: NoopRenderer.deferredUpdates,
|
|
|
|
unbatchedUpdates: NoopRenderer.unbatchedUpdates,
|
|
|
|
discreteUpdates: NoopRenderer.discreteUpdates,
|
|
|
|
flushDiscreteUpdates: NoopRenderer.flushDiscreteUpdates,
|
|
|
|
flushSync(fn: () => mixed) {
|
|
NoopRenderer.flushSync(fn);
|
|
},
|
|
|
|
flushPassiveEffects: NoopRenderer.flushPassiveEffects,
|
|
|
|
act: NoopRenderer.act,
|
|
|
|
// Logs the current state of the tree.
|
|
dumpTree(rootID: string = DEFAULT_ROOT_ID) {
|
|
const root = roots.get(rootID);
|
|
const rootContainer = rootContainers.get(rootID);
|
|
if (!root || !rootContainer) {
|
|
// eslint-disable-next-line react-internal/no-production-logging
|
|
console.log('Nothing rendered yet.');
|
|
return;
|
|
}
|
|
|
|
const bufferedLog = [];
|
|
function log(...args) {
|
|
bufferedLog.push(...args, '\n');
|
|
}
|
|
|
|
function logHostInstances(
|
|
children: Array<Instance | TextInstance>,
|
|
depth,
|
|
) {
|
|
for (let i = 0; i < children.length; i++) {
|
|
const child = children[i];
|
|
const indent = ' '.repeat(depth);
|
|
if (typeof child.text === 'string') {
|
|
log(indent + '- ' + child.text);
|
|
} else {
|
|
// $FlowFixMe - The child should've been refined now.
|
|
log(indent + '- ' + child.type + '#' + child.id);
|
|
// $FlowFixMe - The child should've been refined now.
|
|
logHostInstances(child.children, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
function logContainer(container: Container, depth) {
|
|
log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
|
|
logHostInstances(container.children, depth + 1);
|
|
}
|
|
|
|
function logUpdateQueue(updateQueue: UpdateQueue<mixed>, depth) {
|
|
log(' '.repeat(depth + 1) + 'QUEUED UPDATES');
|
|
const first = updateQueue.firstBaseUpdate;
|
|
const update = first;
|
|
if (update !== null) {
|
|
do {
|
|
log(
|
|
' '.repeat(depth + 1) + '~',
|
|
'[' + update.expirationTime + ']',
|
|
);
|
|
} while (update !== null);
|
|
}
|
|
|
|
const lastPending = updateQueue.shared.pending;
|
|
if (lastPending !== null) {
|
|
const firstPending = lastPending.next;
|
|
const pendingUpdate = firstPending;
|
|
if (pendingUpdate !== null) {
|
|
do {
|
|
log(
|
|
' '.repeat(depth + 1) + '~',
|
|
'[' + pendingUpdate.expirationTime + ']',
|
|
);
|
|
} while (pendingUpdate !== null && pendingUpdate !== firstPending);
|
|
}
|
|
}
|
|
}
|
|
|
|
function logFiber(fiber: Fiber, depth) {
|
|
log(
|
|
' '.repeat(depth) +
|
|
'- ' +
|
|
// need to explicitly coerce Symbol to a string
|
|
(fiber.type ? fiber.type.name || fiber.type.toString() : '[root]'),
|
|
'[' +
|
|
fiber.childExpirationTime +
|
|
(fiber.pendingProps ? '*' : '') +
|
|
']',
|
|
);
|
|
if (fiber.updateQueue) {
|
|
logUpdateQueue(fiber.updateQueue, depth);
|
|
}
|
|
// const childInProgress = fiber.progressedChild;
|
|
// if (childInProgress && childInProgress !== fiber.child) {
|
|
// log(
|
|
// ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority,
|
|
// );
|
|
// logFiber(childInProgress, depth + 1);
|
|
// if (fiber.child) {
|
|
// log(' '.repeat(depth + 1) + 'CURRENT');
|
|
// }
|
|
// } else if (fiber.child && fiber.updateQueue) {
|
|
// log(' '.repeat(depth + 1) + 'CHILDREN');
|
|
// }
|
|
if (fiber.child) {
|
|
logFiber(fiber.child, depth + 1);
|
|
}
|
|
if (fiber.sibling) {
|
|
logFiber(fiber.sibling, depth);
|
|
}
|
|
}
|
|
|
|
log('HOST INSTANCES:');
|
|
logContainer(rootContainer, 0);
|
|
log('FIBERS:');
|
|
logFiber(root.current, 0);
|
|
|
|
// eslint-disable-next-line react-internal/no-production-logging
|
|
console.log(...bufferedLog);
|
|
},
|
|
|
|
getRoot(rootID: string = DEFAULT_ROOT_ID) {
|
|
return roots.get(rootID);
|
|
},
|
|
};
|
|
|
|
return ReactNoop;
|
|
}
|
|
|
|
export default createReactNoop;
|