Resolve host configs at build time (#12792)

* Extract base Jest config

This makes it easier to change the source config without affecting the build test config.

* Statically import the host config

This changes react-reconciler to import HostConfig instead of getting it through a function argument.

Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope).

This is still very broken.

* Add scaffolding for importing an inlined renderer

* Fix the build

* ES exports for renderer methods

* ES modules for host configs

* Remove closures from the reconciler

* Check each renderer's config with Flow

* Fix uncovered Flow issue

We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null.
Help Flow.

* Prettier

* Get rid of enable*Reconciler flags

They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence.

This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing.
Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead.

* Prettier again

* Fix Flow config creation issue

* Fix imprecise Flow typing

* Revert accidental changes
This commit is contained in:
Dan Abramov
2018-05-19 11:29:11 +01:00
committed by GitHub
parent c0fe8d6f69
commit 47b003a828
76 changed files with 8241 additions and 8226 deletions
+1 -6
View File
@@ -6,12 +6,11 @@
*/
import React from 'react';
import ReactFiberReconciler from 'react-reconciler';
import * as ARTRenderer from 'react-reconciler/inline.art';
import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
import FastNoSideEffects from 'art/modes/fast-noSideEffects';
import ReactARTHostConfig from './ReactARTHostConfig';
import {TYPES, childrenAsString} from './ReactARTInternals';
Mode.setCurrent(
@@ -132,10 +131,6 @@ class Text extends React.Component {
}
}
/** ART Renderer */
const ARTRenderer = ReactFiberReconciler(ReactARTHostConfig);
/** API */
export const ClippingRectangle = TYPES.CLIPPING_RECTANGLE;
+152 -142
View File
@@ -234,157 +234,167 @@ function applyTextProps(instance, props, prevProps = {}) {
}
}
const ReactARTHostConfig = {
appendInitialChild(parentInstance, child) {
if (typeof child === 'string') {
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
invariant(false, 'Text children should already be flattened.');
return;
}
export * from 'shared/HostConfigWithNoPersistence';
export * from 'shared/HostConfigWithNoHydration';
child.inject(parentInstance);
},
export function appendInitialChild(parentInstance, child) {
if (typeof child === 'string') {
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
invariant(false, 'Text children should already be flattened.');
return;
}
createInstance(type, props, internalInstanceHandle) {
let instance;
child.inject(parentInstance);
}
switch (type) {
case TYPES.CLIPPING_RECTANGLE:
instance = Mode.ClippingRectangle();
instance._applyProps = applyClippingRectangleProps;
break;
case TYPES.GROUP:
instance = Mode.Group();
instance._applyProps = applyGroupProps;
break;
case TYPES.SHAPE:
instance = Mode.Shape();
instance._applyProps = applyShapeProps;
break;
case TYPES.TEXT:
instance = Mode.Text(
props.children,
props.font,
props.alignment,
props.path,
);
instance._applyProps = applyTextProps;
break;
}
export function createInstance(type, props, internalInstanceHandle) {
let instance;
invariant(instance, 'ReactART does not support the type "%s"', type);
instance._applyProps(instance, props);
return instance;
},
createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
return text;
},
finalizeInitialChildren(domElement, type, props) {
return false;
},
getPublicInstance(instance) {
return instance;
},
prepareForCommit() {
// Noop
},
prepareUpdate(domElement, type, oldProps, newProps) {
return UPDATE_SIGNAL;
},
resetAfterCommit() {
// Noop
},
resetTextContent(domElement) {
// Noop
},
shouldDeprioritizeSubtree(type, props) {
return false;
},
getRootHostContext() {
return emptyObject;
},
getChildHostContext() {
return emptyObject;
},
scheduleDeferredCallback: ReactScheduler.scheduleWork,
shouldSetTextContent(type, props) {
return (
typeof props.children === 'string' || typeof props.children === 'number'
);
},
now: ReactScheduler.now,
// The ART renderer is secondary to the React DOM renderer.
isPrimaryRenderer: false,
mutation: {
appendChild(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
},
appendChildToContainer(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
},
insertBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
switch (type) {
case TYPES.CLIPPING_RECTANGLE:
instance = Mode.ClippingRectangle();
instance._applyProps = applyClippingRectangleProps;
break;
case TYPES.GROUP:
instance = Mode.Group();
instance._applyProps = applyGroupProps;
break;
case TYPES.SHAPE:
instance = Mode.Shape();
instance._applyProps = applyShapeProps;
break;
case TYPES.TEXT:
instance = Mode.Text(
props.children,
props.font,
props.alignment,
props.path,
);
child.injectBefore(beforeChild);
},
instance._applyProps = applyTextProps;
break;
}
insertInContainerBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
},
invariant(instance, 'ReactART does not support the type "%s"', type);
removeChild(parentInstance, child) {
destroyEventListeners(child);
child.eject();
},
instance._applyProps(instance, props);
removeChildFromContainer(parentInstance, child) {
destroyEventListeners(child);
child.eject();
},
return instance;
}
commitTextUpdate(textInstance, oldText, newText) {
// Noop
},
export function createTextInstance(
text,
rootContainerInstance,
internalInstanceHandle,
) {
return text;
}
commitMount(instance, type, newProps) {
// Noop
},
export function finalizeInitialChildren(domElement, type, props) {
return false;
}
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
instance._applyProps(instance, newProps, oldProps);
},
},
};
export function getPublicInstance(instance) {
return instance;
}
export default ReactARTHostConfig;
export function prepareForCommit() {
// Noop
}
export function prepareUpdate(domElement, type, oldProps, newProps) {
return UPDATE_SIGNAL;
}
export function resetAfterCommit() {
// Noop
}
export function resetTextContent(domElement) {
// Noop
}
export function shouldDeprioritizeSubtree(type, props) {
return false;
}
export function getRootHostContext() {
return emptyObject;
}
export function getChildHostContext() {
return emptyObject;
}
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
export function shouldSetTextContent(type, props) {
return (
typeof props.children === 'string' || typeof props.children === 'number'
);
}
export const now = ReactScheduler.now;
// The ART renderer is secondary to the React DOM renderer.
export const isPrimaryRenderer = false;
export const supportsMutation = true;
export function appendChild(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
}
export function appendChildToContainer(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
}
export function insertBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
}
export function insertInContainerBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
}
export function removeChild(parentInstance, child) {
destroyEventListeners(child);
child.eject();
}
export function removeChildFromContainer(parentInstance, child) {
destroyEventListeners(child);
child.eject();
}
export function commitTextUpdate(textInstance, oldText, newText) {
// Noop
}
export function commitMount(instance, type, newProps) {
// Noop
}
export function commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
) {
instance._applyProps(instance, newProps, oldProps);
}
+23 -17
View File
@@ -15,6 +15,19 @@ const React = require('react');
const ReactDOM = require('react-dom');
const ReactTestUtils = require('react-dom/test-utils');
// Isolate test renderer.
jest.resetModules();
const ReactTestRenderer = require('react-test-renderer');
// Isolate ART renderer.
jest.resetModules();
const ReactART = require('react-art');
const ARTSVGMode = require('art/modes/svg');
const ARTCurrentMode = require('art/modes/current');
const Circle = require('react-art/Circle');
const Rectangle = require('react-art/Rectangle');
const Wedge = require('react-art/Wedge');
let Group;
let Shape;
let Surface;
@@ -22,15 +35,6 @@ let TestComponent;
const Missing = {};
const ReactART = require('react-art');
const ARTSVGMode = require('art/modes/svg');
const ARTCurrentMode = require('art/modes/current');
const renderer = require('react-test-renderer');
const Circle = require('react-art/Circle');
const Rectangle = require('react-art/Rectangle');
const Wedge = require('react-art/Wedge');
function testDOMNodeStructure(domNode, expectedStructure) {
expect(domNode).toBeDefined();
expect(domNode.nodeName).toBe(expectedStructure.nodeName);
@@ -362,7 +366,7 @@ describe('ReactART', () => {
// Using test renderer instead of the DOM renderer here because async
// testing APIs for the DOM renderer don't exist.
const testRenderer = renderer.create(
const testRenderer = ReactTestRenderer.create(
<CurrentRendererContext.Provider value="Test">
<Yield value="A" />
<Yield value="B" />
@@ -397,7 +401,7 @@ describe('ReactART', () => {
describe('ReactARTComponents', () => {
it('should generate a <Shape> with props for drawing the Circle', () => {
const circle = renderer.create(
const circle = ReactTestRenderer.create(
<Circle radius={10} stroke="green" strokeWidth={3} fill="blue" />,
);
expect(circle.toJSON()).toMatchSnapshot();
@@ -405,7 +409,9 @@ describe('ReactARTComponents', () => {
it('should warn if radius is missing on a Circle component', () => {
expect(() =>
renderer.create(<Circle stroke="green" strokeWidth={3} fill="blue" />),
ReactTestRenderer.create(
<Circle stroke="green" strokeWidth={3} fill="blue" />,
),
).toWarnDev(
'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
'but its value is `undefined`.' +
@@ -414,7 +420,7 @@ describe('ReactARTComponents', () => {
});
it('should generate a <Shape> with props for drawing the Rectangle', () => {
const rectangle = renderer.create(
const rectangle = ReactTestRenderer.create(
<Rectangle width={50} height={50} stroke="green" fill="blue" />,
);
expect(rectangle.toJSON()).toMatchSnapshot();
@@ -422,7 +428,7 @@ describe('ReactARTComponents', () => {
it('should warn if width/height is missing on a Rectangle component', () => {
expect(() =>
renderer.create(<Rectangle stroke="green" fill="blue" />),
ReactTestRenderer.create(<Rectangle stroke="green" fill="blue" />),
).toWarnDev([
'Warning: Failed prop type: The prop `width` is marked as required in `Rectangle`, ' +
'but its value is `undefined`.' +
@@ -434,21 +440,21 @@ describe('ReactARTComponents', () => {
});
it('should generate a <Shape> with props for drawing the Wedge', () => {
const wedge = renderer.create(
const wedge = ReactTestRenderer.create(
<Wedge outerRadius={50} startAngle={0} endAngle={360} fill="blue" />,
);
expect(wedge.toJSON()).toMatchSnapshot();
});
it('should return null if startAngle equals to endAngle on Wedge', () => {
const wedge = renderer.create(
const wedge = ReactTestRenderer.create(
<Wedge outerRadius={50} startAngle={0} endAngle={0} fill="blue" />,
);
expect(wedge.toJSON()).toBeNull();
});
it('should warn if outerRadius/startAngle/endAngle is missing on a Wedge component', () => {
expect(() => renderer.create(<Wedge fill="blue" />)).toWarnDev([
expect(() => ReactTestRenderer.create(<Wedge fill="blue" />)).toWarnDev([
'Warning: Failed prop type: The prop `outerRadius` is marked as required in `Wedge`, ' +
'but its value is `undefined`.' +
'\n in Wedge (at **)',
+1 -4
View File
@@ -19,7 +19,7 @@ import type {Container} from './ReactDOMHostConfig';
import '../shared/checkReact';
import './ReactDOMClientInjection';
import ReactFiberReconciler from 'react-reconciler';
import * as DOMRenderer from 'react-reconciler/inline.dom';
import * as ReactPortal from 'shared/ReactPortal';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import * as ReactGenericBatching from 'events/ReactGenericBatching';
@@ -35,7 +35,6 @@ import invariant from 'fbjs/lib/invariant';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import warning from 'fbjs/lib/warning';
import ReactDOMHostConfig from './ReactDOMHostConfig';
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
import * as ReactDOMEventListener from '../events/ReactDOMEventListener';
@@ -447,8 +446,6 @@ function shouldHydrateDueToLegacyHeuristic(container) {
);
}
const DOMRenderer = ReactFiberReconciler(ReactDOMHostConfig);
ReactGenericBatching.injection.injectRenderer(DOMRenderer);
let warnedAboutHydrateAPI = false;
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -12,6 +12,8 @@ import type {ReactNodeList} from 'shared/ReactTypes';
import './ReactFabricInjection';
import * as ReactFabricRenderer from 'react-reconciler/inline.fabric';
import * as ReactPortal from 'shared/ReactPortal';
import * as ReactGenericBatching from 'events/ReactGenericBatching';
import ReactVersion from 'shared/ReactVersion';
@@ -19,7 +21,6 @@ import ReactVersion from 'shared/ReactVersion';
import NativeMethodsMixin from './NativeMethodsMixin';
import ReactNativeComponent from './ReactNativeComponent';
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
import ReactFabricRenderer from './ReactFabricRenderer';
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
@@ -64,7 +65,7 @@ function findNodeHandle(componentOrHandle: any): ?number {
}
if (hostInstance.canonical) {
// Fabric
return hostInstance.canonical._nativeTag;
return (hostInstance.canonical: any)._nativeTag;
}
return hostInstance._nativeTag;
}
+233 -223
View File
@@ -33,9 +33,9 @@ import {
cloneNodeWithNewChildren,
cloneNodeWithNewChildrenAndProps,
cloneNodeWithNewProps,
createChildSet,
appendChild,
appendChildToSet,
createChildSet as createChildNodeSet,
appendChild as appendChildNode,
appendChildToSet as appendChildNodeToSet,
completeRoot,
registerEventHandler,
} from 'FabricUIManager';
@@ -47,9 +47,24 @@ import UIManager from 'UIManager';
// This means that they never overlap.
let nextReactTag = 2;
type HostContext = $ReadOnly<{|
type Node = Object;
export type Type = string;
export type Props = Object;
export type Instance = {
node: Node,
canonical: ReactFabricHostComponent,
};
export type TextInstance = {
node: Node,
};
export type HydratableInstance = Instance | TextInstance;
export type PublicInstance = ReactFabricHostComponent;
export type Container = number;
export type ChildSet = Object;
export type HostContext = $ReadOnly<{|
isInAParentText: boolean,
|}>;
export type UpdatePayload = Object;
// TODO: Remove this conditional once all changes have propagated.
if (registerEventHandler) {
@@ -135,247 +150,242 @@ class ReactFabricHostComponent {
// eslint-disable-next-line no-unused-expressions
(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
type Node = Object;
type ChildSet = Object;
type Container = number;
type Instance = {
node: Node,
canonical: ReactFabricHostComponent,
};
type Props = Object;
type TextInstance = {
node: Node,
};
export * from 'shared/HostConfigWithNoMutation';
export * from 'shared/HostConfigWithNoHydration';
const ReactFabricHostConfig = {
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
appendChild(parentInstance.node, child.node);
},
export function appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
appendChildNode(parentInstance.node, child.node);
}
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
const tag = nextReactTag;
nextReactTag += 2;
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
const tag = nextReactTag;
nextReactTag += 2;
const viewConfig = ReactNativeViewConfigRegistry.get(type);
const viewConfig = ReactNativeViewConfigRegistry.get(type);
if (__DEV__) {
for (const key in viewConfig.validAttributes) {
if (props.hasOwnProperty(key)) {
deepFreezeAndThrowOnMutationInDev(props[key]);
}
if (__DEV__) {
for (const key in viewConfig.validAttributes) {
if (props.hasOwnProperty(key)) {
deepFreezeAndThrowOnMutationInDev(props[key]);
}
}
}
invariant(
type !== 'RCTView' || !hostContext.isInAParentText,
'Nesting of <View> within <Text> is not currently supported.',
);
invariant(
type !== 'RCTView' || !hostContext.isInAParentText,
'Nesting of <View> within <Text> is not currently supported.',
);
const updatePayload = ReactNativeAttributePayload.create(
props,
viewConfig.validAttributes,
);
const updatePayload = ReactNativeAttributePayload.create(
props,
viewConfig.validAttributes,
);
const node = createNode(
tag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload, // props
internalInstanceHandle, // internalInstanceHandle
);
const node = createNode(
tag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload, // props
internalInstanceHandle, // internalInstanceHandle
);
const component = new ReactFabricHostComponent(tag, viewConfig, props);
const component = new ReactFabricHostComponent(tag, viewConfig, props);
return {
node: node,
canonical: component,
};
},
return {
node: node,
canonical: component,
};
}
createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
invariant(
hostContext.isInAParentText,
'Text strings must be rendered within a <Text> component.',
);
export function createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
invariant(
hostContext.isInAParentText,
'Text strings must be rendered within a <Text> component.',
);
const tag = nextReactTag;
nextReactTag += 2;
const tag = nextReactTag;
nextReactTag += 2;
const node = createNode(
tag, // reactTag
'RCTRawText', // viewName
rootContainerInstance, // rootTag
{text: text}, // props
internalInstanceHandle, // instance handle
);
const node = createNode(
tag, // reactTag
'RCTRawText', // viewName
rootContainerInstance, // rootTag
{text: text}, // props
internalInstanceHandle, // instance handle
);
return {
node: node,
};
},
return {
node: node,
};
}
finalizeInitialChildren(
parentInstance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
): boolean {
return false;
},
export function finalizeInitialChildren(
parentInstance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): boolean {
return false;
}
getRootHostContext(rootContainerInstance: Container): HostContext {
return {isInAParentText: false};
},
export function getRootHostContext(
rootContainerInstance: Container,
): HostContext {
return {isInAParentText: false};
}
getChildHostContext(
parentHostContext: HostContext,
type: string,
): HostContext {
const prevIsInAParentText = parentHostContext.isInAParentText;
const isInAParentText =
type === 'AndroidTextInput' || // Android
type === 'RCTMultilineTextInputView' || // iOS
type === 'RCTSinglelineTextInputView' || // iOS
type === 'RCTText' ||
type === 'RCTVirtualText';
export function getChildHostContext(
parentHostContext: HostContext,
type: string,
rootContainerInstance: Container,
): HostContext {
const prevIsInAParentText = parentHostContext.isInAParentText;
const isInAParentText =
type === 'AndroidTextInput' || // Android
type === 'RCTMultilineTextInputView' || // iOS
type === 'RCTSinglelineTextInputView' || // iOS
type === 'RCTText' ||
type === 'RCTVirtualText';
if (prevIsInAParentText !== isInAParentText) {
return {isInAParentText};
if (prevIsInAParentText !== isInAParentText) {
return {isInAParentText};
} else {
return parentHostContext;
}
}
export function getPublicInstance(instance: Instance): * {
return instance.canonical;
}
export function prepareForCommit(containerInfo: Container): void {
// Noop
}
export function prepareUpdate(
instance: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Object {
const viewConfig = instance.canonical.viewConfig;
const updatePayload = ReactNativeAttributePayload.diff(
oldProps,
newProps,
viewConfig.validAttributes,
);
// TODO: If the event handlers have changed, we need to update the current props
// in the commit phase but there is no host config hook to do it yet.
return updatePayload;
}
export function resetAfterCommit(containerInfo: Container): void {
// Noop
}
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
}
export function shouldSetTextContent(type: string, props: Props): boolean {
// TODO (bvaughn) Revisit this decision.
// Always returning false simplifies the createInstance() implementation,
// But creates an additional child Fiber for raw text children.
// No additional native views are created though.
// It's not clear to me which is better so I'm deferring for now.
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
return false;
}
// The Fabric renderer is secondary to the existing React Native renderer.
export const isPrimaryRenderer = false;
export const now = ReactNativeFrameScheduling.now;
export const scheduleDeferredCallback =
ReactNativeFrameScheduling.scheduleDeferredCallback;
export const cancelDeferredCallback =
ReactNativeFrameScheduling.cancelDeferredCallback;
// -------------------
// Persistence
// -------------------
export const supportsPersistence = true;
export function cloneInstance(
instance: Instance,
updatePayload: null | Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
keepChildren: boolean,
recyclableInstance: null | Instance,
): Instance {
const node = instance.node;
let clone;
if (keepChildren) {
if (updatePayload !== null) {
clone = cloneNodeWithNewProps(
node,
updatePayload,
internalInstanceHandle,
);
} else {
return parentHostContext;
clone = cloneNode(node, internalInstanceHandle);
}
},
} else {
if (updatePayload !== null) {
clone = cloneNodeWithNewChildrenAndProps(
node,
updatePayload,
internalInstanceHandle,
);
} else {
clone = cloneNodeWithNewChildren(node, internalInstanceHandle);
}
}
return {
node: clone,
canonical: instance.canonical,
};
}
getPublicInstance(instance: Instance): * {
return instance.canonical;
},
export function createContainerChildSet(container: Container): ChildSet {
return createChildNodeSet(container);
}
now: ReactNativeFrameScheduling.now,
export function appendChildToContainerChildSet(
childSet: ChildSet,
child: Instance | TextInstance,
): void {
appendChildNodeToSet(childSet, child.node);
}
// The Fabric renderer is secondary to the existing React Native renderer.
isPrimaryRenderer: false,
export function finalizeContainerChildren(
container: Container,
newChildren: ChildSet,
): void {
completeRoot(container, newChildren);
}
prepareForCommit(): void {
// Noop
},
prepareUpdate(
instance: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Object {
const viewConfig = instance.canonical.viewConfig;
const updatePayload = ReactNativeAttributePayload.diff(
oldProps,
newProps,
viewConfig.validAttributes,
);
// TODO: If the event handlers have changed, we need to update the current props
// in the commit phase but there is no host config hook to do it yet.
return updatePayload;
},
resetAfterCommit(): void {
// Noop
},
scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},
shouldSetTextContent(type: string, props: Props): boolean {
// TODO (bvaughn) Revisit this decision.
// Always returning false simplifies the createInstance() implementation,
// But creates an additional child Fiber for raw text children.
// No additional native views are created though.
// It's not clear to me which is better so I'm deferring for now.
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
return false;
},
persistence: {
cloneInstance(
instance: Instance,
updatePayload: null | Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
keepChildren: boolean,
recyclableInstance: null | Instance,
): Instance {
const node = instance.node;
let clone;
if (keepChildren) {
if (updatePayload !== null) {
clone = cloneNodeWithNewProps(
node,
updatePayload,
internalInstanceHandle,
);
} else {
clone = cloneNode(node, internalInstanceHandle);
}
} else {
if (updatePayload !== null) {
clone = cloneNodeWithNewChildrenAndProps(
node,
updatePayload,
internalInstanceHandle,
);
} else {
clone = cloneNodeWithNewChildren(node, internalInstanceHandle);
}
}
return {
node: clone,
canonical: instance.canonical,
};
},
createContainerChildSet(container: Container): ChildSet {
return createChildSet(container);
},
appendChildToContainerChildSet(
childSet: ChildSet,
child: Instance | TextInstance,
): void {
appendChildToSet(childSet, child.node);
},
finalizeContainerChildren(
container: Container,
newChildren: ChildSet,
): void {
completeRoot(container, newChildren);
},
replaceContainerChildren(
container: Container,
newChildren: ChildSet,
): void {},
},
};
export default ReactFabricHostConfig;
export function replaceContainerChildren(
container: Container,
newChildren: ChildSet,
): void {}
@@ -1,15 +0,0 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import ReactFiberReconciler from 'react-reconciler';
import ReactFabricHostConfig from './ReactFabricHostConfig';
const ReactFabricRenderer = ReactFiberReconciler(ReactFabricHostConfig);
export default ReactFabricRenderer;
@@ -7,7 +7,7 @@
* @flow
*/
import type {Deadline} from 'react-reconciler';
import type {Deadline} from 'react-reconciler/src/ReactFiberScheduler';
const hasNativePerformanceNow =
typeof performance === 'object' && typeof performance.now === 'function';
@@ -43,7 +43,10 @@ function setTimeoutCallback() {
// RN has a poor polyfill for requestIdleCallback so we aren't using it.
// This implementation is only intended for short-term use anyway.
// We also don't implement cancel functionality b'c Fiber doesn't currently need it.
function scheduleDeferredCallback(callback: Callback): number {
function scheduleDeferredCallback(
callback: Callback,
options?: {timeout: number},
): number {
// We assume only one callback is scheduled at a time b'c that's how Fiber works.
scheduledCallback = callback;
return setTimeout(setTimeoutCallback, 1);
+357 -346
View File
@@ -26,18 +26,22 @@ import {
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
type Container = number;
export type Type = string;
export type Props = Object;
export type Container = number;
export type Instance = {
_children: Array<Instance | number>,
_nativeTag: number,
viewConfig: ReactNativeBaseComponentViewConfig,
};
type Props = Object;
type TextInstance = number;
type HostContext = $ReadOnly<{|
export type TextInstance = number;
export type HydratableInstance = Instance | TextInstance;
export type PublicInstance = Instance;
export type HostContext = $ReadOnly<{|
isInAParentText: boolean,
|}>;
export type UpdatePayload = Object; // Unused
export type ChildSet = void; // Unused
// Counter for uniquely identifying views.
// % 10 === 1 means it is a rootTag.
@@ -63,369 +67,376 @@ function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
}
}
const ReactNativeHostConfig = {
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance._children.push(child);
},
export * from 'shared/HostConfigWithNoPersistence';
export * from 'shared/HostConfigWithNoHydration';
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
const tag = allocateTag();
const viewConfig = ReactNativeViewConfigRegistry.get(type);
export function appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance._children.push(child);
}
if (__DEV__) {
for (const key in viewConfig.validAttributes) {
if (props.hasOwnProperty(key)) {
deepFreezeAndThrowOnMutationInDev(props[key]);
}
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
const tag = allocateTag();
const viewConfig = ReactNativeViewConfigRegistry.get(type);
if (__DEV__) {
for (const key in viewConfig.validAttributes) {
if (props.hasOwnProperty(key)) {
deepFreezeAndThrowOnMutationInDev(props[key]);
}
}
}
invariant(
type !== 'RCTView' || !hostContext.isInAParentText,
'Nesting of <View> within <Text> is not currently supported.',
invariant(
type !== 'RCTView' || !hostContext.isInAParentText,
'Nesting of <View> within <Text> is not currently supported.',
);
const updatePayload = ReactNativeAttributePayload.create(
props,
viewConfig.validAttributes,
);
UIManager.createView(
tag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload, // props
);
const component = new ReactNativeFiberHostComponent(tag, viewConfig);
precacheFiberNode(internalInstanceHandle, tag);
updateFiberProps(tag, props);
// Not sure how to avoid this cast. Flow is okay if the component is defined
// in the same file but if it's external it can't see the types.
return ((component: any): Instance);
}
export function createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
invariant(
hostContext.isInAParentText,
'Text strings must be rendered within a <Text> component.',
);
const tag = allocateTag();
UIManager.createView(
tag, // reactTag
'RCTRawText', // viewName
rootContainerInstance, // rootTag
{text: text}, // props
);
precacheFiberNode(internalInstanceHandle, tag);
return tag;
}
export function finalizeInitialChildren(
parentInstance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): boolean {
// Don't send a no-op message over the bridge.
if (parentInstance._children.length === 0) {
return false;
}
// Map from child objects to native tags.
// Either way we need to pass a copy of the Array to prevent it from being frozen.
const nativeTags = parentInstance._children.map(
child =>
typeof child === 'number'
? child // Leaf node (eg text)
: child._nativeTag,
);
UIManager.setChildren(
parentInstance._nativeTag, // containerTag
nativeTags, // reactTags
);
return false;
}
export function getRootHostContext(
rootContainerInstance: Container,
): HostContext {
return {isInAParentText: false};
}
export function getChildHostContext(
parentHostContext: HostContext,
type: string,
rootContainerInstance: Container,
): HostContext {
const prevIsInAParentText = parentHostContext.isInAParentText;
const isInAParentText =
type === 'AndroidTextInput' || // Android
type === 'RCTMultilineTextInputView' || // iOS
type === 'RCTSinglelineTextInputView' || // iOS
type === 'RCTText' ||
type === 'RCTVirtualText';
if (prevIsInAParentText !== isInAParentText) {
return {isInAParentText};
} else {
return parentHostContext;
}
}
export function getPublicInstance(instance: Instance): * {
return instance;
}
export function prepareForCommit(containerInfo: Container): void {
// Noop
}
export function prepareUpdate(
instance: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Object {
return emptyObject;
}
export function resetAfterCommit(containerInfo: Container): void {
// Noop
}
export const now = ReactNativeFrameScheduling.now;
export const isPrimaryRenderer = true;
export const scheduleDeferredCallback =
ReactNativeFrameScheduling.scheduleDeferredCallback;
export const cancelDeferredCallback =
ReactNativeFrameScheduling.cancelDeferredCallback;
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
}
export function shouldSetTextContent(type: string, props: Props): boolean {
// TODO (bvaughn) Revisit this decision.
// Always returning false simplifies the createInstance() implementation,
// But creates an additional child Fiber for raw text children.
// No additional native views are created though.
// It's not clear to me which is better so I'm deferring for now.
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
return false;
}
// -------------------
// Mutation
// -------------------
export const supportsMutation = true;
export function appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
const childTag = typeof child === 'number' ? child : child._nativeTag;
const children = parentInstance._children;
const index = children.indexOf(child);
if (index >= 0) {
children.splice(index, 1);
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[index], // moveFromIndices
[children.length - 1], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
children.push(child);
const updatePayload = ReactNativeAttributePayload.create(
props,
viewConfig.validAttributes,
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[children.length - 1], // addAtIndices
[], // removeAtIndices
);
}
}
UIManager.createView(
tag, // reactTag
export function appendChildToContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
const childTag = typeof child === 'number' ? child : child._nativeTag;
UIManager.setChildren(
parentInstance, // containerTag
[childTag], // reactTags
);
}
export function commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
UIManager.updateView(
textInstance, // reactTag
'RCTRawText', // viewName
{text: newText}, // props
);
}
export function commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Noop
}
export function commitUpdate(
instance: Instance,
updatePayloadTODO: Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
const viewConfig = instance.viewConfig;
updateFiberProps(instance._nativeTag, newProps);
const updatePayload = ReactNativeAttributePayload.diff(
oldProps,
newProps,
viewConfig.validAttributes,
);
// Avoid the overhead of bridge calls if there's no update.
// This is an expensive no-op for Android, and causes an unnecessary
// view invalidation for certain components (eg RCTTextInput) on iOS.
if (updatePayload != null) {
UIManager.updateView(
instance._nativeTag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload, // props
);
}
}
const component = new ReactNativeFiberHostComponent(tag, viewConfig);
export function insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
const children = (parentInstance: any)._children;
const index = children.indexOf(child);
precacheFiberNode(internalInstanceHandle, tag);
updateFiberProps(tag, props);
// Move existing child or add new child?
if (index >= 0) {
children.splice(index, 1);
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
// Not sure how to avoid this cast. Flow is okay if the component is defined
// in the same file but if it's external it can't see the types.
return ((component: any): Instance);
},
createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
invariant(
hostContext.isInAParentText,
'Text strings must be rendered within a <Text> component.',
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[index], // moveFromIndices
[beforeChildIndex], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
const tag = allocateTag();
const childTag = typeof child === 'number' ? child : child._nativeTag;
UIManager.createView(
tag, // reactTag
'RCTRawText', // viewName
rootContainerInstance, // rootTag
{text: text}, // props
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[beforeChildIndex], // addAtIndices
[], // removeAtIndices
);
}
}
precacheFiberNode(internalInstanceHandle, tag);
export function insertInContainerBefore(
parentInstance: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
// TODO (bvaughn): Remove this check when...
// We create a wrapper object for the container in ReactNative render()
// Or we refactor to remove wrapper objects entirely.
// For more info on pros/cons see PR #8560 description.
invariant(
typeof parentInstance !== 'number',
'Container does not support insertBefore operation',
);
}
return tag;
},
export function removeChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
recursivelyUncacheFiberNode(child);
const children = parentInstance._children;
const index = children.indexOf(child);
finalizeInitialChildren(
parentInstance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
): boolean {
// Don't send a no-op message over the bridge.
if (parentInstance._children.length === 0) {
return false;
}
children.splice(index, 1);
// Map from child objects to native tags.
// Either way we need to pass a copy of the Array to prevent it from being frozen.
const nativeTags = parentInstance._children.map(
child =>
typeof child === 'number'
? child // Leaf node (eg text)
: child._nativeTag,
);
UIManager.manageChildren(
parentInstance._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[index], // removeAtIndices
);
}
UIManager.setChildren(
parentInstance._nativeTag, // containerTag
nativeTags, // reactTags
);
export function removeChildFromContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
recursivelyUncacheFiberNode(child);
UIManager.manageChildren(
parentInstance, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[0], // removeAtIndices
);
}
return false;
},
getRootHostContext(rootContainerInstance: Container): HostContext {
return {isInAParentText: false};
},
getChildHostContext(
parentHostContext: HostContext,
type: string,
): HostContext {
const prevIsInAParentText = parentHostContext.isInAParentText;
const isInAParentText =
type === 'AndroidTextInput' || // Android
type === 'RCTMultilineTextInputView' || // iOS
type === 'RCTSinglelineTextInputView' || // iOS
type === 'RCTText' ||
type === 'RCTVirtualText';
if (prevIsInAParentText !== isInAParentText) {
return {isInAParentText};
} else {
return parentHostContext;
}
},
getPublicInstance(instance: Instance): * {
return instance;
},
now: ReactNativeFrameScheduling.now,
isPrimaryRenderer: true,
prepareForCommit(): void {
// Noop
},
prepareUpdate(
instance: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Object {
return emptyObject;
},
resetAfterCommit(): void {
// Noop
},
scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},
shouldSetTextContent(type: string, props: Props): boolean {
// TODO (bvaughn) Revisit this decision.
// Always returning false simplifies the createInstance() implementation,
// But creates an additional child Fiber for raw text children.
// No additional native views are created though.
// It's not clear to me which is better so I'm deferring for now.
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
return false;
},
mutation: {
appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
const childTag = typeof child === 'number' ? child : child._nativeTag;
const children = parentInstance._children;
const index = children.indexOf(child);
if (index >= 0) {
children.splice(index, 1);
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[index], // moveFromIndices
[children.length - 1], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[children.length - 1], // addAtIndices
[], // removeAtIndices
);
}
},
appendChildToContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
const childTag = typeof child === 'number' ? child : child._nativeTag;
UIManager.setChildren(
parentInstance, // containerTag
[childTag], // reactTags
);
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
UIManager.updateView(
textInstance, // reactTag
'RCTRawText', // viewName
{text: newText}, // props
);
},
commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayloadTODO: Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
const viewConfig = instance.viewConfig;
updateFiberProps(instance._nativeTag, newProps);
const updatePayload = ReactNativeAttributePayload.diff(
oldProps,
newProps,
viewConfig.validAttributes,
);
// Avoid the overhead of bridge calls if there's no update.
// This is an expensive no-op for Android, and causes an unnecessary
// view invalidation for certain components (eg RCTTextInput) on iOS.
if (updatePayload != null) {
UIManager.updateView(
instance._nativeTag, // reactTag
viewConfig.uiViewClassName, // viewName
updatePayload, // props
);
}
},
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
const children = (parentInstance: any)._children;
const index = children.indexOf(child);
// Move existing child or add new child?
if (index >= 0) {
children.splice(index, 1);
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[index], // moveFromIndices
[beforeChildIndex], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
const childTag = typeof child === 'number' ? child : child._nativeTag;
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[beforeChildIndex], // addAtIndices
[], // removeAtIndices
);
}
},
insertInContainerBefore(
parentInstance: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
// TODO (bvaughn): Remove this check when...
// We create a wrapper object for the container in ReactNative render()
// Or we refactor to remove wrapper objects entirely.
// For more info on pros/cons see PR #8560 description.
invariant(
typeof parentInstance !== 'number',
'Container does not support insertBefore operation',
);
},
removeChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
recursivelyUncacheFiberNode(child);
const children = parentInstance._children;
const index = children.indexOf(child);
children.splice(index, 1);
UIManager.manageChildren(
parentInstance._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[index], // removeAtIndices
);
},
removeChildFromContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
recursivelyUncacheFiberNode(child);
UIManager.manageChildren(
parentInstance, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[0], // removeAtIndices
);
},
resetTextContent(instance: Instance): void {
// Noop
},
},
};
export default ReactNativeHostConfig;
export function resetTextContent(instance: Instance): void {
// Noop
}
+2 -5
View File
@@ -12,7 +12,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
import './ReactNativeInjection';
import ReactFiberReconciler from 'react-reconciler';
import * as ReactNativeFiberRenderer from 'react-reconciler/inline.native';
import * as ReactPortal from 'shared/ReactPortal';
import * as ReactGenericBatching from 'events/ReactGenericBatching';
import ReactVersion from 'shared/ReactVersion';
@@ -21,7 +21,6 @@ import UIManager from 'UIManager';
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
import ReactNativeHostConfig from './ReactNativeHostConfig';
import NativeMethodsMixin from './NativeMethodsMixin';
import ReactNativeComponent from './ReactNativeComponent';
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
@@ -31,8 +30,6 @@ import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
import getComponentName from 'shared/getComponentName';
import warning from 'fbjs/lib/warning';
const ReactNativeFiberRenderer = ReactFiberReconciler(ReactNativeHostConfig);
const findHostInstance = ReactNativeFiberRenderer.findHostInstance;
function findNodeHandle(componentOrHandle: any): ?number {
@@ -71,7 +68,7 @@ function findNodeHandle(componentOrHandle: any): ?number {
}
if (hostInstance.canonical) {
// Fabric
return hostInstance.canonical._nativeTag;
return (hostInstance.canonical: any)._nativeTag;
}
return hostInstance._nativeTag;
}
+2
View File
@@ -14,9 +14,11 @@
* environment.
*/
import ReactFiberReconciler from 'react-reconciler';
import createReactNoop from './createReactNoop';
const ReactNoop = createReactNoop(
ReactFiberReconciler, // reconciler
true, // useMutation
);
@@ -14,9 +14,11 @@
* environment.
*/
import ReactFiberPersistentReconciler from 'react-reconciler/persistent';
import createReactNoop from './createReactNoop';
const ReactNoopPersistent = createReactNoop(
ReactFiberPersistentReconciler, // reconciler
false, // useMutation
);
+82 -79
View File
@@ -18,7 +18,6 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
import type {ReactNodeList} from 'shared/ReactTypes';
import ReactFiberReconciler from 'react-reconciler';
import * as ReactPortal from 'shared/ReactPortal';
import emptyObject from 'fbjs/lib/emptyObject';
import expect from 'expect';
@@ -33,7 +32,7 @@ type Instance = {|
|};
type TextInstance = {|text: string, id: number|};
function createReactNoop(useMutation: boolean) {
function createReactNoop(reconciler: Function, useMutation: boolean) {
const UPDATE_SIGNAL = {};
let scheduledCallback = null;
@@ -187,101 +186,105 @@ function createReactNoop(useMutation: boolean) {
},
isPrimaryRenderer: true,
supportsHydration: false,
};
const hostConfig = useMutation
? {
...sharedHostConfig,
mutation: {
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');
}
instance.prop = newProps.prop;
},
supportsMutation: true,
supportsPersistence: false,
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
},
appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
resetTextContent(instance: Instance): void {},
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');
}
instance.prop = newProps.prop;
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
},
appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
resetTextContent(instance: Instance): void {},
}
: {
...sharedHostConfig,
persistence: {
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 : [],
prop: newProps.prop,
};
Object.defineProperty(clone, 'id', {
value: clone.id,
enumerable: false,
});
return clone;
},
supportsMutation: false,
supportsPersistence: true,
createContainerChildSet(
container: Container,
): Array<Instance | TextInstance> {
return [];
},
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 : [],
prop: newProps.prop,
};
Object.defineProperty(clone, 'id', {
value: clone.id,
enumerable: false,
});
return clone;
},
appendChildToContainerChildSet(
childSet: Array<Instance | TextInstance>,
child: Instance | TextInstance,
): void {
childSet.push(child);
},
createContainerChildSet(
container: Container,
): Array<Instance | TextInstance> {
return [];
},
finalizeContainerChildren(
container: Container,
newChildren: Array<Instance | TextInstance>,
): void {},
appendChildToContainerChildSet(
childSet: Array<Instance | TextInstance>,
child: Instance | TextInstance,
): void {
childSet.push(child);
},
replaceContainerChildren(
container: Container,
newChildren: Array<Instance | TextInstance>,
): void {
container.children = newChildren;
},
finalizeContainerChildren(
container: Container,
newChildren: Array<Instance | TextInstance>,
): void {},
replaceContainerChildren(
container: Container,
newChildren: Array<Instance | TextInstance>,
): void {
container.children = newChildren;
},
};
const NoopRenderer = ReactFiberReconciler(hostConfig);
const NoopRenderer = reconciler(hostConfig);
const rootContainers = new Map();
const roots = new Map();
+12 -9
View File
@@ -3,18 +3,21 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
// This entry point is intentionally not typed. It exists only for third-party
// renderers. The renderers we ship (such as React DOM) instead import a named
// "inline" entry point (for example, `react-reconciler/inline.dom`). It uses
// the same code, but the Flow configuration redirects the host config to its
// real implementation so we can check it against exact intended host types.
//
// Only one renderer (the one you passed to `yarn flow <renderer>`) is fully
// type-checked at any given time. The Flow config maps the
// `react-reconciler/inline.<renderer>` import (which is *not* Flow typed) to
// `react-reconciler/inline-typed` (which *is*) for the current renderer.
// On CI, we run Flow checks for each renderer separately.
// TODO: bundle Flow types with the package.
export type {
HostConfig,
Deadline,
Reconciler,
} from './src/ReactFiberReconciler';
'use strict';
const ReactFiberReconciler = require('./src/ReactFiberReconciler');
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
// This file must have the Flow annotation.
//
// This is the Flow-typed entry point for the reconciler. It should not be
// imported directly in code. Instead, our Flow configuration uses this entry
// point for the currently checked renderer (the one you passed to `yarn flow`).
//
// For example, if you run `yarn flow dom`, `react-reconciler/inline.dom` points
// to this module (and thus will be considered Flow-typed). But other renderers
// (e.g. `react-test-renderer`) will see reconciler as untyped during the check.
//
// We can't make all entry points typed at the same time because different
// renderers have different host config types. So we check them one by one.
// We run Flow on all renderers on CI.
export * from './src/ReactFiberReconciler';
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// This file intentionally does *not* have the Flow annotation.
// Don't add it. See `./inline-typed.js` for an explanation.
export * from './src/ReactFiberReconciler';
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// This file intentionally does *not* have the Flow annotation.
// Don't add it. See `./inline-typed.js` for an explanation.
export * from './src/ReactFiberReconciler';
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// This file intentionally does *not* have the Flow annotation.
// Don't add it. See `./inline-typed.js` for an explanation.
export * from './src/ReactFiberReconciler';
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// This file intentionally does *not* have the Flow annotation.
// Don't add it. See `./inline-typed.js` for an explanation.
export * from './src/ReactFiberReconciler';
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// This file intentionally does *not* have the Flow annotation.
// Don't add it. See `./inline-typed.js` for an explanation.
export * from './src/ReactFiberReconciler';
-2
View File
@@ -3,8 +3,6 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+434 -462
View File
@@ -7,22 +7,20 @@
* @flow
*/
import type {HostConfig} from 'react-reconciler';
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {HostContext} from './ReactFiberHostContext';
import type {LegacyContext} from './ReactFiberContext';
import type {NewContext} from './ReactFiberNewContext';
import type {HydrationContext} from './ReactFiberHydrationContext';
import type {FiberRoot} from './ReactFiberRoot';
import type {ProfilerTimer} from './ReactProfilerTimer';
import type {
Instance,
Type,
Props,
UpdatePayload,
Container,
ChildSet,
HostContext,
} from './ReactFiberHostConfig';
import {
enableMutatingReconciler,
enablePersistentReconciler,
enableNoopReconciler,
enableProfilerTimer,
} from 'shared/ReactFeatureFlags';
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
import {
IndeterminateComponent,
FunctionalComponent,
@@ -42,63 +40,129 @@ import {
import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect';
import invariant from 'fbjs/lib/invariant';
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
hostContext: HostContext<C, CX>,
legacyContext: LegacyContext,
newContext: NewContext,
hydrationContext: HydrationContext<C, CX>,
profilerTimer: ProfilerTimer,
) {
const {
createInstance,
createTextInstance,
appendInitialChild,
finalizeInitialChildren,
prepareUpdate,
mutation,
persistence,
} = config;
import {
createInstance,
createTextInstance,
appendInitialChild,
finalizeInitialChildren,
prepareUpdate,
supportsMutation,
supportsPersistence,
cloneInstance,
createContainerChildSet,
appendChildToContainerChildSet,
finalizeContainerChildren,
} from './ReactFiberHostConfig';
import {
getRootHostContainer,
popHostContext,
getHostContext,
popHostContainer,
} from './ReactFiberHostContext';
import {recordElapsedActualRenderTime} from './ReactProfilerTimer';
import {
popContextProvider as popLegacyContextProvider,
popTopLevelContextObject as popTopLevelLegacyContextObject,
} from './ReactFiberContext';
import {popProvider} from './ReactFiberNewContext';
import {
prepareToHydrateHostInstance,
prepareToHydrateHostTextInstance,
popHydrationState,
} from './ReactFiberHydrationContext';
const {
getRootHostContainer,
popHostContext,
getHostContext,
popHostContainer,
} = hostContext;
function markUpdate(workInProgress: Fiber) {
// Tag the fiber with an update effect. This turns a Placement into
// a PlacementAndUpdate.
workInProgress.effectTag |= Update;
}
const {recordElapsedActualRenderTime} = profilerTimer;
function markRef(workInProgress: Fiber) {
workInProgress.effectTag |= Ref;
}
const {
popContextProvider: popLegacyContextProvider,
popTopLevelContextObject: popTopLevelLegacyContextObject,
} = legacyContext;
const {popProvider} = newContext;
const {
prepareToHydrateHostInstance,
prepareToHydrateHostTextInstance,
popHydrationState,
} = hydrationContext;
function markUpdate(workInProgress: Fiber) {
// Tag the fiber with an update effect. This turns a Placement into
// a PlacementAndUpdate.
workInProgress.effectTag |= Update;
function appendAllChildren(parent: Instance, workInProgress: Fiber) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function markRef(workInProgress: Fiber) {
workInProgress.effectTag |= Ref;
}
let updateHostContainer;
let updateHostComponent;
let updateHostText;
if (supportsMutation) {
// Mutation mode
function appendAllChildren(parent: I, workInProgress: Fiber) {
updateHostContainer = function(workInProgress: Fiber) {
// Noop
};
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
updatePayload: null | UpdatePayload,
type: Type,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
currentHostContext: HostContext,
) {
// TODO: Type this specific to this type of component.
workInProgress.updateQueue = (updatePayload: any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update. All the work is done in commitWork.
if (updatePayload) {
markUpdate(workInProgress);
}
};
updateHostText = function(
current: Fiber,
workInProgress: Fiber,
oldText: string,
newText: string,
) {
// If the text differs, mark it as an update. All the work in done in commitWork.
if (oldText !== newText) {
markUpdate(workInProgress);
}
};
} else if (supportsPersistence) {
// Persistent host tree mode
// An unfortunate fork of appendAllChildren because we have two different parent types.
const appendAllChildrenToContainer = function(
containerChildSet: ChildSet,
workInProgress: Fiber,
) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
appendChildToContainerChildSet(containerChildSet, node.stateNode);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
@@ -120,142 +184,249 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
node.sibling.return = node.return;
node = node.sibling;
}
}
let updateHostContainer;
let updateHostComponent;
let updateHostText;
if (mutation) {
if (enableMutatingReconciler) {
// Mutation mode
updateHostContainer = function(workInProgress: Fiber) {
// Noop
};
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
updatePayload: null | PL,
type: T,
oldProps: P,
newProps: P,
rootContainerInstance: C,
currentHostContext: CX,
) {
// TODO: Type this specific to this type of component.
workInProgress.updateQueue = (updatePayload: any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update. All the work is done in commitWork.
if (updatePayload) {
markUpdate(workInProgress);
}
};
updateHostText = function(
current: Fiber,
workInProgress: Fiber,
oldText: string,
newText: string,
) {
// If the text differs, mark it as an update. All the work in done in commitWork.
if (oldText !== newText) {
markUpdate(workInProgress);
}
};
};
updateHostContainer = function(workInProgress: Fiber) {
const portalOrRoot: {
containerInfo: Container,
pendingChildren: ChildSet,
} =
workInProgress.stateNode;
const childrenUnchanged = workInProgress.firstEffect === null;
if (childrenUnchanged) {
// No changes, just reuse the existing instance.
} else {
invariant(false, 'Mutating reconciler is disabled.');
const container = portalOrRoot.containerInfo;
let newChildSet = createContainerChildSet(container);
// If children might have changed, we have to add them all to the set.
appendAllChildrenToContainer(newChildSet, workInProgress);
portalOrRoot.pendingChildren = newChildSet;
// Schedule an update on the container to swap out the container.
markUpdate(workInProgress);
finalizeContainerChildren(container, newChildSet);
}
} else if (persistence) {
if (enablePersistentReconciler) {
// Persistent host tree mode
const {
cloneInstance,
createContainerChildSet,
appendChildToContainerChildSet,
finalizeContainerChildren,
} = persistence;
};
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
updatePayload: null | UpdatePayload,
type: Type,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
currentHostContext: HostContext,
) {
// If there are no effects associated with this node, then none of our children had any updates.
// This guarantees that we can reuse all of them.
const childrenUnchanged = workInProgress.firstEffect === null;
const currentInstance = current.stateNode;
if (childrenUnchanged && updatePayload === null) {
// No changes, just reuse the existing instance.
// Note that this might release a previous clone.
workInProgress.stateNode = currentInstance;
} else {
let recyclableInstance = workInProgress.stateNode;
let newInstance = cloneInstance(
currentInstance,
updatePayload,
type,
oldProps,
newProps,
workInProgress,
childrenUnchanged,
recyclableInstance,
);
if (
finalizeInitialChildren(
newInstance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = newInstance;
if (childrenUnchanged) {
// If there are no other effects in this tree, we need to flag this node as having one.
// Even though we're not going to use it for anything.
// Otherwise parents won't know that there are new children to propagate upwards.
markUpdate(workInProgress);
} else {
// If children might have changed, we have to add them all to the set.
appendAllChildren(newInstance, workInProgress);
}
}
};
updateHostText = function(
current: Fiber,
workInProgress: Fiber,
oldText: string,
newText: string,
) {
if (oldText !== newText) {
// If the text content differs, we'll create a new text instance for it.
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// We'll have to mark it as having an effect, even though we won't use the effect for anything.
// This lets the parents know that at least one of their children has changed.
markUpdate(workInProgress);
}
};
} else {
// No host operations
updateHostContainer = function(workInProgress: Fiber) {
// Noop
};
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
updatePayload: null | UpdatePayload,
type: Type,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
currentHostContext: HostContext,
) {
// Noop
};
updateHostText = function(
current: Fiber,
workInProgress: Fiber,
oldText: string,
newText: string,
) {
// Noop
};
}
// An unfortunate fork of appendAllChildren because we have two different parent types.
const appendAllChildrenToContainer = function(
containerChildSet: CC,
workInProgress: Fiber,
) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendChildToContainerChildSet(containerChildSet, node.stateNode);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case FunctionalComponent:
return null;
case ClassComponent: {
// We are leaving this subtree, so pop context if any.
popLegacyContextProvider(workInProgress);
return null;
}
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
// If we hydrated, pop so that we can delete any remaining children
// that weren't hydrated.
popHydrationState(workInProgress);
// This resets the hacky state to fix isMounted before committing.
// TODO: Delete this when we delete isMounted and findDOMNode.
workInProgress.effectTag &= ~Placement;
}
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
const oldProps = current.memoizedProps;
// If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
const instance: Instance = workInProgress.stateNode;
const currentHostContext = getHostContext();
// TODO: Experiencing an error where oldProps is null. Suggests a host
// component is hitting the resume path. Figure out why. Possibly
// related to `hidden`.
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
updateHostComponent(
current,
workInProgress,
updatePayload,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
};
updateHostContainer = function(workInProgress: Fiber) {
const portalOrRoot: {containerInfo: C, pendingChildren: CC} =
workInProgress.stateNode;
const childrenUnchanged = workInProgress.firstEffect === null;
if (childrenUnchanged) {
// No changes, just reuse the existing instance.
} else {
const container = portalOrRoot.containerInfo;
let newChildSet = createContainerChildSet(container);
// If children might have changed, we have to add them all to the set.
appendAllChildrenToContainer(newChildSet, workInProgress);
portalOrRoot.pendingChildren = newChildSet;
// Schedule an update on the container to swap out the container.
markUpdate(workInProgress);
finalizeContainerChildren(container, newChildSet);
}
};
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
updatePayload: null | PL,
type: T,
oldProps: P,
newProps: P,
rootContainerInstance: C,
currentHostContext: CX,
) {
// If there are no effects associated with this node, then none of our children had any updates.
// This guarantees that we can reuse all of them.
const childrenUnchanged = workInProgress.firstEffect === null;
const currentInstance = current.stateNode;
if (childrenUnchanged && updatePayload === null) {
// No changes, just reuse the existing instance.
// Note that this might release a previous clone.
workInProgress.stateNode = currentInstance;
} else {
let recyclableInstance = workInProgress.stateNode;
let newInstance = cloneInstance(
currentInstance,
updatePayload,
type,
oldProps,
newProps,
workInProgress,
childrenUnchanged,
recyclableInstance,
} else {
if (!newProps) {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}
const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
if (
prepareToHydrateHostInstance(
workInProgress,
rootContainerInstance,
currentHostContext,
)
) {
// If changes to the hydrated node needs to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
} else {
let instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
appendAllChildren(instance, workInProgress);
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (
finalizeInitialChildren(
newInstance,
instance,
type,
newProps,
rootContainerInstance,
@@ -264,289 +435,90 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = newInstance;
if (childrenUnchanged) {
// If there are no other effects in this tree, we need to flag this node as having one.
// Even though we're not going to use it for anything.
// Otherwise parents won't know that there are new children to propagate upwards.
markUpdate(workInProgress);
} else {
// If children might have changed, we have to add them all to the set.
appendAllChildren(newInstance, workInProgress);
}
workInProgress.stateNode = instance;
}
};
updateHostText = function(
current: Fiber,
workInProgress: Fiber,
oldText: string,
newText: string,
) {
if (oldText !== newText) {
// If the text content differs, we'll create a new text instance for it.
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
return null;
}
case HostText: {
let newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// We'll have to mark it as having an effect, even though we won't use the effect for anything.
// This lets the parents know that at least one of their children has changed.
markUpdate(workInProgress);
}
};
} else {
invariant(false, 'Persistent reconciler is disabled.');
}
} else {
if (enableNoopReconciler) {
// No host operations
updateHostContainer = function(workInProgress: Fiber) {
// Noop
};
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
updatePayload: null | PL,
type: T,
oldProps: P,
newProps: P,
rootContainerInstance: C,
currentHostContext: CX,
) {
// Noop
};
updateHostText = function(
current: Fiber,
workInProgress: Fiber,
oldText: string,
newText: string,
) {
// Noop
};
} else {
invariant(false, 'Noop reconciler is disabled.');
}
return null;
}
case ForwardRef:
return null;
case TimeoutComponent:
return null;
case Fragment:
return null;
case Mode:
return null;
case Profiler:
if (enableProfilerTimer) {
recordElapsedActualRenderTime(workInProgress);
}
return null;
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
return null;
case ContextProvider:
// Pop provider fiber
popProvider(workInProgress);
return null;
case ContextConsumer:
return null;
// Error cases
case IndeterminateComponent:
invariant(
false,
'An indeterminate component should have become determinate before ' +
'completing. This error is likely caused by a bug in React. Please ' +
'file an issue.',
);
// eslint-disable-next-line no-fallthrough
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case FunctionalComponent:
return null;
case ClassComponent: {
// We are leaving this subtree, so pop context if any.
popLegacyContextProvider(workInProgress);
return null;
}
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
// If we hydrated, pop so that we can delete any remaining children
// that weren't hydrated.
popHydrationState(workInProgress);
// This resets the hacky state to fix isMounted before committing.
// TODO: Delete this when we delete isMounted and findDOMNode.
workInProgress.effectTag &= ~Placement;
}
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
const oldProps = current.memoizedProps;
// If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
const instance: I = workInProgress.stateNode;
const currentHostContext = getHostContext();
// TODO: Experiencing an error where oldProps is null. Suggests a host
// component is hitting the resume path. Figure out why. Possibly
// related to `hidden`.
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
updateHostComponent(
current,
workInProgress,
updatePayload,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if (!newProps) {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}
const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
if (
prepareToHydrateHostInstance(
workInProgress,
rootContainerInstance,
currentHostContext,
)
) {
// If changes to the hydrated node needs to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
} else {
let instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
appendAllChildren(instance, workInProgress);
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = instance;
}
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
return null;
}
case HostText: {
let newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
}
return null;
}
case ForwardRef:
return null;
case TimeoutComponent:
return null;
case Fragment:
return null;
case Mode:
return null;
case Profiler:
if (enableProfilerTimer) {
recordElapsedActualRenderTime(workInProgress);
}
return null;
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
return null;
case ContextProvider:
// Pop provider fiber
popProvider(workInProgress);
return null;
case ContextConsumer:
return null;
// Error cases
case IndeterminateComponent:
invariant(
false,
'An indeterminate component should have become determinate before ' +
'completing. This error is likely caused by a bug in React. Please ' +
'file an issue.',
);
// eslint-disable-next-line no-fallthrough
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
}
return {
completeWork,
};
}
export {completeWork};
+276 -303
View File
@@ -8,7 +8,7 @@
*/
import type {Fiber} from './ReactFiber';
import type {StackCursor, Stack} from './ReactFiberStack';
import type {StackCursor} from './ReactFiberStack';
import {isFiberMounted} from 'react-reconciler/reflection';
import {ClassComponent, HostRoot} from 'shared/ReactTypeOfWork';
@@ -20,6 +20,7 @@ import checkPropTypes from 'prop-types/checkPropTypes';
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
import {createCursor, push, pop} from './ReactFiberStack';
let warnedAboutMissingGetChildContext;
@@ -27,308 +28,280 @@ if (__DEV__) {
warnedAboutMissingGetChildContext = {};
}
export type LegacyContext = {
getUnmaskedContext(workInProgress: Fiber): Object,
cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
): void,
getMaskedContext(workInProgress: Fiber, unmaskedContext: Object): Object,
hasContextChanged(): boolean,
isContextConsumer(fiber: Fiber): boolean,
isContextProvider(fiber: Fiber): boolean,
popContextProvider(fiber: Fiber): void,
popTopLevelContextObject(fiber: Fiber): void,
pushTopLevelContextObject(
fiber: Fiber,
context: Object,
didChange: boolean,
): void,
processChildContext(fiber: Fiber, parentContext: Object): Object,
pushContextProvider(workInProgress: Fiber): boolean,
invalidateContextProvider(workInProgress: Fiber, didChange: boolean): void,
findCurrentUnmaskedContext(fiber: Fiber): Object,
};
// A cursor to the current merged context object on the stack.
let contextStackCursor: StackCursor<Object> = createCursor(emptyObject);
// A cursor to a boolean indicating whether the context has changed.
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
// Keep track of the previous context object that was on the stack.
// We use this to get access to the parent context after we have already
// pushed the next context provider, and now need to merge their contexts.
let previousContext: Object = emptyObject;
export default function(stack: Stack): LegacyContext {
const {createCursor, push, pop} = stack;
// A cursor to the current merged context object on the stack.
let contextStackCursor: StackCursor<Object> = createCursor(emptyObject);
// A cursor to a boolean indicating whether the context has changed.
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
// Keep track of the previous context object that was on the stack.
// We use this to get access to the parent context after we have already
// pushed the next context provider, and now need to merge their contexts.
let previousContext: Object = emptyObject;
function getUnmaskedContext(workInProgress: Fiber): Object {
const hasOwnContext = isContextProvider(workInProgress);
if (hasOwnContext) {
// If the fiber is a context provider itself, when we read its context
// we have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}
return contextStackCursor.current;
function getUnmaskedContext(workInProgress: Fiber): Object {
const hasOwnContext = isContextProvider(workInProgress);
if (hasOwnContext) {
// If the fiber is a context provider itself, when we read its context
// we have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}
function cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
) {
const instance = workInProgress.stateNode;
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
function getMaskedContext(workInProgress: Fiber, unmaskedContext: Object) {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyObject;
}
// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
const instance = workInProgress.stateNode;
if (
instance &&
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
) {
return instance.__reactInternalMemoizedMaskedChildContext;
}
const context = {};
for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
if (__DEV__) {
const name = getComponentName(workInProgress) || 'Unknown';
checkPropTypes(
contextTypes,
context,
'context',
name,
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
);
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// Context is created before the class component is instantiated so check for instance.
if (instance) {
cacheContext(workInProgress, unmaskedContext, context);
}
return context;
}
function hasContextChanged(): boolean {
return didPerformWorkStackCursor.current;
}
function isContextConsumer(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
}
function isContextProvider(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
}
function popContextProvider(fiber: Fiber): void {
if (!isContextProvider(fiber)) {
return;
}
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
function popTopLevelContextObject(fiber: Fiber) {
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
function pushTopLevelContextObject(
fiber: Fiber,
context: Object,
didChange: boolean,
): void {
invariant(
contextStackCursor.cursor == null,
'Unexpected context found on stack. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
}
function processChildContext(fiber: Fiber, parentContext: Object): Object {
const instance = fiber.stateNode;
const childContextTypes = fiber.type.childContextTypes;
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
if (typeof instance.getChildContext !== 'function') {
if (__DEV__) {
const componentName = getComponentName(fiber) || 'Unknown';
if (!warnedAboutMissingGetChildContext[componentName]) {
warnedAboutMissingGetChildContext[componentName] = true;
warning(
false,
'%s.childContextTypes is specified but there is no getChildContext() method ' +
'on the instance. You can either define getChildContext() on %s or remove ' +
'childContextTypes from it.',
componentName,
componentName,
);
}
}
return parentContext;
}
let childContext;
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
}
startPhaseTimer(fiber, 'getChildContext');
childContext = instance.getChildContext();
stopPhaseTimer();
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase(null);
}
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber) || 'Unknown',
contextKey,
);
}
if (__DEV__) {
const name = getComponentName(fiber) || 'Unknown';
checkPropTypes(
childContextTypes,
childContext,
'child context',
name,
// In practice, there is one case in which we won't get a stack. It's when
// somebody calls unstable_renderSubtreeIntoContainer() and we process
// context from the parent component instance. The stack will be missing
// because it's outside of the reconciliation, and so the pointer has not
// been set. This is rare and doesn't matter. We'll also remove that API.
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
);
}
return {...parentContext, ...childContext};
}
function pushContextProvider(workInProgress: Fiber): boolean {
if (!isContextProvider(workInProgress)) {
return false;
}
const instance = workInProgress.stateNode;
// We push the context as early as possible to ensure stack integrity.
// If the instance does not exist yet, we will push null at first,
// and replace it on the stack later when invalidating the context.
const memoizedMergedChildContext =
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
emptyObject;
// Remember the parent context so we can merge with it later.
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
previousContext = contextStackCursor.current;
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(
didPerformWorkStackCursor,
didPerformWorkStackCursor.current,
workInProgress,
);
return true;
}
function invalidateContextProvider(
workInProgress: Fiber,
didChange: boolean,
): void {
const instance = workInProgress.stateNode;
invariant(
instance,
'Expected to have an instance by this point. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
if (didChange) {
// Merge parent and own context.
// Skip this if we're not updating due to sCU.
// This avoids unnecessarily recomputing memoized values.
const mergedContext = processChildContext(
workInProgress,
previousContext,
);
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
} else {
pop(didPerformWorkStackCursor, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
}
}
function findCurrentUnmaskedContext(fiber: Fiber): Object {
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
// makes sense elsewhere
invariant(
isFiberMounted(fiber) && fiber.tag === ClassComponent,
'Expected subtree parent to be a mounted class component. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
let node: Fiber = fiber;
while (node.tag !== HostRoot) {
if (isContextProvider(node)) {
return node.stateNode.__reactInternalMemoizedMergedChildContext;
}
const parent = node.return;
invariant(
parent,
'Found unexpected detached subtree parent. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
node = parent;
}
return node.stateNode.context;
}
return {
getUnmaskedContext,
cacheContext,
getMaskedContext,
hasContextChanged,
isContextConsumer,
isContextProvider,
popContextProvider,
popTopLevelContextObject,
pushTopLevelContextObject,
processChildContext,
pushContextProvider,
invalidateContextProvider,
findCurrentUnmaskedContext,
};
return contextStackCursor.current;
}
function cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
): void {
const instance = workInProgress.stateNode;
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
function getMaskedContext(
workInProgress: Fiber,
unmaskedContext: Object,
): Object {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyObject;
}
// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
const instance = workInProgress.stateNode;
if (
instance &&
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
) {
return instance.__reactInternalMemoizedMaskedChildContext;
}
const context = {};
for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
if (__DEV__) {
const name = getComponentName(workInProgress) || 'Unknown';
checkPropTypes(
contextTypes,
context,
'context',
name,
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
);
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// Context is created before the class component is instantiated so check for instance.
if (instance) {
cacheContext(workInProgress, unmaskedContext, context);
}
return context;
}
function hasContextChanged(): boolean {
return didPerformWorkStackCursor.current;
}
function isContextConsumer(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
}
function isContextProvider(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
}
function popContextProvider(fiber: Fiber): void {
if (!isContextProvider(fiber)) {
return;
}
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
function popTopLevelContextObject(fiber: Fiber): void {
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
function pushTopLevelContextObject(
fiber: Fiber,
context: Object,
didChange: boolean,
): void {
invariant(
contextStackCursor.cursor == null,
'Unexpected context found on stack. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
}
function processChildContext(fiber: Fiber, parentContext: Object): Object {
const instance = fiber.stateNode;
const childContextTypes = fiber.type.childContextTypes;
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
if (typeof instance.getChildContext !== 'function') {
if (__DEV__) {
const componentName = getComponentName(fiber) || 'Unknown';
if (!warnedAboutMissingGetChildContext[componentName]) {
warnedAboutMissingGetChildContext[componentName] = true;
warning(
false,
'%s.childContextTypes is specified but there is no getChildContext() method ' +
'on the instance. You can either define getChildContext() on %s or remove ' +
'childContextTypes from it.',
componentName,
componentName,
);
}
}
return parentContext;
}
let childContext;
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
}
startPhaseTimer(fiber, 'getChildContext');
childContext = instance.getChildContext();
stopPhaseTimer();
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase(null);
}
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber) || 'Unknown',
contextKey,
);
}
if (__DEV__) {
const name = getComponentName(fiber) || 'Unknown';
checkPropTypes(
childContextTypes,
childContext,
'child context',
name,
// In practice, there is one case in which we won't get a stack. It's when
// somebody calls unstable_renderSubtreeIntoContainer() and we process
// context from the parent component instance. The stack will be missing
// because it's outside of the reconciliation, and so the pointer has not
// been set. This is rare and doesn't matter. We'll also remove that API.
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
);
}
return {...parentContext, ...childContext};
}
function pushContextProvider(workInProgress: Fiber): boolean {
if (!isContextProvider(workInProgress)) {
return false;
}
const instance = workInProgress.stateNode;
// We push the context as early as possible to ensure stack integrity.
// If the instance does not exist yet, we will push null at first,
// and replace it on the stack later when invalidating the context.
const memoizedMergedChildContext =
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
emptyObject;
// Remember the parent context so we can merge with it later.
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
previousContext = contextStackCursor.current;
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(
didPerformWorkStackCursor,
didPerformWorkStackCursor.current,
workInProgress,
);
return true;
}
function invalidateContextProvider(
workInProgress: Fiber,
didChange: boolean,
): void {
const instance = workInProgress.stateNode;
invariant(
instance,
'Expected to have an instance by this point. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
if (didChange) {
// Merge parent and own context.
// Skip this if we're not updating due to sCU.
// This avoids unnecessarily recomputing memoized values.
const mergedContext = processChildContext(workInProgress, previousContext);
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
} else {
pop(didPerformWorkStackCursor, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
}
}
function findCurrentUnmaskedContext(fiber: Fiber): Object {
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
// makes sense elsewhere
invariant(
isFiberMounted(fiber) && fiber.tag === ClassComponent,
'Expected subtree parent to be a mounted class component. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
let node: Fiber = fiber;
while (node.tag !== HostRoot) {
if (isContextProvider(node)) {
return node.stateNode.__reactInternalMemoizedMergedChildContext;
}
const parent = node.return;
invariant(
parent,
'Found unexpected detached subtree parent. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
node = parent;
}
return node.stateNode.context;
}
export {
getUnmaskedContext,
cacheContext,
getMaskedContext,
hasContextChanged,
isContextConsumer,
isContextProvider,
popContextProvider,
popTopLevelContextObject,
pushTopLevelContextObject,
processChildContext,
pushContextProvider,
invalidateContextProvider,
findCurrentUnmaskedContext,
};
+20
View File
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant';
// We expect that our Rollup, Jest, and Flow configurations
// always shim this module with the corresponding host config
// (either provided by a renderer, or a generic shim for npm).
//
// We should never resolve to this file, but it exists to make
// sure that if we *do* accidentally break the configuration,
// the failure isn't silent.
invariant(false, 'This module must be shimmed by a specific renderer.');
+94 -106
View File
@@ -7,119 +7,107 @@
* @flow
*/
import type {HostConfig} from 'react-reconciler';
import type {Fiber} from './ReactFiber';
import type {StackCursor, Stack} from './ReactFiberStack';
import type {StackCursor} from './ReactFiberStack';
import type {Container, HostContext} from './ReactFiberHostConfig';
import invariant from 'fbjs/lib/invariant';
import {getChildHostContext, getRootHostContext} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack';
declare class NoContextT {}
const NO_CONTEXT: NoContextT = ({}: any);
export type HostContext<C, CX> = {
getHostContext(): CX,
getRootHostContainer(): C,
popHostContainer(fiber: Fiber): void,
popHostContext(fiber: Fiber): void,
pushHostContainer(fiber: Fiber, container: C): void,
pushHostContext(fiber: Fiber): void,
};
let contextStackCursor: StackCursor<HostContext | NoContextT> = createCursor(
NO_CONTEXT,
);
let contextFiberStackCursor: StackCursor<Fiber | NoContextT> = createCursor(
NO_CONTEXT,
);
let rootInstanceStackCursor: StackCursor<Container | NoContextT> = createCursor(
NO_CONTEXT,
);
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
stack: Stack,
): HostContext<C, CX> {
const {getChildHostContext, getRootHostContext} = config;
const {createCursor, push, pop} = stack;
let contextStackCursor: StackCursor<CX | NoContextT> = createCursor(
NO_CONTEXT,
function requiredContext<Value>(c: Value | NoContextT): Value {
invariant(
c !== NO_CONTEXT,
'Expected host context to exist. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
let contextFiberStackCursor: StackCursor<Fiber | NoContextT> = createCursor(
NO_CONTEXT,
);
let rootInstanceStackCursor: StackCursor<C | NoContextT> = createCursor(
NO_CONTEXT,
);
function requiredContext<Value>(c: Value | NoContextT): Value {
invariant(
c !== NO_CONTEXT,
'Expected host context to exist. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
return (c: any);
}
function getRootHostContainer(): C {
const rootInstance = requiredContext(rootInstanceStackCursor.current);
return rootInstance;
}
function pushHostContainer(fiber: Fiber, nextRootInstance: C) {
// Push current root instance onto the stack;
// This allows us to reset root when portals are popped.
push(rootInstanceStackCursor, nextRootInstance, fiber);
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
// Finally, we need to push the host context to the stack.
// However, we can't just call getRootHostContext() and push it because
// we'd have a different number of entries on the stack depending on
// whether getRootHostContext() throws somewhere in renderer code or not.
// So we push an empty value first. This lets us safely unwind on errors.
push(contextStackCursor, NO_CONTEXT, fiber);
const nextRootContext = getRootHostContext(nextRootInstance);
// Now that we know this function doesn't throw, replace it.
pop(contextStackCursor, fiber);
push(contextStackCursor, nextRootContext, fiber);
}
function popHostContainer(fiber: Fiber) {
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
pop(rootInstanceStackCursor, fiber);
}
function getHostContext(): CX {
const context = requiredContext(contextStackCursor.current);
return context;
}
function pushHostContext(fiber: Fiber): void {
const rootInstance: C = requiredContext(rootInstanceStackCursor.current);
const context: CX = requiredContext(contextStackCursor.current);
const nextContext = getChildHostContext(context, fiber.type, rootInstance);
// Don't push this Fiber's context unless it's unique.
if (context === nextContext) {
return;
}
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor, nextContext, fiber);
}
function popHostContext(fiber: Fiber): void {
// Do not pop unless this Fiber provided the current context.
// pushHostContext() only pushes Fibers that provide unique contexts.
if (contextFiberStackCursor.current !== fiber) {
return;
}
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
}
return {
getHostContext,
getRootHostContainer,
popHostContainer,
popHostContext,
pushHostContainer,
pushHostContext,
};
return (c: any);
}
function getRootHostContainer(): Container {
const rootInstance = requiredContext(rootInstanceStackCursor.current);
return rootInstance;
}
function pushHostContainer(fiber: Fiber, nextRootInstance: Container) {
// Push current root instance onto the stack;
// This allows us to reset root when portals are popped.
push(rootInstanceStackCursor, nextRootInstance, fiber);
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
// Finally, we need to push the host context to the stack.
// However, we can't just call getRootHostContext() and push it because
// we'd have a different number of entries on the stack depending on
// whether getRootHostContext() throws somewhere in renderer code or not.
// So we push an empty value first. This lets us safely unwind on errors.
push(contextStackCursor, NO_CONTEXT, fiber);
const nextRootContext = getRootHostContext(nextRootInstance);
// Now that we know this function doesn't throw, replace it.
pop(contextStackCursor, fiber);
push(contextStackCursor, nextRootContext, fiber);
}
function popHostContainer(fiber: Fiber) {
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
pop(rootInstanceStackCursor, fiber);
}
function getHostContext(): HostContext {
const context = requiredContext(contextStackCursor.current);
return context;
}
function pushHostContext(fiber: Fiber): void {
const rootInstance: Container = requiredContext(
rootInstanceStackCursor.current,
);
const context: HostContext = requiredContext(contextStackCursor.current);
const nextContext = getChildHostContext(context, fiber.type, rootInstance);
// Don't push this Fiber's context unless it's unique.
if (context === nextContext) {
return;
}
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor, nextContext, fiber);
}
function popHostContext(fiber: Fiber): void {
// Do not pop unless this Fiber provided the current context.
// pushHostContext() only pushes Fibers that provide unique contexts.
if (contextFiberStackCursor.current !== fiber) {
return;
}
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
}
export {
getHostContext,
getRootHostContainer,
popHostContainer,
popHostContext,
pushHostContainer,
pushHostContext,
};
+324 -337
View File
@@ -7,380 +7,367 @@
* @flow
*/
import type {HostConfig} from 'react-reconciler';
import type {Fiber} from './ReactFiber';
import type {
Instance,
TextInstance,
HydratableInstance,
Container,
HostContext,
} from './ReactFiberHostConfig';
import {HostComponent, HostText, HostRoot} from 'shared/ReactTypeOfWork';
import {Deletion, Placement} from 'shared/ReactTypeOfSideEffect';
import invariant from 'fbjs/lib/invariant';
import {createFiberFromHostInstanceForDeletion} from './ReactFiber';
import {
shouldSetTextContent,
supportsHydration,
canHydrateInstance,
canHydrateTextInstance,
getNextHydratableSibling,
getFirstHydratableChild,
hydrateInstance,
hydrateTextInstance,
didNotMatchHydratedContainerTextInstance,
didNotMatchHydratedTextInstance,
didNotHydrateContainerInstance,
didNotHydrateInstance,
didNotFindHydratableContainerInstance,
didNotFindHydratableContainerTextInstance,
didNotFindHydratableInstance,
didNotFindHydratableTextInstance,
} from './ReactFiberHostConfig';
export type HydrationContext<C, CX> = {
enterHydrationState(fiber: Fiber): boolean,
resetHydrationState(): void,
tryToClaimNextHydratableInstance(fiber: Fiber): void,
prepareToHydrateHostInstance(
fiber: Fiber,
rootContainerInstance: C,
hostContext: CX,
): boolean,
prepareToHydrateHostTextInstance(fiber: Fiber): boolean,
popHydrationState(fiber: Fiber): boolean,
};
// The deepest Fiber on the stack involved in a hydration context.
// This may have been an insertion or a hydration.
let hydrationParentFiber: null | Fiber = null;
let nextHydratableInstance: null | HydratableInstance = null;
let isHydrating: boolean = false;
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
): HydrationContext<C, CX> {
const {shouldSetTextContent, hydration} = config;
function enterHydrationState(fiber: Fiber): boolean {
if (!supportsHydration) {
return false;
}
// If this doesn't have hydration mode.
if (!hydration) {
return {
enterHydrationState() {
return false;
},
resetHydrationState() {},
tryToClaimNextHydratableInstance() {},
prepareToHydrateHostInstance() {
invariant(
false,
'Expected prepareToHydrateHostInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
const parentInstance = fiber.stateNode.containerInfo;
nextHydratableInstance = getFirstHydratableChild(parentInstance);
hydrationParentFiber = fiber;
isHydrating = true;
return true;
}
function deleteHydratableInstance(
returnFiber: Fiber,
instance: HydratableInstance,
) {
if (__DEV__) {
switch (returnFiber.tag) {
case HostRoot:
didNotHydrateContainerInstance(
returnFiber.stateNode.containerInfo,
instance,
);
},
prepareToHydrateHostTextInstance() {
invariant(
false,
'Expected prepareToHydrateHostTextInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
break;
case HostComponent:
didNotHydrateInstance(
returnFiber.type,
returnFiber.memoizedProps,
returnFiber.stateNode,
instance,
);
},
popHydrationState(fiber: Fiber) {
return false;
},
};
}
const {
canHydrateInstance,
canHydrateTextInstance,
getNextHydratableSibling,
getFirstHydratableChild,
hydrateInstance,
hydrateTextInstance,
didNotMatchHydratedContainerTextInstance,
didNotMatchHydratedTextInstance,
didNotHydrateContainerInstance,
didNotHydrateInstance,
didNotFindHydratableContainerInstance,
didNotFindHydratableContainerTextInstance,
didNotFindHydratableInstance,
didNotFindHydratableTextInstance,
} = hydration;
// The deepest Fiber on the stack involved in a hydration context.
// This may have been an insertion or a hydration.
let hydrationParentFiber: null | Fiber = null;
let nextHydratableInstance: null | HI = null;
let isHydrating: boolean = false;
function enterHydrationState(fiber: Fiber) {
const parentInstance = fiber.stateNode.containerInfo;
nextHydratableInstance = getFirstHydratableChild(parentInstance);
hydrationParentFiber = fiber;
isHydrating = true;
return true;
}
function deleteHydratableInstance(returnFiber: Fiber, instance: I | TI) {
if (__DEV__) {
switch (returnFiber.tag) {
case HostRoot:
didNotHydrateContainerInstance(
returnFiber.stateNode.containerInfo,
instance,
);
break;
case HostComponent:
didNotHydrateInstance(
returnFiber.type,
returnFiber.memoizedProps,
returnFiber.stateNode,
instance,
);
break;
}
}
const childToDelete = createFiberFromHostInstanceForDeletion();
childToDelete.stateNode = instance;
childToDelete.return = returnFiber;
childToDelete.effectTag = Deletion;
// This might seem like it belongs on progressedFirstDeletion. However,
// these children are not part of the reconciliation list of children.
// Even if we abort and rereconcile the children, that will try to hydrate
// again and the nodes are still in the host tree so these will be
// recreated.
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
break;
}
}
function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
fiber.effectTag |= Placement;
if (__DEV__) {
switch (returnFiber.tag) {
case HostRoot: {
const parentContainer = returnFiber.stateNode.containerInfo;
switch (fiber.tag) {
case HostComponent:
const type = fiber.type;
const props = fiber.pendingProps;
didNotFindHydratableContainerInstance(
parentContainer,
type,
props,
);
break;
case HostText:
const text = fiber.pendingProps;
didNotFindHydratableContainerTextInstance(parentContainer, text);
break;
}
break;
const childToDelete = createFiberFromHostInstanceForDeletion();
childToDelete.stateNode = instance;
childToDelete.return = returnFiber;
childToDelete.effectTag = Deletion;
// This might seem like it belongs on progressedFirstDeletion. However,
// these children are not part of the reconciliation list of children.
// Even if we abort and rereconcile the children, that will try to hydrate
// again and the nodes are still in the host tree so these will be
// recreated.
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
}
function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
fiber.effectTag |= Placement;
if (__DEV__) {
switch (returnFiber.tag) {
case HostRoot: {
const parentContainer = returnFiber.stateNode.containerInfo;
switch (fiber.tag) {
case HostComponent:
const type = fiber.type;
const props = fiber.pendingProps;
didNotFindHydratableContainerInstance(parentContainer, type, props);
break;
case HostText:
const text = fiber.pendingProps;
didNotFindHydratableContainerTextInstance(parentContainer, text);
break;
}
case HostComponent: {
const parentType = returnFiber.type;
const parentProps = returnFiber.memoizedProps;
const parentInstance = returnFiber.stateNode;
switch (fiber.tag) {
case HostComponent:
const type = fiber.type;
const props = fiber.pendingProps;
didNotFindHydratableInstance(
parentType,
parentProps,
parentInstance,
type,
props,
);
break;
case HostText:
const text = fiber.pendingProps;
didNotFindHydratableTextInstance(
parentType,
parentProps,
parentInstance,
text,
);
break;
}
break;
}
default:
return;
break;
}
}
}
function tryHydrate(fiber, nextInstance) {
switch (fiber.tag) {
case HostComponent: {
const type = fiber.type;
const props = fiber.pendingProps;
const instance = canHydrateInstance(nextInstance, type, props);
if (instance !== null) {
fiber.stateNode = (instance: I);
return true;
const parentType = returnFiber.type;
const parentProps = returnFiber.memoizedProps;
const parentInstance = returnFiber.stateNode;
switch (fiber.tag) {
case HostComponent:
const type = fiber.type;
const props = fiber.pendingProps;
didNotFindHydratableInstance(
parentType,
parentProps,
parentInstance,
type,
props,
);
break;
case HostText:
const text = fiber.pendingProps;
didNotFindHydratableTextInstance(
parentType,
parentProps,
parentInstance,
text,
);
break;
}
return false;
}
case HostText: {
const text = fiber.pendingProps;
const textInstance = canHydrateTextInstance(nextInstance, text);
if (textInstance !== null) {
fiber.stateNode = (textInstance: TI);
return true;
}
return false;
break;
}
default:
return false;
return;
}
}
}
function tryToClaimNextHydratableInstance(fiber: Fiber) {
if (!isHydrating) {
return;
function tryHydrate(fiber, nextInstance) {
switch (fiber.tag) {
case HostComponent: {
const type = fiber.type;
const props = fiber.pendingProps;
const instance = canHydrateInstance(nextInstance, type, props);
if (instance !== null) {
fiber.stateNode = (instance: Instance);
return true;
}
return false;
}
let nextInstance = nextHydratableInstance;
if (!nextInstance) {
case HostText: {
const text = fiber.pendingProps;
const textInstance = canHydrateTextInstance(nextInstance, text);
if (textInstance !== null) {
fiber.stateNode = (textInstance: TextInstance);
return true;
}
return false;
}
default:
return false;
}
}
function tryToClaimNextHydratableInstance(fiber: Fiber): void {
if (!isHydrating) {
return;
}
let nextInstance = nextHydratableInstance;
if (!nextInstance) {
// Nothing to hydrate. Make it an insertion.
insertNonHydratedInstance((hydrationParentFiber: any), fiber);
isHydrating = false;
hydrationParentFiber = fiber;
return;
}
const firstAttemptedInstance = nextInstance;
if (!tryHydrate(fiber, nextInstance)) {
// If we can't hydrate this instance let's try the next one.
// We use this as a heuristic. It's based on intuition and not data so it
// might be flawed or unnecessary.
nextInstance = getNextHydratableSibling(firstAttemptedInstance);
if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
// Nothing to hydrate. Make it an insertion.
insertNonHydratedInstance((hydrationParentFiber: any), fiber);
isHydrating = false;
hydrationParentFiber = fiber;
return;
}
if (!tryHydrate(fiber, nextInstance)) {
// If we can't hydrate this instance let's try the next one.
// We use this as a heuristic. It's based on intuition and not data so it
// might be flawed or unnecessary.
nextInstance = getNextHydratableSibling(nextInstance);
if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
// Nothing to hydrate. Make it an insertion.
insertNonHydratedInstance((hydrationParentFiber: any), fiber);
isHydrating = false;
hydrationParentFiber = fiber;
return;
}
// We matched the next one, we'll now assume that the first one was
// superfluous and we'll delete it. Since we can't eagerly delete it
// we'll have to schedule a deletion. To do that, this node needs a dummy
// fiber associated with it.
deleteHydratableInstance(
(hydrationParentFiber: any),
nextHydratableInstance,
);
}
hydrationParentFiber = fiber;
nextHydratableInstance = getFirstHydratableChild(nextInstance);
}
function prepareToHydrateHostInstance(
fiber: Fiber,
rootContainerInstance: C,
hostContext: CX,
): boolean {
const instance: I = fiber.stateNode;
const updatePayload = hydrateInstance(
instance,
fiber.type,
fiber.memoizedProps,
rootContainerInstance,
hostContext,
fiber,
// We matched the next one, we'll now assume that the first one was
// superfluous and we'll delete it. Since we can't eagerly delete it
// we'll have to schedule a deletion. To do that, this node needs a dummy
// fiber associated with it.
deleteHydratableInstance(
(hydrationParentFiber: any),
firstAttemptedInstance,
);
}
hydrationParentFiber = fiber;
nextHydratableInstance = getFirstHydratableChild((nextInstance: any));
}
function prepareToHydrateHostInstance(
fiber: Fiber,
rootContainerInstance: Container,
hostContext: HostContext,
): boolean {
if (!supportsHydration) {
invariant(
false,
'Expected prepareToHydrateHostInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
// TODO: Type this specific to this type of component.
fiber.updateQueue = (updatePayload: any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update.
if (updatePayload !== null) {
return true;
}
return false;
}
function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
const textInstance: TI = fiber.stateNode;
const textContent: string = fiber.memoizedProps;
const shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber);
if (__DEV__) {
if (shouldUpdate) {
// We assume that prepareToHydrateHostTextInstance is called in a context where the
// hydration parent is the parent host component of this host text.
const returnFiber = hydrationParentFiber;
if (returnFiber !== null) {
switch (returnFiber.tag) {
case HostRoot: {
const parentContainer = returnFiber.stateNode.containerInfo;
didNotMatchHydratedContainerTextInstance(
parentContainer,
textInstance,
textContent,
);
break;
}
case HostComponent: {
const parentType = returnFiber.type;
const parentProps = returnFiber.memoizedProps;
const parentInstance = returnFiber.stateNode;
didNotMatchHydratedTextInstance(
parentType,
parentProps,
parentInstance,
textInstance,
textContent,
);
break;
}
const instance: Instance = fiber.stateNode;
const updatePayload = hydrateInstance(
instance,
fiber.type,
fiber.memoizedProps,
rootContainerInstance,
hostContext,
fiber,
);
// TODO: Type this specific to this type of component.
fiber.updateQueue = (updatePayload: any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update.
if (updatePayload !== null) {
return true;
}
return false;
}
function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
if (!supportsHydration) {
invariant(
false,
'Expected prepareToHydrateHostTextInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
const textInstance: TextInstance = fiber.stateNode;
const textContent: string = fiber.memoizedProps;
const shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber);
if (__DEV__) {
if (shouldUpdate) {
// We assume that prepareToHydrateHostTextInstance is called in a context where the
// hydration parent is the parent host component of this host text.
const returnFiber = hydrationParentFiber;
if (returnFiber !== null) {
switch (returnFiber.tag) {
case HostRoot: {
const parentContainer = returnFiber.stateNode.containerInfo;
didNotMatchHydratedContainerTextInstance(
parentContainer,
textInstance,
textContent,
);
break;
}
case HostComponent: {
const parentType = returnFiber.type;
const parentProps = returnFiber.memoizedProps;
const parentInstance = returnFiber.stateNode;
didNotMatchHydratedTextInstance(
parentType,
parentProps,
parentInstance,
textInstance,
textContent,
);
break;
}
}
}
}
return shouldUpdate;
}
function popToNextHostParent(fiber: Fiber): void {
let parent = fiber.return;
while (
parent !== null &&
parent.tag !== HostComponent &&
parent.tag !== HostRoot
) {
parent = parent.return;
}
hydrationParentFiber = parent;
}
function popHydrationState(fiber: Fiber): boolean {
if (fiber !== hydrationParentFiber) {
// We're deeper than the current hydration context, inside an inserted
// tree.
return false;
}
if (!isHydrating) {
// If we're not currently hydrating but we're in a hydration context, then
// we were an insertion and now need to pop up reenter hydration of our
// siblings.
popToNextHostParent(fiber);
isHydrating = true;
return false;
}
const type = fiber.type;
// If we have any remaining hydratable nodes, we need to delete them now.
// We only do this deeper than head and body since they tend to have random
// other nodes in them. We also ignore components with pure text content in
// side of them.
// TODO: Better heuristic.
if (
fiber.tag !== HostComponent ||
(type !== 'head' &&
type !== 'body' &&
!shouldSetTextContent(type, fiber.memoizedProps))
) {
let nextInstance = nextHydratableInstance;
while (nextInstance) {
deleteHydratableInstance(fiber, nextInstance);
nextInstance = getNextHydratableSibling(nextInstance);
}
}
popToNextHostParent(fiber);
nextHydratableInstance = hydrationParentFiber
? getNextHydratableSibling(fiber.stateNode)
: null;
return true;
}
function resetHydrationState() {
hydrationParentFiber = null;
nextHydratableInstance = null;
isHydrating = false;
}
return {
enterHydrationState,
resetHydrationState,
tryToClaimNextHydratableInstance,
prepareToHydrateHostInstance,
prepareToHydrateHostTextInstance,
popHydrationState,
};
return shouldUpdate;
}
function popToNextHostParent(fiber: Fiber): void {
let parent = fiber.return;
while (
parent !== null &&
parent.tag !== HostComponent &&
parent.tag !== HostRoot
) {
parent = parent.return;
}
hydrationParentFiber = parent;
}
function popHydrationState(fiber: Fiber): boolean {
if (!supportsHydration) {
return false;
}
if (fiber !== hydrationParentFiber) {
// We're deeper than the current hydration context, inside an inserted
// tree.
return false;
}
if (!isHydrating) {
// If we're not currently hydrating but we're in a hydration context, then
// we were an insertion and now need to pop up reenter hydration of our
// siblings.
popToNextHostParent(fiber);
isHydrating = true;
return false;
}
const type = fiber.type;
// If we have any remaining hydratable nodes, we need to delete them now.
// We only do this deeper than head and body since they tend to have random
// other nodes in them. We also ignore components with pure text content in
// side of them.
// TODO: Better heuristic.
if (
fiber.tag !== HostComponent ||
(type !== 'head' &&
type !== 'body' &&
!shouldSetTextContent(type, fiber.memoizedProps))
) {
let nextInstance = nextHydratableInstance;
while (nextInstance) {
deleteHydratableInstance(fiber, nextInstance);
nextInstance = getNextHydratableSibling(nextInstance);
}
}
popToNextHostParent(fiber);
nextHydratableInstance = hydrationParentFiber
? getNextHydratableSibling(fiber.stateNode)
: null;
return true;
}
function resetHydrationState(): void {
if (!supportsHydration) {
return;
}
hydrationParentFiber = null;
nextHydratableInstance = null;
isHydrating = false;
}
export {
enterHydrationState,
resetHydrationState,
tryToClaimNextHydratableInstance,
prepareToHydrateHostInstance,
prepareToHydrateHostTextInstance,
popHydrationState,
};
+83 -85
View File
@@ -9,7 +9,7 @@
import type {Fiber} from './ReactFiber';
import type {ReactContext} from 'shared/ReactTypes';
import type {StackCursor, Stack} from './ReactFiberStack';
import type {StackCursor} from './ReactFiberStack';
export type NewContext = {
pushProvider(providerFiber: Fiber): void,
@@ -19,90 +19,88 @@ export type NewContext = {
};
import warning from 'fbjs/lib/warning';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack';
export default function(stack: Stack, isPrimaryRenderer: boolean) {
const {createCursor, push, pop} = stack;
const providerCursor: StackCursor<Fiber | null> = createCursor(null);
const valueCursor: StackCursor<mixed> = createCursor(null);
const changedBitsCursor: StackCursor<number> = createCursor(0);
const providerCursor: StackCursor<Fiber | null> = createCursor(null);
const valueCursor: StackCursor<mixed> = createCursor(null);
const changedBitsCursor: StackCursor<number> = createCursor(0);
let rendererSigil;
if (__DEV__) {
// Use this to detect multiple renderers using the same context
rendererSigil = {};
}
function pushProvider(providerFiber: Fiber): void {
const context: ReactContext<any> = providerFiber.type._context;
if (isPrimaryRenderer) {
push(changedBitsCursor, context._changedBits, providerFiber);
push(valueCursor, context._currentValue, providerFiber);
push(providerCursor, providerFiber, providerFiber);
context._currentValue = providerFiber.pendingProps.value;
context._changedBits = providerFiber.stateNode;
if (__DEV__) {
warning(
context._currentRenderer === undefined ||
context._currentRenderer === null ||
context._currentRenderer === rendererSigil,
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
context._currentRenderer = rendererSigil;
}
} else {
push(changedBitsCursor, context._changedBits2, providerFiber);
push(valueCursor, context._currentValue2, providerFiber);
push(providerCursor, providerFiber, providerFiber);
context._currentValue2 = providerFiber.pendingProps.value;
context._changedBits2 = providerFiber.stateNode;
if (__DEV__) {
warning(
context._currentRenderer2 === undefined ||
context._currentRenderer2 === null ||
context._currentRenderer2 === rendererSigil,
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
context._currentRenderer2 = rendererSigil;
}
}
}
function popProvider(providerFiber: Fiber): void {
const changedBits = changedBitsCursor.current;
const currentValue = valueCursor.current;
pop(providerCursor, providerFiber);
pop(valueCursor, providerFiber);
pop(changedBitsCursor, providerFiber);
const context: ReactContext<any> = providerFiber.type._context;
if (isPrimaryRenderer) {
context._currentValue = currentValue;
context._changedBits = changedBits;
} else {
context._currentValue2 = currentValue;
context._changedBits2 = changedBits;
}
}
function getContextCurrentValue(context: ReactContext<any>): any {
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
function getContextChangedBits(context: ReactContext<any>): number {
return isPrimaryRenderer ? context._changedBits : context._changedBits2;
}
return {
pushProvider,
popProvider,
getContextCurrentValue,
getContextChangedBits,
};
let rendererSigil;
if (__DEV__) {
// Use this to detect multiple renderers using the same context
rendererSigil = {};
}
function pushProvider(providerFiber: Fiber): void {
const context: ReactContext<any> = providerFiber.type._context;
if (isPrimaryRenderer) {
push(changedBitsCursor, context._changedBits, providerFiber);
push(valueCursor, context._currentValue, providerFiber);
push(providerCursor, providerFiber, providerFiber);
context._currentValue = providerFiber.pendingProps.value;
context._changedBits = providerFiber.stateNode;
if (__DEV__) {
warning(
context._currentRenderer === undefined ||
context._currentRenderer === null ||
context._currentRenderer === rendererSigil,
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
context._currentRenderer = rendererSigil;
}
} else {
push(changedBitsCursor, context._changedBits2, providerFiber);
push(valueCursor, context._currentValue2, providerFiber);
push(providerCursor, providerFiber, providerFiber);
context._currentValue2 = providerFiber.pendingProps.value;
context._changedBits2 = providerFiber.stateNode;
if (__DEV__) {
warning(
context._currentRenderer2 === undefined ||
context._currentRenderer2 === null ||
context._currentRenderer2 === rendererSigil,
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
context._currentRenderer2 = rendererSigil;
}
}
}
function popProvider(providerFiber: Fiber): void {
const changedBits = changedBitsCursor.current;
const currentValue = valueCursor.current;
pop(providerCursor, providerFiber);
pop(valueCursor, providerFiber);
pop(changedBitsCursor, providerFiber);
const context: ReactContext<any> = providerFiber.type._context;
if (isPrimaryRenderer) {
context._currentValue = currentValue;
context._changedBits = changedBits;
} else {
context._currentValue2 = currentValue;
context._changedBits2 = changedBits;
}
}
function getContextCurrentValue(context: ReactContext<any>): any {
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
function getContextChangedBits(context: ReactContext<any>): number {
return isPrimaryRenderer ? context._changedBits : context._changedBits2;
}
export {
pushProvider,
popProvider,
getContextCurrentValue,
getContextChangedBits,
};
+225 -473
View File
@@ -9,6 +9,12 @@
import type {Fiber} from './ReactFiber';
import type {FiberRoot} from './ReactFiberRoot';
import type {
Instance,
TextInstance,
Container,
PublicInstance,
} from './ReactFiberHostConfig';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -23,508 +29,254 @@ import getComponentName from 'shared/getComponentName';
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import {getPublicInstance} from './ReactFiberHostConfig';
import {
findCurrentUnmaskedContext,
isContextProvider,
processChildContext,
} from './ReactFiberContext';
import {createFiberRoot} from './ReactFiberRoot';
import * as ReactFiberDevToolsHook from './ReactFiberDevToolsHook';
import ReactFiberScheduler from './ReactFiberScheduler';
import {
computeUniqueAsyncExpiration,
recalculateCurrentTime,
computeExpirationForFiber,
scheduleWork,
requestWork,
flushRoot,
batchedUpdates,
unbatchedUpdates,
flushSync,
flushControlled,
deferredUpdates,
syncUpdates,
interactiveUpdates,
flushInteractiveUpdates,
} from './ReactFiberScheduler';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
type OpaqueRoot = FiberRoot;
// 0 is PROD, 1 is DEV.
// Might add PROFILE later.
type BundleType = 0 | 1;
type DevToolsConfig = {|
bundleType: BundleType,
version: string,
rendererPackageName: string,
// Note: this actually *does* depend on Fiber internal fields.
// Used by "inspect clicked DOM element" in React DevTools.
findFiberByHostInstance?: (instance: Instance | TextInstance) => Fiber,
// Used by RN in-app inspector.
// This API is unfortunately RN-specific.
// TODO: Change it to accept Fiber instead and type it properly.
getInspectorDataForViewTag?: (tag: number) => Object,
|};
let didWarnAboutNestedUpdates;
if (__DEV__) {
didWarnAboutNestedUpdates = false;
}
export type Deadline = {
timeRemaining: () => number,
didTimeout: boolean,
};
type OpaqueHandle = Fiber;
type OpaqueRoot = FiberRoot;
export type HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL> = {
getRootHostContext(rootContainerInstance: C): CX,
getChildHostContext(parentHostContext: CX, type: T, instance: C): CX,
getPublicInstance(instance: I | TI): PI,
createInstance(
type: T,
props: P,
rootContainerInstance: C,
hostContext: CX,
internalInstanceHandle: OpaqueHandle,
): I,
appendInitialChild(parentInstance: I, child: I | TI): void,
finalizeInitialChildren(
parentInstance: I,
type: T,
props: P,
rootContainerInstance: C,
hostContext: CX,
): boolean,
prepareUpdate(
instance: I,
type: T,
oldProps: P,
newProps: P,
rootContainerInstance: C,
hostContext: CX,
): null | PL,
shouldSetTextContent(type: T, props: P): boolean,
shouldDeprioritizeSubtree(type: T, props: P): boolean,
createTextInstance(
text: string,
rootContainerInstance: C,
hostContext: CX,
internalInstanceHandle: OpaqueHandle,
): TI,
scheduleDeferredCallback(
callback: (deadline: Deadline) => void,
options?: {timeout: number},
): number,
cancelDeferredCallback(callbackID: number): void,
prepareForCommit(containerInfo: C): void,
resetAfterCommit(containerInfo: C): void,
now(): number,
// Temporary workaround for scenario where multiple renderers concurrently
// render using the same context objects. E.g. React DOM and React ART on the
// same page. DOM is the primary renderer; ART is the secondary renderer.
isPrimaryRenderer: boolean,
+hydration?: HydrationHostConfig<T, P, I, TI, HI, C, CX, PL>,
+mutation?: MutableUpdatesHostConfig<T, P, I, TI, C, PL>,
+persistence?: PersistentUpdatesHostConfig<T, P, I, TI, C, CC, PL>,
};
type MutableUpdatesHostConfig<T, P, I, TI, C, PL> = {
commitUpdate(
instance: I,
updatePayload: PL,
type: T,
oldProps: P,
newProps: P,
internalInstanceHandle: OpaqueHandle,
): void,
commitMount(
instance: I,
type: T,
newProps: P,
internalInstanceHandle: OpaqueHandle,
): void,
commitTextUpdate(textInstance: TI, oldText: string, newText: string): void,
resetTextContent(instance: I): void,
appendChild(parentInstance: I, child: I | TI): void,
appendChildToContainer(container: C, child: I | TI): void,
insertBefore(parentInstance: I, child: I | TI, beforeChild: I | TI): void,
insertInContainerBefore(
container: C,
child: I | TI,
beforeChild: I | TI,
): void,
removeChild(parentInstance: I, child: I | TI): void,
removeChildFromContainer(container: C, child: I | TI): void,
};
type PersistentUpdatesHostConfig<T, P, I, TI, C, CC, PL> = {
cloneInstance(
instance: I,
updatePayload: null | PL,
type: T,
oldProps: P,
newProps: P,
internalInstanceHandle: OpaqueHandle,
keepChildren: boolean,
recyclableInstance: I,
): I,
createContainerChildSet(container: C): CC,
appendChildToContainerChildSet(childSet: CC, child: I | TI): void,
finalizeContainerChildren(container: C, newChildren: CC): void,
replaceContainerChildren(container: C, newChildren: CC): void,
};
type HydrationHostConfig<T, P, I, TI, HI, C, CX, PL> = {
// Optional hydration
canHydrateInstance(instance: HI, type: T, props: P): null | I,
canHydrateTextInstance(instance: HI, text: string): null | TI,
getNextHydratableSibling(instance: I | TI | HI): null | HI,
getFirstHydratableChild(parentInstance: I | C): null | HI,
hydrateInstance(
instance: I,
type: T,
props: P,
rootContainerInstance: C,
hostContext: CX,
internalInstanceHandle: OpaqueHandle,
): null | PL,
hydrateTextInstance(
textInstance: TI,
text: string,
internalInstanceHandle: OpaqueHandle,
): boolean,
didNotMatchHydratedContainerTextInstance(
parentContainer: C,
textInstance: TI,
text: string,
): void,
didNotMatchHydratedTextInstance(
parentType: T,
parentProps: P,
parentInstance: I,
textInstance: TI,
text: string,
): void,
didNotHydrateContainerInstance(parentContainer: C, instance: I | TI): void,
didNotHydrateInstance(
parentType: T,
parentProps: P,
parentInstance: I,
instance: I | TI,
): void,
didNotFindHydratableContainerInstance(
parentContainer: C,
type: T,
props: P,
): void,
didNotFindHydratableContainerTextInstance(
parentContainer: C,
text: string,
): void,
didNotFindHydratableInstance(
parentType: T,
parentProps: P,
parentInstance: I,
type: T,
props: P,
): void,
didNotFindHydratableTextInstance(
parentType: T,
parentProps: P,
parentInstance: I,
text: string,
): void,
};
// 0 is PROD, 1 is DEV.
// Might add PROFILE later.
type BundleType = 0 | 1;
type DevToolsConfig<I, TI> = {|
bundleType: BundleType,
version: string,
rendererPackageName: string,
// Note: this actually *does* depend on Fiber internal fields.
// Used by "inspect clicked DOM element" in React DevTools.
findFiberByHostInstance?: (instance: I | TI) => Fiber,
// Used by RN in-app inspector.
// This API is unfortunately RN-specific.
// TODO: Change it to accept Fiber instead and type it properly.
getInspectorDataForViewTag?: (tag: number) => Object,
|};
export type Reconciler<C, I, TI> = {
createContainer(
containerInfo: C,
isAsync: boolean,
hydrate: boolean,
): OpaqueRoot,
updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime,
updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
): ExpirationTime,
flushRoot(root: OpaqueRoot, expirationTime: ExpirationTime): void,
requestWork(root: OpaqueRoot, expirationTime: ExpirationTime): void,
batchedUpdates<A>(fn: () => A): A,
unbatchedUpdates<A>(fn: () => A): A,
flushSync<A>(fn: () => A): A,
flushControlled(fn: () => mixed): void,
deferredUpdates<A>(fn: () => A): A,
interactiveUpdates<A>(fn: () => A): A,
injectIntoDevTools(devToolsConfig: DevToolsConfig<I, TI>): boolean,
computeUniqueAsyncExpiration(): ExpirationTime,
// Used to extract the return value from the initial render. Legacy API.
getPublicRootInstance(
container: OpaqueRoot,
): React$Component<any, any> | TI | I | null,
// Use for findDOMNode/findHostNode. Legacy API.
findHostInstance(component: Object): I | TI | null,
// Used internally for filtering out portals. Legacy API.
findHostInstanceWithNoPortals(component: Fiber): I | TI | null,
};
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
): Reconciler<C, I, TI> {
const {getPublicInstance} = config;
const {
computeUniqueAsyncExpiration,
recalculateCurrentTime,
computeExpirationForFiber,
scheduleWork,
requestWork,
flushRoot,
batchedUpdates,
unbatchedUpdates,
flushSync,
flushControlled,
deferredUpdates,
syncUpdates,
interactiveUpdates,
flushInteractiveUpdates,
legacyContext,
} = ReactFiberScheduler(config);
const {
findCurrentUnmaskedContext,
isContextProvider,
processChildContext,
} = legacyContext;
function getContextForSubtree(
parentComponent: ?React$Component<any, any>,
): Object {
if (!parentComponent) {
return emptyObject;
}
const fiber = ReactInstanceMap.get(parentComponent);
const parentContext = findCurrentUnmaskedContext(fiber);
return isContextProvider(fiber)
? processChildContext(fiber, parentContext)
: parentContext;
function getContextForSubtree(
parentComponent: ?React$Component<any, any>,
): Object {
if (!parentComponent) {
return emptyObject;
}
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
if (__DEV__) {
if (
ReactDebugCurrentFiber.phase === 'render' &&
ReactDebugCurrentFiber.current !== null &&
!didWarnAboutNestedUpdates
) {
didWarnAboutNestedUpdates = true;
warning(
false,
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
'Check the render method of %s.',
getComponentName(ReactDebugCurrentFiber.current) || 'Unknown',
);
}
}
const fiber = ReactInstanceMap.get(parentComponent);
const parentContext = findCurrentUnmaskedContext(fiber);
return isContextProvider(fiber)
? processChildContext(fiber, parentContext)
: parentContext;
}
const update = createUpdate(expirationTime);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
warning(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}
enqueueUpdate(current, update, expirationTime);
scheduleWork(current, expirationTime);
return expirationTime;
}
function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// 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 scheduleRootUpdate(current, element, expirationTime, callback);
}
function findHostInstance(component: Object): PI | null {
const fiber = ReactInstanceMap.get(component);
if (fiber === undefined) {
if (typeof component.render === 'function') {
invariant(false, 'Unable to find node on an unmounted component.');
} else {
invariant(
false,
'Argument appears to not be a ReactComponent. Keys: %s',
Object.keys(component),
);
}
}
const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
}
return {
createContainer(
containerInfo: C,
isAsync: boolean,
hydrate: boolean,
): OpaqueRoot {
return createFiberRoot(containerInfo, isAsync, hydrate);
},
updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
const currentTime = recalculateCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
},
updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
if (__DEV__) {
if (
ReactDebugCurrentFiber.phase === 'render' &&
ReactDebugCurrentFiber.current !== null &&
!didWarnAboutNestedUpdates
) {
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
didWarnAboutNestedUpdates = true;
warning(
false,
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
'Check the render method of %s.',
getComponentName(ReactDebugCurrentFiber.current) || 'Unknown',
);
},
}
}
flushRoot,
const update = createUpdate(expirationTime);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
requestWork,
callback = callback === undefined ? null : callback;
if (callback !== null) {
warning(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}
enqueueUpdate(current, update, expirationTime);
computeUniqueAsyncExpiration,
scheduleWork(current, expirationTime);
return expirationTime;
}
batchedUpdates,
export function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// TODO: If this is a nested container, this won't be the root.
const current = container.current;
unbatchedUpdates,
deferredUpdates,
syncUpdates,
interactiveUpdates,
flushInteractiveUpdates,
flushControlled,
flushSync,
getPublicRootInstance(
container: OpaqueRoot,
): React$Component<any, any> | PI | null {
const containerFiber = container.current;
if (!containerFiber.child) {
return null;
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);
}
switch (containerFiber.child.tag) {
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
return containerFiber.child.stateNode;
}
},
}
}
findHostInstance,
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
findHostInstanceWithNoPortals(fiber: Fiber): PI | null {
const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
return scheduleRootUpdate(current, element, expirationTime, callback);
}
function findHostInstance(component: Object): PublicInstance | null {
const fiber = ReactInstanceMap.get(component);
if (fiber === undefined) {
if (typeof component.render === 'function') {
invariant(false, 'Unable to find node on an unmounted component.');
} else {
invariant(
false,
'Argument appears to not be a ReactComponent. Keys: %s',
Object.keys(component),
);
}
}
const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
}
export function createContainer(
containerInfo: Container,
isAsync: boolean,
hydrate: boolean,
): OpaqueRoot {
return createFiberRoot(containerInfo, isAsync, hydrate);
}
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
const currentTime = recalculateCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
export {
flushRoot,
requestWork,
computeUniqueAsyncExpiration,
batchedUpdates,
unbatchedUpdates,
deferredUpdates,
syncUpdates,
interactiveUpdates,
flushInteractiveUpdates,
flushControlled,
flushSync,
};
export function getPublicRootInstance(
container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
const containerFiber = container.current;
if (!containerFiber.child) {
return null;
}
switch (containerFiber.child.tag) {
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
return containerFiber.child.stateNode;
}
}
export {findHostInstance};
export function findHostInstanceWithNoPortals(
fiber: Fiber,
): PublicInstance | null {
const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
}
export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
const {findFiberByHostInstance} = devToolsConfig;
return ReactFiberDevToolsHook.injectInternals({
...devToolsConfig,
findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null {
const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
},
injectIntoDevTools(devToolsConfig: DevToolsConfig<I, TI>): boolean {
const {findFiberByHostInstance} = devToolsConfig;
return ReactFiberDevToolsHook.injectInternals({
...devToolsConfig,
findHostInstanceByFiber(fiber: Fiber): I | TI | null {
const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
},
findFiberByHostInstance(instance: I | TI): Fiber | null {
if (!findFiberByHostInstance) {
// Might not be implemented by the renderer.
return null;
}
return findFiberByHostInstance(instance);
},
});
findFiberByHostInstance(instance: Instance | TextInstance): Fiber | null {
if (!findFiberByHostInstance) {
// Might not be implemented by the renderer.
return null;
}
return findFiberByHostInstance(instance);
},
};
});
}
File diff suppressed because it is too large Load Diff
+78 -90
View File
@@ -15,100 +15,88 @@ export type StackCursor<T> = {
current: T,
};
export type Stack = {
createCursor<T>(defaultValue: T): StackCursor<T>,
isEmpty(): boolean,
push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void,
pop<T>(cursor: StackCursor<T>, fiber: Fiber): void,
const valueStack: Array<any> = [];
// DEV only
checkThatStackIsEmpty(): void,
resetStackAfterFatalErrorInDev(): void,
};
let fiberStack: Array<Fiber | null>;
export default function(): Stack {
const valueStack: Array<any> = [];
if (__DEV__) {
fiberStack = [];
}
let fiberStack: Array<Fiber | null>;
if (__DEV__) {
fiberStack = [];
}
let index = -1;
function createCursor<T>(defaultValue: T): StackCursor<T> {
return {
current: defaultValue,
};
}
function isEmpty(): boolean {
return index === -1;
}
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
if (index < 0) {
if (__DEV__) {
warning(false, 'Unexpected pop.');
}
return;
}
if (__DEV__) {
if (fiber !== fiberStack[index]) {
warning(false, 'Unexpected Fiber popped.');
}
}
cursor.current = valueStack[index];
valueStack[index] = null;
if (__DEV__) {
fiberStack[index] = null;
}
index--;
}
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
index++;
valueStack[index] = cursor.current;
if (__DEV__) {
fiberStack[index] = fiber;
}
cursor.current = value;
}
function checkThatStackIsEmpty() {
if (__DEV__) {
if (index !== -1) {
warning(
false,
'Expected an empty stack. Something was not reset properly.',
);
}
}
}
function resetStackAfterFatalErrorInDev() {
if (__DEV__) {
index = -1;
valueStack.length = 0;
fiberStack.length = 0;
}
}
let index = -1;
function createCursor<T>(defaultValue: T): StackCursor<T> {
return {
createCursor,
isEmpty,
pop,
push,
checkThatStackIsEmpty,
resetStackAfterFatalErrorInDev,
current: defaultValue,
};
}
function isEmpty(): boolean {
return index === -1;
}
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
if (index < 0) {
if (__DEV__) {
warning(false, 'Unexpected pop.');
}
return;
}
if (__DEV__) {
if (fiber !== fiberStack[index]) {
warning(false, 'Unexpected Fiber popped.');
}
}
cursor.current = valueStack[index];
valueStack[index] = null;
if (__DEV__) {
fiberStack[index] = null;
}
index--;
}
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
index++;
valueStack[index] = cursor.current;
if (__DEV__) {
fiberStack[index] = fiber;
}
cursor.current = value;
}
function checkThatStackIsEmpty() {
if (__DEV__) {
if (index !== -1) {
warning(
false,
'Expected an empty stack. Something was not reset properly.',
);
}
}
}
function resetStackAfterFatalErrorInDev() {
if (__DEV__) {
index = -1;
valueStack.length = 0;
fiberStack.length = 0;
}
}
export {
createCursor,
isEmpty,
pop,
push,
// DEV only:
checkThatStackIsEmpty,
resetStackAfterFatalErrorInDev,
};
+319 -343
View File
@@ -7,27 +7,13 @@
* @flow
*/
import type {HostConfig} from 'react-reconciler';
import type {Fiber} from './ReactFiber';
import type {FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {HostContext} from './ReactFiberHostContext';
import type {LegacyContext} from './ReactFiberContext';
import type {NewContext} from './ReactFiberNewContext';
import type {CapturedValue} from './ReactCapturedValue';
import type {ProfilerTimer} from './ReactProfilerTimer';
import type {Update} from './ReactUpdateQueue';
import type {Thenable} from './ReactFiberScheduler';
import {createCapturedValue} from './ReactCapturedValue';
import {
enqueueCapturedUpdate,
createUpdate,
enqueueUpdate,
CaptureUpdate,
} from './ReactUpdateQueue';
import {logError} from './ReactFiberCommitWork';
import {
ClassComponent,
HostRoot,
@@ -49,238 +35,252 @@ import {
enableSuspense,
} from 'shared/ReactFeatureFlags';
import {createCapturedValue} from './ReactCapturedValue';
import {
enqueueCapturedUpdate,
createUpdate,
enqueueUpdate,
CaptureUpdate,
} from './ReactUpdateQueue';
import {logError} from './ReactFiberCommitWork';
import {Never, Sync, expirationTimeToMs} from './ReactFiberExpirationTime';
import {popHostContainer, popHostContext} from './ReactFiberHostContext';
import {
popContextProvider as popLegacyContextProvider,
popTopLevelContextObject as popTopLevelLegacyContextObject,
} from './ReactFiberContext';
import {popProvider} from './ReactFiberNewContext';
import {
resumeActualRenderTimerIfPaused,
recordElapsedActualRenderTime,
} from './ReactProfilerTimer';
import {
suspendRoot,
onUncaughtError,
markLegacyErrorBoundaryAsFailed,
isAlreadyFailedLegacyErrorBoundary,
recalculateCurrentTime,
computeExpirationForFiber,
scheduleWork,
retrySuspendedRoot,
} from './ReactFiberScheduler';
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
hostContext: HostContext<C, CX>,
legacyContext: LegacyContext,
newContext: NewContext,
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (
startTime: ExpirationTime,
fiber: Fiber,
) => ExpirationTime,
recalculateCurrentTime: () => ExpirationTime,
markLegacyErrorBoundaryAsFailed: (instance: mixed) => void,
isAlreadyFailedLegacyErrorBoundary: (instance: mixed) => boolean,
onUncaughtError: (error: mixed) => void,
profilerTimer: ProfilerTimer,
suspendRoot: (
root: FiberRoot,
thenable: Thenable,
timeoutMs: number,
suspendedTime: ExpirationTime,
) => void,
retrySuspendedRoot: (root: FiberRoot, suspendedTime: ExpirationTime) => void,
) {
const {popHostContainer, popHostContext} = hostContext;
const {
popContextProvider: popLegacyContextProvider,
popTopLevelContextObject: popTopLevelLegacyContextObject,
} = legacyContext;
const {popProvider} = newContext;
const {
resumeActualRenderTimerIfPaused,
recordElapsedActualRenderTime,
} = profilerTimer;
function createRootErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
expirationTime: ExpirationTime,
): Update<null> {
const update = createUpdate(expirationTime);
// Unmount the root by rendering null.
update.tag = CaptureUpdate;
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element: null};
const error = errorInfo.value;
update.callback = () => {
onUncaughtError(error);
logError(fiber, errorInfo);
};
return update;
}
function createRootErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
expirationTime: ExpirationTime,
): Update<null> {
const update = createUpdate(expirationTime);
// Unmount the root by rendering null.
update.tag = CaptureUpdate;
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element: null};
const error = errorInfo.value;
update.callback = () => {
onUncaughtError(error);
logError(fiber, errorInfo);
};
return update;
}
function createClassErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
expirationTime: ExpirationTime,
): Update<mixed> {
const update = createUpdate(expirationTime);
update.tag = CaptureUpdate;
const getDerivedStateFromCatch = fiber.type.getDerivedStateFromCatch;
if (
enableGetDerivedStateFromCatch &&
typeof getDerivedStateFromCatch === 'function'
) {
const error = errorInfo.value;
update.payload = () => {
return getDerivedStateFromCatch(error);
};
}
const inst = fiber.stateNode;
if (inst !== null && typeof inst.componentDidCatch === 'function') {
update.callback = function callback() {
if (
!enableGetDerivedStateFromCatch ||
getDerivedStateFromCatch !== 'function'
) {
// To preserve the preexisting retry behavior of error boundaries,
// we keep track of which ones already failed during this batch.
// This gets reset before we yield back to the browser.
// TODO: Warn in strict mode if getDerivedStateFromCatch is
// not defined.
markLegacyErrorBoundaryAsFailed(this);
}
const error = errorInfo.value;
const stack = errorInfo.stack;
logError(fiber, errorInfo);
this.componentDidCatch(error, {
componentStack: stack !== null ? stack : '',
});
};
}
return update;
}
function schedulePing(finishedWork) {
// Once the promise resolves, we should try rendering the non-
// placeholder state again.
const currentTime = recalculateCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, finishedWork);
const recoveryUpdate = createUpdate(expirationTime);
enqueueUpdate(finishedWork, recoveryUpdate, expirationTime);
scheduleWork(finishedWork, expirationTime);
}
function throwException(
root: FiberRoot,
returnFiber: Fiber,
sourceFiber: Fiber,
value: mixed,
renderIsExpired: boolean,
renderExpirationTime: ExpirationTime,
currentTimeMs: number,
function createClassErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
expirationTime: ExpirationTime,
): Update<mixed> {
const update = createUpdate(expirationTime);
update.tag = CaptureUpdate;
const getDerivedStateFromCatch = fiber.type.getDerivedStateFromCatch;
if (
enableGetDerivedStateFromCatch &&
typeof getDerivedStateFromCatch === 'function'
) {
// The source fiber did not complete.
sourceFiber.effectTag |= Incomplete;
// Its effect list is no longer valid.
sourceFiber.firstEffect = sourceFiber.lastEffect = null;
const error = errorInfo.value;
update.payload = () => {
return getDerivedStateFromCatch(error);
};
}
if (
enableSuspense &&
value !== null &&
typeof value === 'object' &&
typeof value.then === 'function'
) {
// This is a thenable.
const thenable: Thenable = (value: any);
const expirationTimeMs = expirationTimeToMs(renderExpirationTime);
const startTimeMs = expirationTimeMs - 5000;
let elapsedMs = currentTimeMs - startTimeMs;
if (elapsedMs < 0) {
elapsedMs = 0;
const inst = fiber.stateNode;
if (inst !== null && typeof inst.componentDidCatch === 'function') {
update.callback = function callback() {
if (
!enableGetDerivedStateFromCatch ||
getDerivedStateFromCatch !== 'function'
) {
// To preserve the preexisting retry behavior of error boundaries,
// we keep track of which ones already failed during this batch.
// This gets reset before we yield back to the browser.
// TODO: Warn in strict mode if getDerivedStateFromCatch is
// not defined.
markLegacyErrorBoundaryAsFailed(this);
}
const remainingTimeMs = expirationTimeMs - currentTimeMs;
const error = errorInfo.value;
const stack = errorInfo.stack;
logError(fiber, errorInfo);
this.componentDidCatch(error, {
componentStack: stack !== null ? stack : '',
});
};
}
return update;
}
// Find the earliest timeout of all the timeouts in the ancestor path.
// TODO: Alternatively, we could store the earliest timeout on the context
// stack, rather than searching on every suspend.
let workInProgress = returnFiber;
let earliestTimeoutMs = -1;
searchForEarliestTimeout: do {
if (workInProgress.tag === TimeoutComponent) {
const current = workInProgress.alternate;
if (current !== null && current.memoizedState === true) {
// A parent Timeout already committed in a placeholder state. We
// need to handle this promise immediately. In other words, we
// should never suspend inside a tree that already expired.
function schedulePing(finishedWork) {
// Once the promise resolves, we should try rendering the non-
// placeholder state again.
const currentTime = recalculateCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, finishedWork);
const recoveryUpdate = createUpdate(expirationTime);
enqueueUpdate(finishedWork, recoveryUpdate, expirationTime);
scheduleWork(finishedWork, expirationTime);
}
function throwException(
root: FiberRoot,
returnFiber: Fiber,
sourceFiber: Fiber,
value: mixed,
renderIsExpired: boolean,
renderExpirationTime: ExpirationTime,
currentTimeMs: number,
) {
// The source fiber did not complete.
sourceFiber.effectTag |= Incomplete;
// Its effect list is no longer valid.
sourceFiber.firstEffect = sourceFiber.lastEffect = null;
if (
enableSuspense &&
value !== null &&
typeof value === 'object' &&
typeof value.then === 'function'
) {
// This is a thenable.
const thenable: Thenable = (value: any);
const expirationTimeMs = expirationTimeToMs(renderExpirationTime);
const startTimeMs = expirationTimeMs - 5000;
let elapsedMs = currentTimeMs - startTimeMs;
if (elapsedMs < 0) {
elapsedMs = 0;
}
const remainingTimeMs = expirationTimeMs - currentTimeMs;
// Find the earliest timeout of all the timeouts in the ancestor path.
// TODO: Alternatively, we could store the earliest timeout on the context
// stack, rather than searching on every suspend.
let workInProgress = returnFiber;
let earliestTimeoutMs = -1;
searchForEarliestTimeout: do {
if (workInProgress.tag === TimeoutComponent) {
const current = workInProgress.alternate;
if (current !== null && current.memoizedState === true) {
// A parent Timeout already committed in a placeholder state. We
// need to handle this promise immediately. In other words, we
// should never suspend inside a tree that already expired.
earliestTimeoutMs = 0;
break searchForEarliestTimeout;
}
let timeoutPropMs = workInProgress.pendingProps.ms;
if (typeof timeoutPropMs === 'number') {
if (timeoutPropMs <= 0) {
earliestTimeoutMs = 0;
break searchForEarliestTimeout;
} else if (
earliestTimeoutMs === -1 ||
timeoutPropMs < earliestTimeoutMs
) {
earliestTimeoutMs = timeoutPropMs;
}
let timeoutPropMs = workInProgress.pendingProps.ms;
if (typeof timeoutPropMs === 'number') {
if (timeoutPropMs <= 0) {
earliestTimeoutMs = 0;
break searchForEarliestTimeout;
} else if (
earliestTimeoutMs === -1 ||
timeoutPropMs < earliestTimeoutMs
) {
earliestTimeoutMs = timeoutPropMs;
} else if (earliestTimeoutMs === -1) {
earliestTimeoutMs = remainingTimeMs;
}
}
workInProgress = workInProgress.return;
} while (workInProgress !== null);
// Compute the remaining time until the timeout.
const msUntilTimeout = earliestTimeoutMs - elapsedMs;
if (renderExpirationTime === Never || msUntilTimeout > 0) {
// There's still time remaining.
suspendRoot(root, thenable, msUntilTimeout, renderExpirationTime);
const onResolveOrReject = () => {
retrySuspendedRoot(root, renderExpirationTime);
};
thenable.then(onResolveOrReject, onResolveOrReject);
return;
} else {
// No time remaining. Need to fallback to placeholder.
// Find the nearest timeout that can be retried.
workInProgress = returnFiber;
do {
switch (workInProgress.tag) {
case HostRoot: {
// The root expired, but no fallback was provided. Throw a
// helpful error.
const message =
renderExpirationTime === Sync
? 'A synchronous update was suspended, but no fallback UI ' +
'was provided.'
: 'An update was suspended for longer than the timeout, ' +
'but no fallback UI was provided.';
value = new Error(message);
break;
}
case TimeoutComponent: {
if ((workInProgress.effectTag & DidCapture) === NoEffect) {
workInProgress.effectTag |= ShouldCapture;
const onResolveOrReject = schedulePing.bind(null, workInProgress);
thenable.then(onResolveOrReject, onResolveOrReject);
return;
}
} else if (earliestTimeoutMs === -1) {
earliestTimeoutMs = remainingTimeMs;
// Already captured during this render. Continue to the next
// Timeout ancestor.
break;
}
}
workInProgress = workInProgress.return;
} while (workInProgress !== null);
// Compute the remaining time until the timeout.
const msUntilTimeout = earliestTimeoutMs - elapsedMs;
if (renderExpirationTime === Never || msUntilTimeout > 0) {
// There's still time remaining.
suspendRoot(root, thenable, msUntilTimeout, renderExpirationTime);
const onResolveOrReject = () => {
retrySuspendedRoot(root, renderExpirationTime);
};
thenable.then(onResolveOrReject, onResolveOrReject);
return;
} else {
// No time remaining. Need to fallback to placeholder.
// Find the nearest timeout that can be retried.
workInProgress = returnFiber;
do {
switch (workInProgress.tag) {
case HostRoot: {
// The root expired, but no fallback was provided. Throw a
// helpful error.
const message =
renderExpirationTime === Sync
? 'A synchronous update was suspended, but no fallback UI ' +
'was provided.'
: 'An update was suspended for longer than the timeout, ' +
'but no fallback UI was provided.';
value = new Error(message);
break;
}
case TimeoutComponent: {
if ((workInProgress.effectTag & DidCapture) === NoEffect) {
workInProgress.effectTag |= ShouldCapture;
const onResolveOrReject = schedulePing.bind(
null,
workInProgress,
);
thenable.then(onResolveOrReject, onResolveOrReject);
return;
}
// Already captured during this render. Continue to the next
// Timeout ancestor.
break;
}
}
workInProgress = workInProgress.return;
} while (workInProgress !== null);
}
}
}
// We didn't find a boundary that could handle this type of exception. Start
// over and traverse parent path again, this time treating the exception
// as an error.
value = createCapturedValue(value, sourceFiber);
let workInProgress = returnFiber;
do {
switch (workInProgress.tag) {
case HostRoot: {
const errorInfo = value;
// We didn't find a boundary that could handle this type of exception. Start
// over and traverse parent path again, this time treating the exception
// as an error.
value = createCapturedValue(value, sourceFiber);
let workInProgress = returnFiber;
do {
switch (workInProgress.tag) {
case HostRoot: {
const errorInfo = value;
workInProgress.effectTag |= ShouldCapture;
const update = createRootErrorUpdate(
workInProgress,
errorInfo,
renderExpirationTime,
);
enqueueCapturedUpdate(workInProgress, update, renderExpirationTime);
return;
}
case ClassComponent:
// Capture and retry
const errorInfo = value;
const ctor = workInProgress.type;
const instance = workInProgress.stateNode;
if (
(workInProgress.effectTag & DidCapture) === NoEffect &&
((typeof ctor.getDerivedStateFromCatch === 'function' &&
enableGetDerivedStateFromCatch) ||
(instance !== null &&
typeof instance.componentDidCatch === 'function' &&
!isAlreadyFailedLegacyErrorBoundary(instance)))
) {
workInProgress.effectTag |= ShouldCapture;
const update = createRootErrorUpdate(
// Schedule the error boundary to re-render using updated state
const update = createClassErrorUpdate(
workInProgress,
errorInfo,
renderExpirationTime,
@@ -288,123 +288,99 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
enqueueCapturedUpdate(workInProgress, update, renderExpirationTime);
return;
}
case ClassComponent:
// Capture and retry
const errorInfo = value;
const ctor = workInProgress.type;
const instance = workInProgress.stateNode;
if (
(workInProgress.effectTag & DidCapture) === NoEffect &&
((typeof ctor.getDerivedStateFromCatch === 'function' &&
enableGetDerivedStateFromCatch) ||
(instance !== null &&
typeof instance.componentDidCatch === 'function' &&
!isAlreadyFailedLegacyErrorBoundary(instance)))
) {
workInProgress.effectTag |= ShouldCapture;
// Schedule the error boundary to re-render using updated state
const update = createClassErrorUpdate(
workInProgress,
errorInfo,
renderExpirationTime,
);
enqueueCapturedUpdate(workInProgress, update, renderExpirationTime);
return;
}
break;
default:
break;
}
workInProgress = workInProgress.return;
} while (workInProgress !== null);
}
function unwindWork(
workInProgress: Fiber,
renderIsExpired: boolean,
renderExpirationTime: ExpirationTime,
) {
switch (workInProgress.tag) {
case ClassComponent: {
popLegacyContextProvider(workInProgress);
const effectTag = workInProgress.effectTag;
if (effectTag & ShouldCapture) {
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
return null;
}
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
const effectTag = workInProgress.effectTag;
if (effectTag & ShouldCapture) {
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
return null;
}
case HostComponent: {
popHostContext(workInProgress);
return null;
}
case TimeoutComponent: {
const effectTag = workInProgress.effectTag;
if (effectTag & ShouldCapture) {
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
return null;
}
case HostPortal:
popHostContainer(workInProgress);
return null;
case ContextProvider:
popProvider(workInProgress);
return null;
default:
return null;
}
}
function unwindInterruptedWork(interruptedWork: Fiber) {
switch (interruptedWork.tag) {
case ClassComponent: {
popLegacyContextProvider(interruptedWork);
break;
}
case HostRoot: {
popHostContainer(interruptedWork);
popTopLevelLegacyContextObject(interruptedWork);
break;
}
case HostComponent: {
popHostContext(interruptedWork);
break;
}
case HostPortal:
popHostContainer(interruptedWork);
break;
case ContextProvider:
popProvider(interruptedWork);
break;
case Profiler:
if (enableProfilerTimer) {
// Resume in case we're picking up on work that was paused.
resumeActualRenderTimerIfPaused();
recordElapsedActualRenderTime(interruptedWork);
}
break;
default:
break;
}
}
return {
throwException,
unwindWork,
unwindInterruptedWork,
createRootErrorUpdate,
createClassErrorUpdate,
};
workInProgress = workInProgress.return;
} while (workInProgress !== null);
}
function unwindWork(
workInProgress: Fiber,
renderIsExpired: boolean,
renderExpirationTime: ExpirationTime,
) {
switch (workInProgress.tag) {
case ClassComponent: {
popLegacyContextProvider(workInProgress);
const effectTag = workInProgress.effectTag;
if (effectTag & ShouldCapture) {
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
return null;
}
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
const effectTag = workInProgress.effectTag;
if (effectTag & ShouldCapture) {
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
return null;
}
case HostComponent: {
popHostContext(workInProgress);
return null;
}
case TimeoutComponent: {
const effectTag = workInProgress.effectTag;
if (effectTag & ShouldCapture) {
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
return null;
}
case HostPortal:
popHostContainer(workInProgress);
return null;
case ContextProvider:
popProvider(workInProgress);
return null;
default:
return null;
}
}
function unwindInterruptedWork(interruptedWork: Fiber) {
switch (interruptedWork.tag) {
case ClassComponent: {
popLegacyContextProvider(interruptedWork);
break;
}
case HostRoot: {
popHostContainer(interruptedWork);
popTopLevelLegacyContextObject(interruptedWork);
break;
}
case HostComponent: {
popHostContext(interruptedWork);
break;
}
case HostPortal:
popHostContainer(interruptedWork);
break;
case ContextProvider:
popProvider(interruptedWork);
break;
case Profiler:
if (enableProfilerTimer) {
// Resume in case we're picking up on work that was paused.
resumeActualRenderTimerIfPaused();
recordElapsedActualRenderTime(interruptedWork);
}
break;
default:
break;
}
}
export {
throwException,
unwindWork,
unwindInterruptedWork,
createRootErrorUpdate,
createClassErrorUpdate,
};
+113 -101
View File
@@ -12,6 +12,7 @@ import type {Fiber} from './ReactFiber';
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
import warning from 'fbjs/lib/warning';
import {now} from './ReactFiberHostConfig';
/**
* The "actual" render time is total time required to render the descendants of a Profiler component.
@@ -32,113 +33,124 @@ export type ProfilerTimer = {
stopBaseRenderTimerIfRunning(): void,
};
export function createProfilerTimer(now: () => number): ProfilerTimer {
let fiberStack: Array<Fiber | null>;
let fiberStack: Array<Fiber | null>;
if (__DEV__) {
fiberStack = [];
if (__DEV__) {
fiberStack = [];
}
let timerPausedAt: number = 0;
let totalElapsedPauseTime: number = 0;
function checkActualRenderTimeStackEmpty(): void {
if (!enableProfilerTimer) {
return;
}
if (__DEV__) {
warning(
fiberStack.length === 0,
'Expected an empty stack. Something was not reset properly.',
);
}
}
let timerPausedAt: number = 0;
let totalElapsedPauseTime: number = 0;
function markActualRenderTimeStarted(fiber: Fiber): void {
if (!enableProfilerTimer) {
return;
}
if (__DEV__) {
fiberStack.push(fiber);
}
fiber.stateNode.startTime = now() - totalElapsedPauseTime;
}
function checkActualRenderTimeStackEmpty(): void {
if (__DEV__) {
function pauseActualRenderTimerIfRunning(): void {
if (!enableProfilerTimer) {
return;
}
if (timerPausedAt === 0) {
timerPausedAt = now();
}
}
function recordElapsedActualRenderTime(fiber: Fiber): void {
if (!enableProfilerTimer) {
return;
}
if (__DEV__) {
warning(fiber === fiberStack.pop(), 'Unexpected Fiber popped.');
}
fiber.stateNode.duration +=
now() - totalElapsedPauseTime - fiber.stateNode.startTime;
}
function resetActualRenderTimer(): void {
if (!enableProfilerTimer) {
return;
}
totalElapsedPauseTime = 0;
}
function resumeActualRenderTimerIfPaused(): void {
if (!enableProfilerTimer) {
return;
}
if (timerPausedAt > 0) {
totalElapsedPauseTime += now() - timerPausedAt;
timerPausedAt = 0;
}
}
/**
* The "base" render time is the duration of the begin phase of work for a particular fiber.
* This time is measured and stored on each fiber.
* The time for all sibling fibers are accumulated and stored on their parent during the "complete" phase.
* If a fiber bails out (sCU false) then its "base" timer is cancelled and the fiber is not updated.
*/
let baseStartTime: number = -1;
function recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void {
if (!enableProfilerTimer) {
return;
}
if (baseStartTime !== -1) {
fiber.selfBaseTime = now() - baseStartTime;
}
}
function startBaseRenderTimer(): void {
if (!enableProfilerTimer) {
return;
}
if (__DEV__) {
if (baseStartTime !== -1) {
warning(
fiberStack.length === 0,
'Expected an empty stack. Something was not reset properly.',
false,
'Cannot start base timer that is already running. ' +
'This error is likely caused by a bug in React. ' +
'Please file an issue.',
);
}
}
function markActualRenderTimeStarted(fiber: Fiber): void {
if (__DEV__) {
fiberStack.push(fiber);
}
fiber.stateNode.startTime = now() - totalElapsedPauseTime;
}
function pauseActualRenderTimerIfRunning(): void {
if (timerPausedAt === 0) {
timerPausedAt = now();
}
}
function recordElapsedActualRenderTime(fiber: Fiber): void {
if (__DEV__) {
warning(fiber === fiberStack.pop(), 'Unexpected Fiber popped.');
}
fiber.stateNode.duration +=
now() - totalElapsedPauseTime - fiber.stateNode.startTime;
}
function resetActualRenderTimer(): void {
totalElapsedPauseTime = 0;
}
function resumeActualRenderTimerIfPaused(): void {
if (timerPausedAt > 0) {
totalElapsedPauseTime += now() - timerPausedAt;
timerPausedAt = 0;
}
}
/**
* The "base" render time is the duration of the begin phase of work for a particular fiber.
* This time is measured and stored on each fiber.
* The time for all sibling fibers are accumulated and stored on their parent during the "complete" phase.
* If a fiber bails out (sCU false) then its "base" timer is cancelled and the fiber is not updated.
*/
let baseStartTime: number = -1;
function recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void {
if (baseStartTime !== -1) {
fiber.selfBaseTime = now() - baseStartTime;
}
}
function startBaseRenderTimer(): void {
if (__DEV__) {
if (baseStartTime !== -1) {
warning(
false,
'Cannot start base timer that is already running. ' +
'This error is likely caused by a bug in React. ' +
'Please file an issue.',
);
}
}
baseStartTime = now();
}
function stopBaseRenderTimerIfRunning(): void {
baseStartTime = -1;
}
if (enableProfilerTimer) {
return {
checkActualRenderTimeStackEmpty,
markActualRenderTimeStarted,
pauseActualRenderTimerIfRunning,
recordElapsedActualRenderTime,
resetActualRenderTimer,
resumeActualRenderTimerIfPaused,
recordElapsedBaseRenderTimeIfRunning,
startBaseRenderTimer,
stopBaseRenderTimerIfRunning,
};
} else {
return {
checkActualRenderTimeStackEmpty(): void {},
markActualRenderTimeStarted(fiber: Fiber): void {},
pauseActualRenderTimerIfRunning(): void {},
recordElapsedActualRenderTime(fiber: Fiber): void {},
resetActualRenderTimer(): void {},
resumeActualRenderTimerIfPaused(): void {},
recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void {},
startBaseRenderTimer(): void {},
stopBaseRenderTimerIfRunning(): void {},
};
}
baseStartTime = now();
}
function stopBaseRenderTimerIfRunning(): void {
if (!enableProfilerTimer) {
return;
}
baseStartTime = -1;
}
export {
checkActualRenderTimeStackEmpty,
markActualRenderTimeStarted,
pauseActualRenderTimerIfRunning,
recordElapsedActualRenderTime,
resetActualRenderTimer,
resumeActualRenderTimerIfPaused,
recordElapsedBaseRenderTimeIfRunning,
startBaseRenderTimer,
stopBaseRenderTimerIfRunning,
};
@@ -46,11 +46,10 @@ describe('ReactFiberHostContext', () => {
now: function() {
return 0;
},
mutation: {
appendChildToContainer: function() {
return null;
},
appendChildToContainer: function() {
return null;
},
supportsMutation: true,
});
const container = Renderer.createContainer(/* root: */ null);
@@ -95,11 +94,10 @@ describe('ReactFiberHostContext', () => {
now: function() {
return 0;
},
mutation: {
appendChildToContainer: function() {
return null;
},
appendChildToContainer: function() {
return null;
},
supportsMutation: true,
});
const container = Renderer.createContainer(rootContext);
@@ -12,22 +12,26 @@
let React;
let ReactNoopPersistent;
let ReactPortal;
describe('ReactPersistent', () => {
beforeEach(() => {
jest.resetModules();
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableMutableReconciler = false;
ReactFeatureFlags.enablePersistentReconciler = true;
ReactFeatureFlags.enableNoopReconciler = false;
React = require('react');
ReactNoopPersistent = require('react-noop-renderer/persistent');
ReactPortal = require('shared/ReactPortal');
});
// Inlined from shared folder so we can run this test on a bundle.
function createPortal(children, containerInfo, implementation, key) {
return {
$$typeof: Symbol.for('react.portal'),
key: key == null ? null : '' + key,
children,
containerInfo,
implementation,
};
}
function render(element) {
ReactNoopPersistent.render(element);
}
@@ -160,11 +164,7 @@ describe('ReactPersistent', () => {
}
const portalContainer = {rootID: 'persistent-portal-test', children: []};
const emptyPortalChildSet = portalContainer.children;
render(
<Parent>
{ReactPortal.createPortal(<Child />, portalContainer, null)}
</Parent>,
);
render(<Parent>{createPortal(<Child />, portalContainer, null)}</Parent>);
ReactNoopPersistent.flush();
expect(emptyPortalChildSet).toEqual([]);
@@ -176,11 +176,7 @@ describe('ReactPersistent', () => {
render(
<Parent>
{ReactPortal.createPortal(
<Child>Hello {'World'}</Child>,
portalContainer,
null,
)}
{createPortal(<Child>Hello {'World'}</Child>, portalContainer, null)}
</Parent>,
);
ReactNoopPersistent.flush();
@@ -0,0 +1,10 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-art/src/ReactARTHostConfig';
@@ -0,0 +1,110 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* 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 host config that's used for the `react-reconciler` package on npm.
// It is only used by third-party renderers.
//
// Its API lets you pass the host config as an argument.
// However, inside the `react-reconciler` we treat host config as a module.
// This file is a shim between two worlds.
//
// It works because the `react-reconciler` bundle is wrapped in something like:
//
// module.exports = function ($$$config) {
// /* reconciler code */
// }
//
// So `$$$config` looks like a global variable, but it's
// really an argument to a top-level wrapping function.
declare var $$$hostConfig: any;
export opaque type Type = mixed; // eslint-disable-line no-undef
export opaque type Props = mixed; // eslint-disable-line no-undef
export opaque type Container = mixed; // eslint-disable-line no-undef
export opaque type Instance = mixed; // eslint-disable-line no-undef
export opaque type TextInstance = mixed; // eslint-disable-line no-undef
export opaque type HydratableInstance = mixed; // eslint-disable-line no-undef
export opaque type PublicInstance = mixed; // eslint-disable-line no-undef
export opaque type HostContext = mixed; // eslint-disable-line no-undef
export opaque type UpdatePayload = mixed; // eslint-disable-line no-undef
export opaque type ChildSet = mixed; // eslint-disable-line no-undef
export const getPublicInstance = $$$hostConfig.getPublicInstance;
export const getRootHostContext = $$$hostConfig.getRootHostContext;
export const getChildHostContext = $$$hostConfig.getChildHostContext;
export const prepareForCommit = $$$hostConfig.prepareForCommit;
export const resetAfterCommit = $$$hostConfig.resetAfterCommit;
export const createInstance = $$$hostConfig.createInstance;
export const appendInitialChild = $$$hostConfig.appendInitialChild;
export const finalizeInitialChildren = $$$hostConfig.finalizeInitialChildren;
export const prepareUpdate = $$$hostConfig.prepareUpdate;
export const shouldSetTextContent = $$$hostConfig.shouldSetTextContent;
export const shouldDeprioritizeSubtree =
$$$hostConfig.shouldDeprioritizeSubtree;
export const createTextInstance = $$$hostConfig.createTextInstance;
export const scheduleDeferredCallback = $$$hostConfig.scheduleDeferredCallback;
export const cancelDeferredCallback = $$$hostConfig.cancelDeferredCallback;
export const now = $$$hostConfig.now;
export const isPrimaryRenderer = $$$hostConfig.isPrimaryRenderer;
export const supportsMutation = $$$hostConfig.supportsMutation;
export const supportsPersistence = $$$hostConfig.supportsPersistence;
export const supportsHydration = $$$hostConfig.supportsHydration;
// -------------------
// Mutation
// (optional)
// -------------------
export const appendChild = $$$hostConfig.appendChild;
export const appendChildToContainer = $$$hostConfig.appendChildToContainer;
export const commitTextUpdate = $$$hostConfig.commitTextUpdate;
export const commitMount = $$$hostConfig.commitMount;
export const commitUpdate = $$$hostConfig.commitUpdate;
export const insertBefore = $$$hostConfig.insertBefore;
export const insertInContainerBefore = $$$hostConfig.insertInContainerBefore;
export const removeChild = $$$hostConfig.removeChild;
export const removeChildFromContainer = $$$hostConfig.removeChildFromContainer;
export const resetTextContent = $$$hostConfig.resetTextContent;
// -------------------
// Persistence
// (optional)
// -------------------
export const cloneInstance = $$$hostConfig.cloneInstance;
export const createContainerChildSet = $$$hostConfig.createContainerChildSet;
export const appendChildToContainerChildSet =
$$$hostConfig.appendChildToContainerChildSet;
export const finalizeContainerChildren =
$$$hostConfig.finalizeContainerChildren;
export const replaceContainerChildren = $$$hostConfig.replaceContainerChildren;
// -------------------
// Hydration
// (optional)
// -------------------
export const canHydrateInstance = $$$hostConfig.canHydrateInstance;
export const canHydrateTextInstance = $$$hostConfig.canHydrateTextInstance;
export const getNextHydratableSibling = $$$hostConfig.getNextHydratableSibling;
export const getFirstHydratableChild = $$$hostConfig.getFirstHydratableChild;
export const hydrateInstance = $$$hostConfig.hydrateInstance;
export const hydrateTextInstance = $$$hostConfig.hydrateTextInstance;
export const didNotMatchHydratedContainerTextInstance =
$$$hostConfig.didNotMatchHydratedContainerTextInstance;
export const didNotMatchHydratedTextInstance =
$$$hostConfig.didNotMatchHydratedTextInstance;
export const didNotHydrateContainerInstance =
$$$hostConfig.didNotHydrateContainerInstance;
export const didNotHydrateInstance = $$$hostConfig.didNotHydrateInstance;
export const didNotFindHydratableContainerInstance =
$$$hostConfig.didNotFindHydratableContainerInstance;
export const didNotFindHydratableContainerTextInstance =
$$$hostConfig.didNotFindHydratableContainerTextInstance;
export const didNotFindHydratableInstance =
$$$hostConfig.didNotFindHydratableInstance;
export const didNotFindHydratableTextInstance =
$$$hostConfig.didNotFindHydratableTextInstance;
@@ -0,0 +1,10 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-dom/src/client/ReactDOMHostConfig';
@@ -0,0 +1,10 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-native-renderer/src/ReactFabricHostConfig';
@@ -0,0 +1,10 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-native-renderer/src/ReactNativeHostConfig';
@@ -0,0 +1,10 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-test-renderer/src/ReactTestHostConfig';
+1 -1
View File
@@ -30,7 +30,7 @@
// layout, paint and other browser work is counted against the available time.
// The frame rate is dynamically adjusted.
import type {Deadline} from 'react-reconciler';
import type {Deadline} from 'react-reconciler/src/ReactFiberScheduler';
type FrameCallbackType = Deadline => void;
type CallbackConfigType = {|
scheduledCallback: FrameCallbackType,
+142 -132
View File
@@ -11,6 +11,13 @@ import emptyObject from 'fbjs/lib/emptyObject';
import * as TestRendererScheduling from './ReactTestRendererScheduling';
export type Type = string;
export type Props = Object;
export type Container = {|
children: Array<Instance | TextInstance>,
createNodeMock: Function,
tag: 'CONTAINER',
|};
export type Instance = {|
type: string,
props: Object,
@@ -18,23 +25,22 @@ export type Instance = {|
rootContainerInstance: Container,
tag: 'INSTANCE',
|};
export type TextInstance = {|
text: string,
tag: 'TEXT',
|};
type Container = {|
children: Array<Instance | TextInstance>,
createNodeMock: Function,
tag: 'CONTAINER',
|};
type Props = Object;
export type HydratableInstance = Instance | TextInstance;
export type PublicInstance = Instance | TextInstance;
export type HostContext = Object;
export type UpdatePayload = Object;
export type ChildSet = void; // Unused
const UPDATE_SIGNAL = {};
function getPublicInstance(inst: Instance | TextInstance): * {
export * from 'shared/HostConfigWithNoPersistence';
export * from 'shared/HostConfigWithNoHydration';
export function getPublicInstance(inst: Instance | TextInstance): * {
switch (inst.tag) {
case 'INSTANCE':
const createNodeMock = inst.rootContainerInstance.createNodeMock;
@@ -47,7 +53,7 @@ function getPublicInstance(inst: Instance | TextInstance): * {
}
}
function appendChild(
export function appendChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
@@ -58,7 +64,7 @@ function appendChild(
parentInstance.children.push(child);
}
function insertBefore(
export function insertBefore(
parentInstance: Instance | Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
@@ -71,7 +77,7 @@ function insertBefore(
parentInstance.children.splice(beforeIndex, 0, child);
}
function removeChild(
export function removeChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
@@ -79,140 +85,144 @@ function removeChild(
parentInstance.children.splice(index, 1);
}
const ReactTestHostConfig = {
getRootHostContext() {
return emptyObject;
},
export function getRootHostContext(
rootContainerInstance: Container,
): HostContext {
return emptyObject;
}
getChildHostContext() {
return emptyObject;
},
export function getChildHostContext(
parentHostContext: HostContext,
type: string,
rootContainerInstance: Container,
): HostContext {
return emptyObject;
}
prepareForCommit(): void {
// noop
},
export function prepareForCommit(containerInfo: Container): void {
// noop
}
resetAfterCommit(): void {
// noop
},
export function resetAfterCommit(containerInfo: Container): void {
// noop
}
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): Instance {
return {
type,
props,
children: [],
rootContainerInstance,
tag: 'INSTANCE',
};
},
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): Instance {
return {
type,
props,
children: [],
rootContainerInstance,
tag: 'INSTANCE',
};
}
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
},
export function appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
}
finalizeInitialChildren(
testElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
): boolean {
return false;
},
export function finalizeInitialChildren(
testElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: Object,
): boolean {
return false;
}
prepareUpdate(
testElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: Object,
): null | {} {
return UPDATE_SIGNAL;
},
export function prepareUpdate(
testElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: Object,
): null | {} {
return UPDATE_SIGNAL;
}
shouldSetTextContent(type: string, props: Props): boolean {
return false;
},
export function shouldSetTextContent(type: string, props: Props): boolean {
return false;
}
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
}
createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): TextInstance {
return {
text,
tag: 'TEXT',
};
},
export function createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): TextInstance {
return {
text,
tag: 'TEXT',
};
}
getPublicInstance,
export const isPrimaryRenderer = true;
// This approach enables `now` to be mocked by tests,
// Even after the reconciler has initialized and read host config values.
export const now = () => TestRendererScheduling.nowImplementation();
export const scheduleDeferredCallback =
TestRendererScheduling.scheduleDeferredCallback;
export const cancelDeferredCallback =
TestRendererScheduling.cancelDeferredCallback;
scheduleDeferredCallback: TestRendererScheduling.scheduleDeferredCallback,
cancelDeferredCallback: TestRendererScheduling.cancelDeferredCallback,
// This approach enables `now` to be mocked by tests,
// Even after the reconciler has initialized and read host config values.
now: () => TestRendererScheduling.nowImplementation(),
// -------------------
// Mutation
// -------------------
isPrimaryRenderer: true,
export const supportsMutation = true;
mutation: {
commitUpdate(
instance: Instance,
updatePayload: {},
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
instance.type = type;
instance.props = newProps;
},
export function commitUpdate(
instance: Instance,
updatePayload: {},
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
instance.type = type;
instance.props = newProps;
}
commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// noop
},
export function commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// noop
}
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
},
resetTextContent(testElement: Instance): void {
// noop
},
export function commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
}
appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
},
};
export function resetTextContent(testElement: Instance): void {
// noop
}
export default ReactTestHostConfig;
export const appendChildToContainer = appendChild;
export const insertInContainerBefore = insertBefore;
export const removeChildFromContainer = removeChild;
+2 -4
View File
@@ -11,7 +11,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
import type {Instance, TextInstance} from './ReactTestHostConfig';
import ReactFiberReconciler from 'react-reconciler';
import * as TestRenderer from 'react-reconciler/inline.test';
import {batchedUpdates} from 'events/ReactGenericBatching';
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
import {
@@ -30,7 +30,7 @@ import {
} from 'shared/ReactTypeOfWork';
import invariant from 'fbjs/lib/invariant';
import ReactTestHostConfig from './ReactTestHostConfig';
import * as ReactTestHostConfig from './ReactTestHostConfig';
import * as TestRendererScheduling from './ReactTestRendererScheduling';
type TestRendererOptions = {
@@ -54,8 +54,6 @@ type FindOptions = $Shape<{
export type Predicate = (node: ReactTestInstance) => ?boolean;
const TestRenderer = ReactFiberReconciler(ReactTestHostConfig);
const defaultTestOptions = {
createNodeMock: function() {
return null;
@@ -7,7 +7,7 @@
* @flow
*/
import type {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
import type {Deadline} from 'react-reconciler/src/ReactFiberScheduler';
// Current virtual time
export let nowImplementation = () => 0;
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant';
// Renderers that don't support hydration
// can re-export everything from this module.
function shim(...args: any) {
invariant(
false,
'The current renderer does not support hyration. ' +
'This error is likely caused by a bug in React. ' +
'Please file an issue.',
);
}
// Hydration (when unsupported)
export const supportsHydration = false;
export const canHydrateInstance = shim;
export const canHydrateTextInstance = shim;
export const getNextHydratableSibling = shim;
export const getFirstHydratableChild = shim;
export const hydrateInstance = shim;
export const hydrateTextInstance = shim;
export const didNotMatchHydratedContainerTextInstance = shim;
export const didNotMatchHydratedTextInstance = shim;
export const didNotHydrateContainerInstance = shim;
export const didNotHydrateInstance = shim;
export const didNotFindHydratableContainerInstance = shim;
export const didNotFindHydratableContainerTextInstance = shim;
export const didNotFindHydratableInstance = shim;
export const didNotFindHydratableTextInstance = shim;
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant';
// Renderers that don't support mutation
// can re-export everything from this module.
function shim(...args: any) {
invariant(
false,
'The current renderer does not support mutation. ' +
'This error is likely caused by a bug in React. ' +
'Please file an issue.',
);
}
// Mutation (when unsupported)
export const supportsMutation = false;
export const appendChild = shim;
export const appendChildToContainer = shim;
export const commitTextUpdate = shim;
export const commitMount = shim;
export const commitUpdate = shim;
export const insertBefore = shim;
export const insertInContainerBefore = shim;
export const removeChild = shim;
export const removeChildFromContainer = shim;
export const resetTextContent = shim;
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant';
// Renderers that don't support persistence
// can re-export everything from this module.
function shim(...args: any) {
invariant(
false,
'The current renderer does not support persistence. ' +
'This error is likely caused by a bug in React. ' +
'Please file an issue.',
);
}
// Persistence (when unsupported)
export const supportsPersistence = false;
export const cloneInstance = shim;
export const createContainerChildSet = shim;
export const appendChildToContainerChildSet = shim;
export const finalizeContainerChildren = shim;
export const replaceContainerChildren = shim;
-6
View File
@@ -12,12 +12,6 @@ import invariant from 'fbjs/lib/invariant';
// Exports ReactDOM.createRoot
export const enableUserTimingAPI = __DEV__;
// Mutating mode (React DOM, React ART, React Native):
export const enableMutatingReconciler = true;
// Experimental noop mode (currently unused):
export const enableNoopReconciler = false;
// Experimental persistent mode (Fabric):
export const enablePersistentReconciler = false;
// Experimental error-boundary API that can recover from errors within a single
// render phase
export const enableGetDerivedStateFromCatch = false;
@@ -20,11 +20,6 @@ export const enableSuspense = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __DEV__;
// React Fabric uses persistent reconciler.
export const enableMutatingReconciler = false;
export const enableNoopReconciler = false;
export const enablePersistentReconciler = true;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
// Only used in www builds.
@@ -20,11 +20,6 @@ export const enableSuspense = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = false;
// React Fabric uses persistent reconciler.
export const enableMutatingReconciler = false;
export const enableNoopReconciler = false;
export const enablePersistentReconciler = true;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
// Only used in www builds.
@@ -26,9 +26,6 @@ export const {
// The rest of the flags are static for better dead code elimination.
export const enableUserTimingAPI = __DEV__;
export const enableMutatingReconciler = true;
export const enableNoopReconciler = false;
export const enablePersistentReconciler = false;
// Only used in www builds.
export function addUserTimingListener() {
@@ -16,9 +16,6 @@ export const debugRenderPhaseSideEffects = false;
export const debugRenderPhaseSideEffectsForStrictMode = false;
export const enableGetDerivedStateFromCatch = false;
export const enableSuspense = false;
export const enableMutatingReconciler = true;
export const enableNoopReconciler = false;
export const enablePersistentReconciler = false;
export const enableUserTimingAPI = __DEV__;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const warnAboutDeprecatedLifecycles = false;
@@ -22,12 +22,6 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = false;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
// react-reconciler/persistent entry point
// uses a persistent reconciler.
export const enableMutatingReconciler = false;
export const enableNoopReconciler = false;
export const enablePersistentReconciler = true;
// Only used in www builds.
export function addUserTimingListener() {
invariant(false, 'Not implemented.');
@@ -19,9 +19,6 @@ export const enableGetDerivedStateFromCatch = false;
export const enableSuspense = false;
export const warnAboutDeprecatedLifecycles = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableMutatingReconciler = true;
export const enableNoopReconciler = false;
export const enablePersistentReconciler = false;
export const enableProfilerTimer = false;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
@@ -24,11 +24,6 @@ export const {
// The rest of the flags are static for better dead code elimination.
// The www bundles only use the mutating reconciler.
export const enableMutatingReconciler = true;
export const enableNoopReconciler = false;
export const enablePersistentReconciler = false;
// In www, we have experimental support for gathering data
// from User Timing API calls in production. By default, we
// only emit performance.mark/measure calls in __DEV__. But if
+3
View File
@@ -30,6 +30,9 @@ esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
unsafe.enable_getters_and_setters=true
# Substituted by createFlowConfig.js:
%REACT_RENDERER_FLOW_OPTIONS%
munge_underscores=false
suppress_type=$FlowIssue
+27 -5
View File
@@ -1,15 +1,35 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
const chalk = require('chalk');
const fs = require('fs');
const mkdirp = require('mkdirp');
const {typedRenderers} = require('./typedRenderers');
const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
const config = fs.readFileSync(__dirname + '/config/flowconfig');
const configTemplate = fs
.readFileSync(__dirname + '/config/flowconfig')
.toString();
function writeConfig(folder) {
function writeConfig(renderer) {
const folder = __dirname + '/' + renderer;
mkdirp.sync(folder);
const config = configTemplate.replace(
'%REACT_RENDERER_FLOW_OPTIONS%',
`
module.name_mapper='react-reconciler/inline.${renderer}$$' -> 'react-reconciler/inline-typed'
module.name_mapper='ReactFiberHostConfig$$' -> 'forks/ReactFiberHostConfig.${renderer}'
`.trim(),
);
const disclaimer = `
# ---------------------------------------------------------------#
# NOTE: this file is generated. #
@@ -39,6 +59,8 @@ ${disclaimer}
// Write multiple configs in different folders
// so that we can run those checks in parallel if we want.
typedRenderers.forEach(renderer => {
writeConfig(__dirname + '/' + renderer);
inlinedHostConfigs.forEach(rendererInfo => {
if (rendererInfo.isFlowTyped) {
writeConfig(rendererInfo.shortName);
}
});
+9 -3
View File
@@ -1,3 +1,10 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const chalk = require('chalk');
@@ -8,9 +15,9 @@ require('./createFlowConfigs');
async function runFlow(renderer, args) {
return new Promise(resolve => {
console.log(
'Running Flow for the ' + chalk.cyan(renderer) + ' renderer...',
'Running Flow on the ' + chalk.yellow(renderer) + ' renderer...',
);
let cmd = '../../../node_modules/.bin/flow';
let cmd = __dirname + '/../../node_modules/.bin/flow';
if (process.platform === 'win32') {
cmd = cmd.replace(/\//g, '\\') + '.cmd';
}
@@ -30,7 +37,6 @@ async function runFlow(renderer, args) {
console.log(
'Flow passed for the ' + chalk.green(renderer) + ' renderer',
);
console.log();
resolve();
}
});
-3
View File
@@ -1,3 +0,0 @@
'use strict';
exports.typedRenderers = ['dom', 'fabric', 'native', 'test'];
+23
View File
@@ -0,0 +1,23 @@
'use strict';
module.exports = {
haste: {
hasteImplModulePath: require.resolve('./noHaste.js'),
},
modulePathIgnorePatterns: [
'<rootDir>/scripts/rollup/shims/',
'<rootDir>/scripts/bench/',
],
transform: {
'.*': require.resolve('./preprocessor.js'),
},
setupFiles: [require.resolve('./setupEnvironment.js')],
setupTestFrameworkScriptFile: require.resolve('./setupTests.js'),
// Only include files directly in __tests__, not in nested folders.
testRegex: '/__tests__/[^/]*(\\.js|\\.coffee|[^d]\\.ts)$',
moduleFileExtensions: ['js', 'json', 'node', 'coffee', 'ts'],
rootDir: process.cwd(),
roots: ['<rootDir>/packages', '<rootDir>/scripts'],
collectCoverageFrom: ['packages/**/*.js'],
timers: 'fake',
};
+2 -2
View File
@@ -2,7 +2,7 @@
const {readdirSync, statSync} = require('fs');
const {join} = require('path');
const sourceConfig = require('./config.source');
const baseConfig = require('./config.base');
// Find all folders in packages/* with package.json
const packagesRoot = join(__dirname, '..', '..', 'packages');
@@ -24,7 +24,7 @@ packages.forEach(name => {
] = `<rootDir>/build/node_modules/${name}/$1`;
});
module.exports = Object.assign({}, sourceConfig, {
module.exports = Object.assign({}, baseConfig, {
// Redirect imports to the compiled bundles
moduleNameMapper,
// Don't run bundle tests on blacklisted -test.internal.* files
+7 -20
View File
@@ -1,23 +1,10 @@
'use strict';
module.exports = {
haste: {
hasteImplModulePath: require.resolve('./noHaste.js'),
},
modulePathIgnorePatterns: [
'<rootDir>/scripts/rollup/shims/',
'<rootDir>/scripts/bench/',
const baseConfig = require('./config.base');
module.exports = Object.assign({}, baseConfig, {
setupFiles: [
...baseConfig.setupFiles,
require.resolve('./setupHostConfigs.js'),
],
transform: {
'.*': require.resolve('./preprocessor.js'),
},
setupFiles: [require.resolve('./setupEnvironment.js')],
setupTestFrameworkScriptFile: require.resolve('./setupTests.js'),
// Only include files directly in __tests__, not in nested folders.
testRegex: '/__tests__/[^/]*(\\.js|\\.coffee|[^d]\\.ts)$',
moduleFileExtensions: ['js', 'json', 'node', 'coffee', 'ts'],
rootDir: process.cwd(),
roots: ['<rootDir>/packages', '<rootDir>/scripts'],
collectCoverageFrom: ['packages/**/*.js'],
timers: 'fake',
};
});
+6
View File
@@ -45,3 +45,9 @@ if (typeof window !== 'undefined') {
}
});
}
// Preserve the empty object identity across module resets.
// This is needed for some tests that rely on string refs
// but reset modules between loading different renderers.
const obj = require.requireActual('fbjs/lib/emptyObject');
jest.mock('fbjs/lib/emptyObject', () => obj);
+39
View File
@@ -0,0 +1,39 @@
'use strict';
const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
// When testing the custom renderer code path through `react-reconciler`,
// turn the export into a function, and use the argument as host config.
const shimHostConfigPath = 'react-reconciler/src/ReactFiberHostConfig';
jest.mock('react-reconciler', () => {
return config => {
jest.mock(shimHostConfigPath, () => config);
return require.requireActual('react-reconciler');
};
});
jest.mock('react-reconciler/persistent', () => {
return config => {
jest.mock(shimHostConfigPath, () => config);
return require.requireActual('react-reconciler/persistent');
};
});
// But for inlined host configs (such as React DOM, Native, etc), we
// mock their named entry points to establish a host config mapping.
inlinedHostConfigs.forEach(rendererInfo => {
if (rendererInfo.shortName === 'custom') {
// There is no inline entry point for the custom renderers.
// Instead, it's handled by the generic `react-reconciler` entry point above.
return;
}
jest.mock(`react-reconciler/inline.${rendererInfo.shortName}`, () => {
jest.mock(shimHostConfigPath, () =>
require.requireActual(
`react-reconciler/src/forks/ReactFiberHostConfig.${
rendererInfo.shortName
}.js`
)
);
return require.requireActual('react-reconciler');
});
});
+1 -1
View File
@@ -207,7 +207,7 @@ function getPlugins(
modulesToStub
) {
const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts);
const forks = Modules.getForks(bundleType, entry);
const forks = Modules.getForks(bundleType, entry, moduleType);
const isProduction = isProductionBundleType(bundleType);
const isInGlobalScope = bundleType === UMD_DEV || bundleType === UMD_PROD;
const isFBBundle = bundleType === FB_WWW_DEV || bundleType === FB_WWW_PROD;
+6 -3
View File
@@ -29,6 +29,7 @@ const moduleTypes = {
RENDERER: 'RENDERER',
RENDERER_UTILS: 'RENDERER_UTILS',
RECONCILER: 'RECONCILER',
NON_FIBER_RENDERER: 'NON_FIBER_RENDERER',
};
// React
@@ -39,6 +40,8 @@ const RENDERER = moduleTypes.RENDERER;
const RENDERER_UTILS = moduleTypes.RENDERER_UTILS;
// Standalone reconciler for third-party renderers.
const RECONCILER = moduleTypes.RECONCILER;
// Non-Fiber implementations like SSR and Shallow renderers.
const NON_FIBER_RENDERER = moduleTypes.NON_FIBER_RENDERER;
const bundles = [
/******* Isomorphic *******/
@@ -113,7 +116,7 @@ const bundles = [
FB_WWW_DEV,
FB_WWW_PROD,
],
moduleType: RENDERER,
moduleType: NON_FIBER_RENDERER,
entry: 'react-dom/server.browser',
global: 'ReactDOMServer',
externals: ['react'],
@@ -122,7 +125,7 @@ const bundles = [
{
label: 'dom-server-node',
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
moduleType: NON_FIBER_RENDERER,
entry: 'react-dom/server.node',
externals: ['react', 'stream'],
},
@@ -246,7 +249,7 @@ const bundles = [
{
label: 'test-shallow',
bundleTypes: [FB_WWW_DEV, NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
moduleType: RENDERER,
moduleType: NON_FIBER_RENDERER,
entry: 'react-test-renderer/shallow',
global: 'ReactShallowRenderer',
externals: ['react'],
+31
View File
@@ -1,6 +1,8 @@
'use strict';
const bundleTypes = require('./bundles').bundleTypes;
const moduleTypes = require('./bundles').moduleTypes;
const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
const UMD_DEV = bundleTypes.UMD_DEV;
const UMD_PROD = bundleTypes.UMD_PROD;
@@ -10,6 +12,8 @@ const RN_OSS_DEV = bundleTypes.RN_OSS_DEV;
const RN_OSS_PROD = bundleTypes.RN_OSS_PROD;
const RN_FB_DEV = bundleTypes.RN_FB_DEV;
const RN_FB_PROD = bundleTypes.RN_FB_PROD;
const RENDERER = moduleTypes.RENDERER;
const RECONCILER = moduleTypes.RECONCILER;
// If you need to replace a file with another file for a specific environment,
// add it to this list with the logic for choosing the right replacement.
@@ -143,6 +147,33 @@ const forks = Object.freeze({
}
},
'react-reconciler/src/ReactFiberHostConfig': (
bundleType,
entry,
dependencies,
moduleType
) => {
if (dependencies.indexOf('react-reconciler') !== -1) {
return null;
}
if (moduleType !== RENDERER && moduleType !== RECONCILER) {
return null;
}
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (let rendererInfo of inlinedHostConfigs) {
if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
return `react-reconciler/src/forks/ReactFiberHostConfig.${
rendererInfo.shortName
}.js`;
}
}
throw new Error(
'Expected ReactFiberHostConfig to always be replaced with a shim, but ' +
`found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
'Did you mean to add it there to associate it with a specific renderer?'
);
},
// We wrap top-level listeners into guards on www.
'react-dom/src/events/EventListener': (bundleType, entry) => {
switch (bundleType) {
+7 -2
View File
@@ -56,11 +56,16 @@ function getDependencies(bundleType, entry) {
}
// Hijacks some modules for optimization and integration reasons.
function getForks(bundleType, entry) {
function getForks(bundleType, entry, moduleType) {
const forksForBundle = {};
Object.keys(forks).forEach(srcModule => {
const dependencies = getDependencies(bundleType, entry);
const targetModule = forks[srcModule](bundleType, entry, dependencies);
const targetModule = forks[srcModule](
bundleType,
entry,
dependencies,
moduleType
);
if (targetModule === null) {
return;
}
+12 -12
View File
@@ -56,7 +56,8 @@ ${license}
'use strict';
${
globalName === 'ReactNoopRenderer'
globalName === 'ReactNoopRenderer' ||
globalName === 'ReactNoopRendererPersistent'
? // React Noop needs regenerator runtime because it uses
// generators but GCC doesn't handle them in the output.
// So we use Babel for them.
@@ -79,7 +80,8 @@ ${source}
${license}
*/
${
globalName === 'ReactNoopRenderer'
globalName === 'ReactNoopRenderer' ||
globalName === 'ReactNoopRendererPersistent'
? // React Noop needs regenerator runtime because it uses
// generators but GCC doesn't handle them in the output.
// So we use Babel for them.
@@ -200,14 +202,11 @@ ${license}
'use strict';
if (process.env.NODE_ENV !== "production") {
// This is a hacky way to ensure third party renderers don't share
// top-level module state inside the reconciler. Ideally we should
// remove this hack by putting all top-level state into the closures
// and then forbidding adding more of it in the reconciler.
var $$$reconciler;
module.exports = function(config) {
module.exports = function $$$reconciler($$$hostConfig) {
${source}
return ($$$reconciler || ($$$reconciler = module.exports))(config);
var $$$renderer = module.exports;
module.exports = $$$reconciler;
return $$$renderer;
};
}`;
},
@@ -219,10 +218,11 @@ ${source}
*
${license}
*/
var $$$reconciler;
module.exports = function(config) {
module.exports = function $$$reconciler($$$hostConfig) {
${source}
return ($$$reconciler || ($$$reconciler = module.exports))(config);
var $$$renderer = module.exports;
module.exports = $$$reconciler;
return $$$renderer;
};`;
},
};
+40
View File
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
module.exports = [
{
shortName: 'dom',
entryPoints: ['react-dom'],
isFlowTyped: true,
},
{
shortName: 'art',
entryPoints: ['react-art'],
isFlowTyped: false, // TODO: type it.
},
{
shortName: 'native',
entryPoints: ['react-native-renderer'],
isFlowTyped: true,
},
{
shortName: 'fabric',
entryPoints: ['react-native-renderer/fabric'],
isFlowTyped: true,
},
{
shortName: 'test',
entryPoints: ['react-test-renderer'],
isFlowTyped: true,
},
{
shortName: 'custom',
entryPoints: ['react-reconciler', 'react-reconciler/persistent'],
isFlowTyped: true,
},
];
+6 -3
View File
@@ -12,12 +12,15 @@ process.on('unhandledRejection', err => {
});
const runFlow = require('../flow/runFlow');
const {typedRenderers} = require('../flow/typedRenderers');
const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
async function checkAll() {
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (let renderer of typedRenderers) {
await runFlow(renderer, ['check']);
for (let rendererInfo of inlinedHostConfigs) {
if (rendererInfo.isFlowTyped) {
await runFlow(rendererInfo.shortName, ['check']);
console.log();
}
}
}
+10 -6
View File
@@ -13,21 +13,25 @@ process.on('unhandledRejection', err => {
const chalk = require('chalk');
const runFlow = require('../flow/runFlow');
const {typedRenderers} = require('../flow/typedRenderers');
const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
// This script is using `flow status` for a quick check with a server.
// Use it for local development.
const primaryRenderer = process.argv[2];
if (typedRenderers.indexOf(primaryRenderer) === -1) {
const primaryRenderer = inlinedHostConfigs.find(
info => info.isFlowTyped && info.shortName === process.argv[2]
);
if (!primaryRenderer) {
console.log(
'The ' +
chalk.red('yarn flow') +
' command now requires you to pick a primary renderer:'
);
console.log();
typedRenderers.forEach(renderer => {
console.log(' * ' + chalk.cyan('yarn flow ' + renderer));
inlinedHostConfigs.forEach(rendererInfo => {
if (rendererInfo.isFlowTyped) {
console.log(' * ' + chalk.cyan('yarn flow ' + rendererInfo.shortName));
}
});
console.log();
console.log('If you are not sure, run ' + chalk.green('yarn flow dom') + '.');
@@ -45,4 +49,4 @@ if (typedRenderers.indexOf(primaryRenderer) === -1) {
process.exit(1);
}
runFlow(primaryRenderer, ['status']);
runFlow(primaryRenderer.shortName, ['status']);