mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
Vendored
+1
-6
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
+486
-474
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
Vendored
+12
-9
@@ -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
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
-2
@@ -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';
|
||||
|
||||
+1007
-1069
File diff suppressed because it is too large
Load Diff
+759
-777
File diff suppressed because it is too large
Load Diff
+701
-724
File diff suppressed because it is too large
Load Diff
+434
-462
@@ -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
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
+1710
-1771
File diff suppressed because it is too large
Load Diff
+78
-90
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
+13
-17
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
exports.typedRenderers = ['dom', 'fabric', 'native', 'test'];
|
||||
@@ -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,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
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
};`;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
@@ -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
@@ -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']);
|
||||
|
||||
Reference in New Issue
Block a user