mirror of
https://github.com/status-im/react-native.git
synced 2025-02-25 15:45:32 +00:00
Flat ReactNative renderer bundle [WIP]
Reviewed By: trueadm Differential Revision: D5013497 fbshipit-source-id: 1e23b08751b8b6e2dd570ff584c815c8a9b8f35f
This commit is contained in:
parent
21e3d4db20
commit
94c565a2c4
@ -18,7 +18,6 @@
|
||||
; For RN Apps installed via npm, "Libraries" folder is inside
|
||||
; "node_modules/react-native" but in the source repo it is in the root
|
||||
.*/Libraries/react-native/React.js
|
||||
.*/Libraries/react-native/ReactNative.js
|
||||
|
||||
[include]
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
# React Native Renderer
|
||||
|
||||
This is a downstream copy of React's renderer code to render into React Native.
|
||||
The source of truth is the React repo. Please submit any changes upstream to
|
||||
the [React Core GitHub repository](https://github.com/facebook/react).
|
4624
Libraries/Renderer/ReactNativeFiber-dev.js
Normal file
4624
Libraries/Renderer/ReactNativeFiber-dev.js
Normal file
File diff suppressed because it is too large
Load Diff
3710
Libraries/Renderer/ReactNativeFiber-prod.js
Normal file
3710
Libraries/Renderer/ReactNativeFiber-prod.js
Normal file
File diff suppressed because it is too large
Load Diff
3254
Libraries/Renderer/ReactNativeStack-dev.js
Normal file
3254
Libraries/Renderer/ReactNativeStack-dev.js
Normal file
File diff suppressed because it is too large
Load Diff
2478
Libraries/Renderer/ReactNativeStack-prod.js
Normal file
2478
Libraries/Renderer/ReactNativeStack-prod.js
Normal file
File diff suppressed because it is too large
Load Diff
23
Libraries/Renderer/shims/NativeMethodsMixin.js
Normal file
23
Libraries/Renderer/shims/NativeMethodsMixin.js
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule NativeMethodsMixin
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactNative');
|
||||
|
||||
import type {NativeMethodsMixinType} from 'ReactNativeTypes';
|
||||
|
||||
const {NativeMethodsMixin} = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
||||
|
||||
module.exports = ((NativeMethodsMixin: any): $Exact<NativeMethodsMixinType>);
|
@ -95,8 +95,8 @@ var addPoolingTo = function<T>(
|
||||
CopyConstructor: Class<T>,
|
||||
pooler: Pooler,
|
||||
): Class<T> & {
|
||||
getPooled(...args: $ReadOnlyArray<mixed>): /* arguments of the constructor */ T,
|
||||
release(instance: mixed): void,
|
||||
getPooled(): /* arguments of the constructor */ T,
|
||||
release(): void,
|
||||
} {
|
||||
// Casting as any so that flow ignores the actual implementation and trusts
|
||||
// it to match the type we declared
|
@ -6,12 +6,14 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
* @providesModule ReactPropTypesSecret
|
||||
* @providesModule ReactDebugTool
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactNative');
|
||||
|
||||
module.exports = ReactPropTypesSecret;
|
||||
module.exports =
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactDebugTool;
|
19
Libraries/Renderer/shims/ReactGlobalSharedState.js
Normal file
19
Libraries/Renderer/shims/ReactGlobalSharedState.js
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactGlobalSharedState
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactNative');
|
||||
|
||||
module.exports =
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactGlobalSharedState;
|
@ -13,6 +13,18 @@
|
||||
|
||||
const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
|
||||
|
||||
module.exports = ReactNativeFeatureFlags.useFiber
|
||||
? require('ReactNativeFiber')
|
||||
: require('ReactNativeStack');
|
||||
import type {ReactNativeType} from 'ReactNativeTypes';
|
||||
|
||||
let ReactNative;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactNative = ReactNativeFeatureFlags.useFiber
|
||||
? require('ReactNativeFiber-dev')
|
||||
: require('ReactNativeStack-dev');
|
||||
} else {
|
||||
ReactNative = ReactNativeFeatureFlags.useFiber
|
||||
? require('ReactNativeFiber-prod')
|
||||
: require('ReactNativeStack-prod');
|
||||
}
|
||||
|
||||
module.exports = (ReactNative: ReactNativeType);
|
20
Libraries/Renderer/shims/ReactNativeComponentTree.js
Normal file
20
Libraries/Renderer/shims/ReactNativeComponentTree.js
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeComponentTree
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactNative');
|
||||
|
||||
module.exports =
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactNativeComponentTree;
|
20
Libraries/Renderer/shims/ReactNativePropRegistry.js
Normal file
20
Libraries/Renderer/shims/ReactNativePropRegistry.js
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativePropRegistry
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactNative');
|
||||
|
||||
module.exports =
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactNativePropRegistry;
|
89
Libraries/Renderer/shims/ReactNativeTypes.js
Normal file
89
Libraries/Renderer/shims/ReactNativeTypes.js
Normal file
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeTypes
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import type React from 'react';
|
||||
|
||||
export type MeasureOnSuccessCallback = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
) => void;
|
||||
|
||||
export type MeasureInWindowOnSuccessCallback = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
) => void;
|
||||
|
||||
export type MeasureLayoutOnSuccessCallback = (
|
||||
left: number,
|
||||
top: number,
|
||||
width: number,
|
||||
height: number,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* This type keeps ReactNativeFiberHostComponent and NativeMethodsMixin in sync.
|
||||
* It can also provide types for ReactNative applications that use NMM or refs.
|
||||
*/
|
||||
export type NativeMethodsMixinType = {
|
||||
blur(): void,
|
||||
focus(): void,
|
||||
measure(callback: MeasureOnSuccessCallback): void,
|
||||
measureInWindow(callback: MeasureInWindowOnSuccessCallback): void,
|
||||
measureLayout(
|
||||
relativeToNativeNode: number,
|
||||
onSuccess: MeasureLayoutOnSuccessCallback,
|
||||
onFail: () => void,
|
||||
): void,
|
||||
setNativeProps(nativeProps: Object): void,
|
||||
};
|
||||
|
||||
type ReactNativeBaseComponentViewConfig = {
|
||||
validAttributes: Object,
|
||||
uiViewClassName: string,
|
||||
propTypes?: Object,
|
||||
};
|
||||
|
||||
type SecretInternalsType = {
|
||||
NativeMethodsMixin: NativeMethodsMixinType,
|
||||
createReactNativeComponentClass(
|
||||
viewConfig: ReactNativeBaseComponentViewConfig,
|
||||
): any,
|
||||
ReactNativeComponentTree: any,
|
||||
ReactNativePropRegistry: any,
|
||||
// TODO (bvaughn) Decide which additional types to expose here?
|
||||
// And how much information to fill in for the above types.
|
||||
};
|
||||
|
||||
/**
|
||||
* Flat ReactNative renderer bundles are too big for Flow to parse effeciently.
|
||||
* Provide minimal Flow typing for the high-level RN API and call it a day.
|
||||
*/
|
||||
export type ReactNativeType = {
|
||||
findNodeHandle(componentOrHandle: any): ?number,
|
||||
render(
|
||||
element: React.Element<any>,
|
||||
containerTag: any,
|
||||
callback: ?Function,
|
||||
): any,
|
||||
unmountComponentAtNode(containerTag: number): any,
|
||||
unmountComponentAtNodeAndRemoveContainer(containerTag: number): any,
|
||||
unstable_batchedUpdates: any, // TODO (bvaughn) Add types
|
||||
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: SecretInternalsType,
|
||||
};
|
@ -6,16 +6,13 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule getNextDebugID
|
||||
* @flow
|
||||
* @providesModule ReactPerf
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var nextDebugID = 1;
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactNative');
|
||||
|
||||
function getNextDebugID(): number {
|
||||
return nextDebugID++;
|
||||
}
|
||||
|
||||
module.exports = getNextDebugID;
|
||||
module.exports = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactPerf;
|
@ -12,9 +12,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ReactCoroutine, ReactYield} from 'ReactCoroutine';
|
||||
import type {ReactPortal} from 'ReactPortal';
|
||||
|
||||
export type ReactNode =
|
||||
| ReactElement<any>
|
||||
| ReactCoroutine
|
||||
@ -30,3 +27,26 @@ export type ReactNodeList = ReactEmpty | ReactNode;
|
||||
export type ReactText = string | number;
|
||||
|
||||
export type ReactEmpty = null | void | boolean;
|
||||
|
||||
export type ReactCoroutine = {
|
||||
$$typeof: Symbol | number,
|
||||
key: null | string,
|
||||
children: any,
|
||||
// This should be a more specific CoroutineHandler
|
||||
handler: (props: any, yields: Array<mixed>) => ReactNodeList,
|
||||
props: any,
|
||||
};
|
||||
|
||||
export type ReactYield = {
|
||||
$$typeof: Symbol | number,
|
||||
value: mixed,
|
||||
};
|
||||
|
||||
export type ReactPortal = {
|
||||
$$typeof: Symbol | number,
|
||||
key: null | string,
|
||||
containerInfo: any,
|
||||
children: ReactNodeList,
|
||||
// TODO: figure out the API for cross-renderer implementation.
|
||||
implementation: any,
|
||||
};
|
@ -6,9 +6,14 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactVersion
|
||||
* @providesModule TouchHistoryMath
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = '16.0.0-alpha.12';
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactNative');
|
||||
|
||||
module.exports =
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.TouchHistoryMath;
|
20
Libraries/Renderer/shims/createReactNativeComponentClass.js
Normal file
20
Libraries/Renderer/shims/createReactNativeComponentClass.js
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule createReactNativeComponentClass
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactNative');
|
||||
|
||||
module.exports =
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.createReactNativeComponentClass;
|
@ -6,15 +6,14 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactTypeOfInternalContext
|
||||
* @flow
|
||||
* @providesModule takeSnapshot
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type TypeOfInternalContext = number;
|
||||
const {
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
} = require('ReactNative');
|
||||
|
||||
module.exports = {
|
||||
NoContext: 0,
|
||||
AsyncUpdates: 1,
|
||||
};
|
||||
module.exports =
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.takeSnapshot;
|
@ -1,282 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule NativeMethodsMixin
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNative = require('ReactNative');
|
||||
var ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
|
||||
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||
var TextInputState = require('TextInputState');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var findNodeHandle = require('findNodeHandle');
|
||||
|
||||
var {
|
||||
mountSafeCallback,
|
||||
throwOnStylesProp,
|
||||
warnForStyleProps,
|
||||
} = require('NativeMethodsMixinUtils');
|
||||
|
||||
import type {
|
||||
MeasureInWindowOnSuccessCallback,
|
||||
MeasureLayoutOnSuccessCallback,
|
||||
MeasureOnSuccessCallback,
|
||||
} from 'NativeMethodsMixinUtils';
|
||||
import type {
|
||||
ReactNativeBaseComponentViewConfig,
|
||||
} from 'ReactNativeViewConfigRegistry';
|
||||
|
||||
/**
|
||||
* `NativeMethodsMixin` provides methods to access the underlying native
|
||||
* component directly. This can be useful in cases when you want to focus
|
||||
* a view or measure its on-screen dimensions, for example.
|
||||
*
|
||||
* The methods described here are available on most of the default components
|
||||
* provided by React Native. Note, however, that they are *not* available on
|
||||
* composite components that aren't directly backed by a native view. This will
|
||||
* generally include most components that you define in your own app. For more
|
||||
* information, see [Direct
|
||||
* Manipulation](docs/direct-manipulation.html).
|
||||
*/
|
||||
// TODO (bvaughn) Figure out how to use the NativeMethodsInterface type to-
|
||||
// ensure that these mixins and ReactNativeFiberHostComponent stay in sync.
|
||||
// Unfortunately, using it causes Flow to complain WRT createClass mixins:
|
||||
// "call of method `createClass`. Expected an exact object instead of ..."
|
||||
var NativeMethodsMixin = {
|
||||
/**
|
||||
* Determines the location on screen, width, and height of the given view and
|
||||
* returns the values via an async callback. If successful, the callback will
|
||||
* be called with the following arguments:
|
||||
*
|
||||
* - x
|
||||
* - y
|
||||
* - width
|
||||
* - height
|
||||
* - pageX
|
||||
* - pageY
|
||||
*
|
||||
* Note that these measurements are not available until after the rendering
|
||||
* has been completed in native. If you need the measurements as soon as
|
||||
* possible, consider using the [`onLayout`
|
||||
* prop](docs/view.html#onlayout) instead.
|
||||
*/
|
||||
measure: function(callback: MeasureOnSuccessCallback) {
|
||||
UIManager.measure(
|
||||
ReactNative.findNodeHandle(this),
|
||||
mountSafeCallback(this, callback),
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines the location of the given view in the window and returns the
|
||||
* values via an async callback. If the React root view is embedded in
|
||||
* another native view, this will give you the absolute coordinates. If
|
||||
* successful, the callback will be called with the following
|
||||
* arguments:
|
||||
*
|
||||
* - x
|
||||
* - y
|
||||
* - width
|
||||
* - height
|
||||
*
|
||||
* Note that these measurements are not available until after the rendering
|
||||
* has been completed in native.
|
||||
*/
|
||||
measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) {
|
||||
UIManager.measureInWindow(
|
||||
ReactNative.findNodeHandle(this),
|
||||
mountSafeCallback(this, callback),
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Like [`measure()`](#measure), but measures the view relative an ancestor,
|
||||
* specified as `relativeToNativeNode`. This means that the returned x, y
|
||||
* are relative to the origin x, y of the ancestor view.
|
||||
*
|
||||
* As always, to obtain a native node handle for a component, you can use
|
||||
* `ReactNative.findNodeHandle(component)`.
|
||||
*/
|
||||
measureLayout: function(
|
||||
relativeToNativeNode: number,
|
||||
onSuccess: MeasureLayoutOnSuccessCallback,
|
||||
onFail: () => void /* currently unused */,
|
||||
) {
|
||||
UIManager.measureLayout(
|
||||
ReactNative.findNodeHandle(this),
|
||||
relativeToNativeNode,
|
||||
mountSafeCallback(this, onFail),
|
||||
mountSafeCallback(this, onSuccess),
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* This function sends props straight to native. They will not participate in
|
||||
* future diff process - this means that if you do not include them in the
|
||||
* next render, they will remain active (see [Direct
|
||||
* Manipulation](docs/direct-manipulation.html)).
|
||||
*/
|
||||
setNativeProps: function(nativeProps: Object) {
|
||||
// Ensure ReactNative factory function has configured findNodeHandle.
|
||||
// Requiring it won't execute the factory function until first referenced.
|
||||
// It's possible for tests that use ReactTestRenderer to reach this point,
|
||||
// Without having executed ReactNative.
|
||||
// Defer the factory function until now to avoid a cycle with UIManager.
|
||||
// TODO (bvaughn) Remove this once ReactNativeStack is dropped.
|
||||
require('ReactNative');
|
||||
|
||||
injectedSetNativeProps(this, nativeProps);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests focus for the given input or view. The exact behavior triggered
|
||||
* will depend on the platform and type of view.
|
||||
*/
|
||||
focus: function() {
|
||||
TextInputState.focusTextInput(ReactNative.findNodeHandle(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes focus from an input or view. This is the opposite of `focus()`.
|
||||
*/
|
||||
blur: function() {
|
||||
TextInputState.blurTextInput(ReactNative.findNodeHandle(this));
|
||||
},
|
||||
};
|
||||
|
||||
// TODO (bvaughn) Inline this once ReactNativeStack is dropped.
|
||||
function setNativePropsFiber(componentOrHandle: any, nativeProps: Object) {
|
||||
// Class components don't have viewConfig -> validateAttributes.
|
||||
// Nor does it make sense to set native props on a non-native component.
|
||||
// Instead, find the nearest host component and set props on it.
|
||||
// Use findNodeHandle() rather than ReactNative.findNodeHandle() because
|
||||
// We want the instance/wrapper (not the native tag).
|
||||
let maybeInstance;
|
||||
|
||||
// Fiber errors if findNodeHandle is called for an umounted component.
|
||||
// Tests using ReactTestRenderer will trigger this case indirectly.
|
||||
// Mimicking stack behavior, we should silently ignore this case.
|
||||
// TODO Fix ReactTestRenderer so we can remove this try/catch.
|
||||
try {
|
||||
maybeInstance = findNodeHandle(componentOrHandle);
|
||||
} catch (error) {}
|
||||
|
||||
// If there is no host component beneath this we should fail silently.
|
||||
// This is not an error; it could mean a class component rendered null.
|
||||
if (maybeInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewConfig: ReactNativeBaseComponentViewConfig =
|
||||
maybeInstance.viewConfig;
|
||||
|
||||
if (__DEV__) {
|
||||
warnForStyleProps(nativeProps, viewConfig.validAttributes);
|
||||
}
|
||||
|
||||
var updatePayload = ReactNativeAttributePayload.create(
|
||||
nativeProps,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
UIManager.updateView(
|
||||
maybeInstance._nativeTag,
|
||||
viewConfig.uiViewClassName,
|
||||
updatePayload,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO (bvaughn) Remove this once ReactNativeStack is dropped.
|
||||
function setNativePropsStack(componentOrHandle: any, nativeProps: Object) {
|
||||
// Class components don't have viewConfig -> validateAttributes.
|
||||
// Nor does it make sense to set native props on a non-native component.
|
||||
// Instead, find the nearest host component and set props on it.
|
||||
// Use findNodeHandle() rather than ReactNative.findNodeHandle() because
|
||||
// We want the instance/wrapper (not the native tag).
|
||||
let maybeInstance = findNodeHandle(componentOrHandle);
|
||||
|
||||
// If there is no host component beneath this we should fail silently.
|
||||
// This is not an error; it could mean a class component rendered null.
|
||||
if (maybeInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let viewConfig: ReactNativeBaseComponentViewConfig;
|
||||
if (maybeInstance.viewConfig !== undefined) {
|
||||
// ReactNativeBaseComponent
|
||||
viewConfig = maybeInstance.viewConfig;
|
||||
} else if (
|
||||
maybeInstance._instance !== undefined &&
|
||||
maybeInstance._instance.viewConfig !== undefined
|
||||
) {
|
||||
// ReactCompositeComponentWrapper
|
||||
// Some instances (eg Text) define their own viewConfig
|
||||
viewConfig = maybeInstance._instance.viewConfig;
|
||||
} else {
|
||||
// ReactCompositeComponentWrapper
|
||||
// Other instances (eg TextInput) defer to their children's viewConfig
|
||||
while (maybeInstance._renderedComponent !== undefined) {
|
||||
maybeInstance = maybeInstance._renderedComponent;
|
||||
}
|
||||
viewConfig = maybeInstance.viewConfig;
|
||||
}
|
||||
|
||||
const tag: number = typeof maybeInstance.getHostNode === 'function'
|
||||
? maybeInstance.getHostNode()
|
||||
: maybeInstance._rootNodeID;
|
||||
|
||||
if (__DEV__) {
|
||||
warnForStyleProps(nativeProps, viewConfig.validAttributes);
|
||||
}
|
||||
|
||||
var updatePayload = ReactNativeAttributePayload.create(
|
||||
nativeProps,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
UIManager.updateView(tag, viewConfig.uiViewClassName, updatePayload);
|
||||
}
|
||||
|
||||
// Switching based on fiber vs stack to avoid a lot of inline checks at runtime.
|
||||
// HACK Normally this injection would be done by the renderer, but in this case
|
||||
// that would result in a cycle between ReactNative and NativeMethodsMixin.
|
||||
// We avoid requiring additional code for this injection so it's probably ok?
|
||||
// TODO (bvaughn) Remove this once ReactNativeStack is gone.
|
||||
let injectedSetNativeProps: (
|
||||
componentOrHandle: any,
|
||||
nativeProps: Object,
|
||||
) => void;
|
||||
if (ReactNativeFeatureFlags.useFiber) {
|
||||
injectedSetNativeProps = setNativePropsFiber;
|
||||
} else {
|
||||
injectedSetNativeProps = setNativePropsStack;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
// hide this from Flow since we can't define these properties outside of
|
||||
// __DEV__ without actually implementing them (setting them to undefined
|
||||
// isn't allowed by ReactClass)
|
||||
var NativeMethodsMixin_DEV = (NativeMethodsMixin: any);
|
||||
invariant(
|
||||
!NativeMethodsMixin_DEV.componentWillMount &&
|
||||
!NativeMethodsMixin_DEV.componentWillReceiveProps,
|
||||
'Do not override existing functions.',
|
||||
);
|
||||
NativeMethodsMixin_DEV.componentWillMount = function() {
|
||||
throwOnStylesProp(this, this.props);
|
||||
};
|
||||
NativeMethodsMixin_DEV.componentWillReceiveProps = function(newProps) {
|
||||
throwOnStylesProp(this, newProps);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = NativeMethodsMixin;
|
@ -1,122 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule NativeMethodsMixinUtils
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
export type MeasureOnSuccessCallback = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
) => void;
|
||||
|
||||
export type MeasureInWindowOnSuccessCallback = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
) => void;
|
||||
|
||||
export type MeasureLayoutOnSuccessCallback = (
|
||||
left: number,
|
||||
top: number,
|
||||
width: number,
|
||||
height: number,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Shared between ReactNativeFiberHostComponent and NativeMethodsMixin to keep
|
||||
* API in sync.
|
||||
*/
|
||||
export interface NativeMethodsInterface {
|
||||
blur(): void,
|
||||
focus(): void,
|
||||
measure(callback: MeasureOnSuccessCallback): void,
|
||||
measureInWindow(callback: MeasureInWindowOnSuccessCallback): void,
|
||||
measureLayout(
|
||||
relativeToNativeNode: number,
|
||||
onSuccess: MeasureLayoutOnSuccessCallback,
|
||||
onFail: () => void,
|
||||
): void,
|
||||
setNativeProps(nativeProps: Object): void,
|
||||
}
|
||||
|
||||
/**
|
||||
* In the future, we should cleanup callbacks by cancelling them instead of
|
||||
* using this.
|
||||
*/
|
||||
function mountSafeCallback(context: any, callback: ?Function): any {
|
||||
return function() {
|
||||
if (!callback) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof context.__isMounted === 'boolean') {
|
||||
// TODO(gaearon): this is gross and should be removed.
|
||||
// It is currently necessary because View uses createClass,
|
||||
// and so any measure() calls on View (which are done by React
|
||||
// DevTools) trigger the isMounted() deprecation warning.
|
||||
if (!context.__isMounted) {
|
||||
return undefined;
|
||||
}
|
||||
// The else branch is important so that we don't
|
||||
// trigger the deprecation warning by calling isMounted.
|
||||
} else if (typeof context.isMounted === 'function') {
|
||||
if (!context.isMounted()) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return callback.apply(context, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
function throwOnStylesProp(component: any, props: any) {
|
||||
if (props.styles !== undefined) {
|
||||
var owner = component._owner || null;
|
||||
var name = component.constructor.displayName;
|
||||
var msg =
|
||||
'`styles` is not a supported property of `' +
|
||||
name +
|
||||
'`, did ' +
|
||||
'you mean `style` (singular)?';
|
||||
if (owner && owner.constructor && owner.constructor.displayName) {
|
||||
msg +=
|
||||
'\n\nCheck the `' +
|
||||
owner.constructor.displayName +
|
||||
'` parent ' +
|
||||
' component.';
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function warnForStyleProps(props: any, validAttributes: any) {
|
||||
for (var key in validAttributes.style) {
|
||||
if (!(validAttributes[key] || props[key] === undefined)) {
|
||||
console.error(
|
||||
'You are setting the style `{ ' +
|
||||
key +
|
||||
': ... }` as a prop. You ' +
|
||||
'should nest it in a style object. ' +
|
||||
'E.g. `{ style: { ' +
|
||||
key +
|
||||
': ... } }`',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mountSafeCallback,
|
||||
throwOnStylesProp,
|
||||
warnForStyleProps,
|
||||
};
|
@ -1,516 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeAttributePayload
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativePropRegistry = require('ReactNativePropRegistry');
|
||||
|
||||
var deepDiffer = require('deepDiffer');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
|
||||
var emptyObject = {};
|
||||
|
||||
/**
|
||||
* Create a payload that contains all the updates between two sets of props.
|
||||
*
|
||||
* These helpers are all encapsulated into a single module, because they use
|
||||
* mutation as a performance optimization which leads to subtle shared
|
||||
* dependencies between the code paths. To avoid this mutable state leaking
|
||||
* across modules, I've kept them isolated to this module.
|
||||
*/
|
||||
|
||||
type AttributeDiffer = (prevProp: mixed, nextProp: mixed) => boolean;
|
||||
type AttributePreprocessor = (nextProp: mixed) => mixed;
|
||||
|
||||
type CustomAttributeConfiguration =
|
||||
| {diff: AttributeDiffer, process: AttributePreprocessor}
|
||||
| {diff: AttributeDiffer}
|
||||
| {process: AttributePreprocessor};
|
||||
|
||||
type AttributeConfiguration = {
|
||||
[key: string]:
|
||||
| CustomAttributeConfiguration
|
||||
| AttributeConfiguration /*| boolean*/,
|
||||
};
|
||||
|
||||
type NestedNode = Array<NestedNode> | Object | number;
|
||||
|
||||
// Tracks removed keys
|
||||
var removedKeys = null;
|
||||
var removedKeyCount = 0;
|
||||
|
||||
function defaultDiffer(prevProp: mixed, nextProp: mixed): boolean {
|
||||
if (typeof nextProp !== 'object' || nextProp === null) {
|
||||
// Scalars have already been checked for equality
|
||||
return true;
|
||||
} else {
|
||||
// For objects and arrays, the default diffing algorithm is a deep compare
|
||||
return deepDiffer(prevProp, nextProp);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveObject(idOrObject: number | Object): Object {
|
||||
if (typeof idOrObject === 'number') {
|
||||
return ReactNativePropRegistry.getByID(idOrObject);
|
||||
}
|
||||
return idOrObject;
|
||||
}
|
||||
|
||||
function restoreDeletedValuesInNestedArray(
|
||||
updatePayload: Object,
|
||||
node: NestedNode,
|
||||
validAttributes: AttributeConfiguration,
|
||||
) {
|
||||
if (Array.isArray(node)) {
|
||||
var i = node.length;
|
||||
while (i-- && removedKeyCount > 0) {
|
||||
restoreDeletedValuesInNestedArray(
|
||||
updatePayload,
|
||||
node[i],
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
} else if (node && removedKeyCount > 0) {
|
||||
var obj = resolveObject(node);
|
||||
for (var propKey in removedKeys) {
|
||||
if (!removedKeys[propKey]) {
|
||||
continue;
|
||||
}
|
||||
var nextProp = obj[propKey];
|
||||
if (nextProp === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var attributeConfig = validAttributes[propKey];
|
||||
if (!attributeConfig) {
|
||||
continue; // not a valid native prop
|
||||
}
|
||||
|
||||
if (typeof nextProp === 'function') {
|
||||
nextProp = true;
|
||||
}
|
||||
if (typeof nextProp === 'undefined') {
|
||||
nextProp = null;
|
||||
}
|
||||
|
||||
if (typeof attributeConfig !== 'object') {
|
||||
// case: !Object is the default case
|
||||
updatePayload[propKey] = nextProp;
|
||||
} else if (
|
||||
typeof attributeConfig.diff === 'function' ||
|
||||
typeof attributeConfig.process === 'function'
|
||||
) {
|
||||
// case: CustomAttributeConfiguration
|
||||
var nextValue = typeof attributeConfig.process === 'function'
|
||||
? attributeConfig.process(nextProp)
|
||||
: nextProp;
|
||||
updatePayload[propKey] = nextValue;
|
||||
}
|
||||
removedKeys[propKey] = false;
|
||||
removedKeyCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function diffNestedArrayProperty(
|
||||
updatePayload: ?Object,
|
||||
prevArray: Array<NestedNode>,
|
||||
nextArray: Array<NestedNode>,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): ?Object {
|
||||
var minLength = prevArray.length < nextArray.length
|
||||
? prevArray.length
|
||||
: nextArray.length;
|
||||
var i;
|
||||
for (i = 0; i < minLength; i++) {
|
||||
// Diff any items in the array in the forward direction. Repeated keys
|
||||
// will be overwritten by later values.
|
||||
updatePayload = diffNestedProperty(
|
||||
updatePayload,
|
||||
prevArray[i],
|
||||
nextArray[i],
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
for (; i < prevArray.length; i++) {
|
||||
// Clear out all remaining properties.
|
||||
updatePayload = clearNestedProperty(
|
||||
updatePayload,
|
||||
prevArray[i],
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
for (; i < nextArray.length; i++) {
|
||||
// Add all remaining properties.
|
||||
updatePayload = addNestedProperty(
|
||||
updatePayload,
|
||||
nextArray[i],
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
function diffNestedProperty(
|
||||
updatePayload: ?Object,
|
||||
prevProp: NestedNode,
|
||||
nextProp: NestedNode,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): ?Object {
|
||||
if (!updatePayload && prevProp === nextProp) {
|
||||
// If no properties have been added, then we can bail out quickly on object
|
||||
// equality.
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (!prevProp || !nextProp) {
|
||||
if (nextProp) {
|
||||
return addNestedProperty(updatePayload, nextProp, validAttributes);
|
||||
}
|
||||
if (prevProp) {
|
||||
return clearNestedProperty(updatePayload, prevProp, validAttributes);
|
||||
}
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (!Array.isArray(prevProp) && !Array.isArray(nextProp)) {
|
||||
// Both are leaves, we can diff the leaves.
|
||||
return diffProperties(
|
||||
updatePayload,
|
||||
resolveObject(prevProp),
|
||||
resolveObject(nextProp),
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(prevProp) && Array.isArray(nextProp)) {
|
||||
// Both are arrays, we can diff the arrays.
|
||||
return diffNestedArrayProperty(
|
||||
updatePayload,
|
||||
prevProp,
|
||||
nextProp,
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(prevProp)) {
|
||||
return diffProperties(
|
||||
updatePayload,
|
||||
// $FlowFixMe - We know that this is always an object when the input is.
|
||||
flattenStyle(prevProp),
|
||||
// $FlowFixMe - We know that this isn't an array because of above flow.
|
||||
resolveObject(nextProp),
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
return diffProperties(
|
||||
updatePayload,
|
||||
resolveObject(prevProp),
|
||||
// $FlowFixMe - We know that this is always an object when the input is.
|
||||
flattenStyle(nextProp),
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* addNestedProperty takes a single set of props and valid attribute
|
||||
* attribute configurations. It processes each prop and adds it to the
|
||||
* updatePayload.
|
||||
*/
|
||||
function addNestedProperty(
|
||||
updatePayload: ?Object,
|
||||
nextProp: NestedNode,
|
||||
validAttributes: AttributeConfiguration,
|
||||
) {
|
||||
if (!nextProp) {
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (!Array.isArray(nextProp)) {
|
||||
// Add each property of the leaf.
|
||||
return addProperties(
|
||||
updatePayload,
|
||||
resolveObject(nextProp),
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
for (var i = 0; i < nextProp.length; i++) {
|
||||
// Add all the properties of the array.
|
||||
updatePayload = addNestedProperty(
|
||||
updatePayload,
|
||||
nextProp[i],
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* clearNestedProperty takes a single set of props and valid attributes. It
|
||||
* adds a null sentinel to the updatePayload, for each prop key.
|
||||
*/
|
||||
function clearNestedProperty(
|
||||
updatePayload: ?Object,
|
||||
prevProp: NestedNode,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): ?Object {
|
||||
if (!prevProp) {
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (!Array.isArray(prevProp)) {
|
||||
// Add each property of the leaf.
|
||||
return clearProperties(
|
||||
updatePayload,
|
||||
resolveObject(prevProp),
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
for (var i = 0; i < prevProp.length; i++) {
|
||||
// Add all the properties of the array.
|
||||
updatePayload = clearNestedProperty(
|
||||
updatePayload,
|
||||
prevProp[i],
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* diffProperties takes two sets of props and a set of valid attributes
|
||||
* and write to updatePayload the values that changed or were deleted.
|
||||
* If no updatePayload is provided, a new one is created and returned if
|
||||
* anything changed.
|
||||
*/
|
||||
function diffProperties(
|
||||
updatePayload: ?Object,
|
||||
prevProps: Object,
|
||||
nextProps: Object,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): ?Object {
|
||||
var attributeConfig: ?(CustomAttributeConfiguration | AttributeConfiguration);
|
||||
var nextProp;
|
||||
var prevProp;
|
||||
|
||||
for (var propKey in nextProps) {
|
||||
attributeConfig = validAttributes[propKey];
|
||||
if (!attributeConfig) {
|
||||
continue; // not a valid native prop
|
||||
}
|
||||
|
||||
prevProp = prevProps[propKey];
|
||||
nextProp = nextProps[propKey];
|
||||
|
||||
// functions are converted to booleans as markers that the associated
|
||||
// events should be sent from native.
|
||||
if (typeof nextProp === 'function') {
|
||||
nextProp = (true: any);
|
||||
// If nextProp is not a function, then don't bother changing prevProp
|
||||
// since nextProp will win and go into the updatePayload regardless.
|
||||
if (typeof prevProp === 'function') {
|
||||
prevProp = (true: any);
|
||||
}
|
||||
}
|
||||
|
||||
// An explicit value of undefined is treated as a null because it overrides
|
||||
// any other preceding value.
|
||||
if (typeof nextProp === 'undefined') {
|
||||
nextProp = (null: any);
|
||||
if (typeof prevProp === 'undefined') {
|
||||
prevProp = (null: any);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedKeys) {
|
||||
removedKeys[propKey] = false;
|
||||
}
|
||||
|
||||
if (updatePayload && updatePayload[propKey] !== undefined) {
|
||||
// Something else already triggered an update to this key because another
|
||||
// value diffed. Since we're now later in the nested arrays our value is
|
||||
// more important so we need to calculate it and override the existing
|
||||
// value. It doesn't matter if nothing changed, we'll set it anyway.
|
||||
|
||||
// Pattern match on: attributeConfig
|
||||
if (typeof attributeConfig !== 'object') {
|
||||
// case: !Object is the default case
|
||||
updatePayload[propKey] = nextProp;
|
||||
} else if (
|
||||
typeof attributeConfig.diff === 'function' ||
|
||||
typeof attributeConfig.process === 'function'
|
||||
) {
|
||||
// case: CustomAttributeConfiguration
|
||||
var nextValue = typeof attributeConfig.process === 'function'
|
||||
? attributeConfig.process(nextProp)
|
||||
: nextProp;
|
||||
updatePayload[propKey] = nextValue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prevProp === nextProp) {
|
||||
continue; // nothing changed
|
||||
}
|
||||
|
||||
// Pattern match on: attributeConfig
|
||||
if (typeof attributeConfig !== 'object') {
|
||||
// case: !Object is the default case
|
||||
if (defaultDiffer(prevProp, nextProp)) {
|
||||
// a normal leaf has changed
|
||||
(updatePayload || (updatePayload = {}))[propKey] = nextProp;
|
||||
}
|
||||
} else if (
|
||||
typeof attributeConfig.diff === 'function' ||
|
||||
typeof attributeConfig.process === 'function'
|
||||
) {
|
||||
// case: CustomAttributeConfiguration
|
||||
var shouldUpdate =
|
||||
prevProp === undefined ||
|
||||
(typeof attributeConfig.diff === 'function'
|
||||
? attributeConfig.diff(prevProp, nextProp)
|
||||
: defaultDiffer(prevProp, nextProp));
|
||||
if (shouldUpdate) {
|
||||
nextValue = typeof attributeConfig.process === 'function'
|
||||
? attributeConfig.process(nextProp)
|
||||
: nextProp;
|
||||
(updatePayload || (updatePayload = {}))[propKey] = nextValue;
|
||||
}
|
||||
} else {
|
||||
// default: fallthrough case when nested properties are defined
|
||||
removedKeys = null;
|
||||
removedKeyCount = 0;
|
||||
// We think that attributeConfig is not CustomAttributeConfiguration at
|
||||
// this point so we assume it must be AttributeConfiguration.
|
||||
updatePayload = diffNestedProperty(
|
||||
updatePayload,
|
||||
prevProp,
|
||||
nextProp,
|
||||
((attributeConfig: any): AttributeConfiguration),
|
||||
);
|
||||
if (removedKeyCount > 0 && updatePayload) {
|
||||
restoreDeletedValuesInNestedArray(
|
||||
updatePayload,
|
||||
nextProp,
|
||||
((attributeConfig: any): AttributeConfiguration),
|
||||
);
|
||||
removedKeys = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also iterate through all the previous props to catch any that have been
|
||||
// removed and make sure native gets the signal so it can reset them to the
|
||||
// default.
|
||||
for (propKey in prevProps) {
|
||||
if (nextProps[propKey] !== undefined) {
|
||||
continue; // we've already covered this key in the previous pass
|
||||
}
|
||||
attributeConfig = validAttributes[propKey];
|
||||
if (!attributeConfig) {
|
||||
continue; // not a valid native prop
|
||||
}
|
||||
|
||||
if (updatePayload && updatePayload[propKey] !== undefined) {
|
||||
// This was already updated to a diff result earlier.
|
||||
continue;
|
||||
}
|
||||
|
||||
prevProp = prevProps[propKey];
|
||||
if (prevProp === undefined) {
|
||||
continue; // was already empty anyway
|
||||
}
|
||||
// Pattern match on: attributeConfig
|
||||
if (
|
||||
typeof attributeConfig !== 'object' ||
|
||||
typeof attributeConfig.diff === 'function' ||
|
||||
typeof attributeConfig.process === 'function'
|
||||
) {
|
||||
// case: CustomAttributeConfiguration | !Object
|
||||
// Flag the leaf property for removal by sending a sentinel.
|
||||
(updatePayload || (updatePayload = {}))[propKey] = null;
|
||||
if (!removedKeys) {
|
||||
removedKeys = {};
|
||||
}
|
||||
if (!removedKeys[propKey]) {
|
||||
removedKeys[propKey] = true;
|
||||
removedKeyCount++;
|
||||
}
|
||||
} else {
|
||||
// default:
|
||||
// This is a nested attribute configuration where all the properties
|
||||
// were removed so we need to go through and clear out all of them.
|
||||
updatePayload = clearNestedProperty(
|
||||
updatePayload,
|
||||
prevProp,
|
||||
((attributeConfig: any): AttributeConfiguration),
|
||||
);
|
||||
}
|
||||
}
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* addProperties adds all the valid props to the payload after being processed.
|
||||
*/
|
||||
function addProperties(
|
||||
updatePayload: ?Object,
|
||||
props: Object,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): ?Object {
|
||||
// TODO: Fast path
|
||||
return diffProperties(updatePayload, emptyObject, props, validAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* clearProperties clears all the previous props by adding a null sentinel
|
||||
* to the payload for each valid key.
|
||||
*/
|
||||
function clearProperties(
|
||||
updatePayload: ?Object,
|
||||
prevProps: Object,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): ?Object {
|
||||
// TODO: Fast path
|
||||
return diffProperties(updatePayload, prevProps, emptyObject, validAttributes);
|
||||
}
|
||||
|
||||
var ReactNativeAttributePayload = {
|
||||
create: function(
|
||||
props: Object,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): ?Object {
|
||||
return addProperties(
|
||||
null, // updatePayload
|
||||
props,
|
||||
validAttributes,
|
||||
);
|
||||
},
|
||||
|
||||
diff: function(
|
||||
prevProps: Object,
|
||||
nextProps: Object,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): ?Object {
|
||||
return diffProperties(
|
||||
null, // updatePayload
|
||||
prevProps,
|
||||
nextProps,
|
||||
validAttributes,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeAttributePayload;
|
@ -1,194 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeBaseComponent
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var ReactMultiChild = require('ReactMultiChild');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
||||
|
||||
type ReactNativeBaseComponentViewConfig = {
|
||||
validAttributes: Object,
|
||||
uiViewClassName: string,
|
||||
};
|
||||
|
||||
// require('UIManagerStatTracker').install(); // uncomment to enable
|
||||
|
||||
/**
|
||||
* @constructor ReactNativeBaseComponent
|
||||
* @extends ReactComponent
|
||||
* @extends ReactMultiChild
|
||||
* @param {!object} UIKit View Configuration.
|
||||
*/
|
||||
var ReactNativeBaseComponent = function(
|
||||
viewConfig: ReactNativeBaseComponentViewConfig,
|
||||
) {
|
||||
this.viewConfig = viewConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mixin for containers that contain UIViews. NOTE: markup is rendered markup
|
||||
* which is a `viewID` ... see the return value for `mountComponent` !
|
||||
*/
|
||||
ReactNativeBaseComponent.Mixin = {
|
||||
getPublicInstance: function() {
|
||||
// TODO: This should probably use a composite wrapper
|
||||
return this;
|
||||
},
|
||||
|
||||
unmountComponent: function(safely, skipLifecycle) {
|
||||
ReactNativeComponentTree.uncacheNode(this);
|
||||
this.unmountChildren(safely, skipLifecycle);
|
||||
this._rootNodeID = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Every native component is responsible for allocating its own `tag`, and
|
||||
* issuing the native `createView` command. But it is not responsible for
|
||||
* recording the fact that its own `rootNodeID` is associated with a
|
||||
* `nodeHandle`. Only the code that actually adds its `nodeHandle` (`tag`) as
|
||||
* a child of a container can confidently record that in
|
||||
* `ReactNativeTagHandles`.
|
||||
*/
|
||||
initializeChildren: function(children, containerTag, transaction, context) {
|
||||
var mountImages = this.mountChildren(children, transaction, context);
|
||||
// In a well balanced tree, half of the nodes are in the bottom row and have
|
||||
// no children - let's avoid calling out to the native bridge for a large
|
||||
// portion of the children.
|
||||
if (mountImages.length) {
|
||||
// TODO: Pool these per platform view class. Reusing the `mountImages`
|
||||
// array would likely be a jit deopt.
|
||||
var createdTags = [];
|
||||
for (var i = 0, l = mountImages.length; i < l; i++) {
|
||||
var mountImage = mountImages[i];
|
||||
var childTag = mountImage;
|
||||
createdTags[i] = childTag;
|
||||
}
|
||||
UIManager.setChildren(containerTag, createdTags);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the component's currently mounted representation.
|
||||
*
|
||||
* @param {object} nextElement
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @param {object} context
|
||||
* @internal
|
||||
*/
|
||||
receiveComponent: function(nextElement, transaction, context) {
|
||||
var prevElement = this._currentElement;
|
||||
this._currentElement = nextElement;
|
||||
|
||||
if (__DEV__) {
|
||||
for (var key in this.viewConfig.validAttributes) {
|
||||
if (nextElement.props.hasOwnProperty(key)) {
|
||||
deepFreezeAndThrowOnMutationInDev(nextElement.props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updatePayload = ReactNativeAttributePayload.diff(
|
||||
prevElement.props,
|
||||
nextElement.props,
|
||||
this.viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
if (updatePayload) {
|
||||
UIManager.updateView(
|
||||
this._rootNodeID,
|
||||
this.viewConfig.uiViewClassName,
|
||||
updatePayload,
|
||||
);
|
||||
}
|
||||
|
||||
this.updateChildren(nextElement.props.children, transaction, context);
|
||||
},
|
||||
|
||||
/**
|
||||
* Currently this still uses IDs for reconciliation so this can return null.
|
||||
*
|
||||
* @return {null} Null.
|
||||
*/
|
||||
getHostNode: function() {
|
||||
return this._rootNodeID;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {ReactNativeReconcileTransaction} transaction
|
||||
* @param {?ReactNativeBaseComponent} the parent component instance
|
||||
* @param {?object} info about the host container
|
||||
* @param {object} context
|
||||
* @return {string} Unique iOS view tag.
|
||||
*/
|
||||
mountComponent: function(
|
||||
transaction,
|
||||
hostParent,
|
||||
hostContainerInfo,
|
||||
context,
|
||||
) {
|
||||
var tag = ReactNativeTagHandles.allocateTag();
|
||||
|
||||
this._rootNodeID = tag;
|
||||
this._hostParent = hostParent;
|
||||
this._hostContainerInfo = hostContainerInfo;
|
||||
|
||||
if (__DEV__) {
|
||||
for (var key in this.viewConfig.validAttributes) {
|
||||
if (this._currentElement.props.hasOwnProperty(key)) {
|
||||
deepFreezeAndThrowOnMutationInDev(this._currentElement.props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updatePayload = ReactNativeAttributePayload.create(
|
||||
this._currentElement.props,
|
||||
this.viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
var nativeTopRootTag = hostContainerInfo._tag;
|
||||
UIManager.createView(
|
||||
tag,
|
||||
this.viewConfig.uiViewClassName,
|
||||
nativeTopRootTag,
|
||||
updatePayload,
|
||||
);
|
||||
|
||||
ReactNativeComponentTree.precacheNode(this, tag);
|
||||
|
||||
this.initializeChildren(
|
||||
this._currentElement.props.children,
|
||||
tag,
|
||||
transaction,
|
||||
context,
|
||||
);
|
||||
return tag;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Order of mixins is important. ReactNativeBaseComponent overrides methods in
|
||||
* ReactMultiChild.
|
||||
*/
|
||||
Object.assign(
|
||||
ReactNativeBaseComponent.prototype,
|
||||
ReactMultiChild,
|
||||
ReactNativeBaseComponent.Mixin,
|
||||
NativeMethodsMixin,
|
||||
);
|
||||
|
||||
module.exports = ReactNativeBaseComponent;
|
@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeBridgeEventPlugin
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var EventPropagators = require('EventPropagators');
|
||||
var SyntheticEvent = require('SyntheticEvent');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
var customBubblingEventTypes = UIManager.customBubblingEventTypes;
|
||||
var customDirectEventTypes = UIManager.customDirectEventTypes;
|
||||
|
||||
var allTypesByEventName = {};
|
||||
|
||||
for (var bubblingTypeName in customBubblingEventTypes) {
|
||||
allTypesByEventName[bubblingTypeName] =
|
||||
customBubblingEventTypes[bubblingTypeName];
|
||||
}
|
||||
|
||||
for (var directTypeName in customDirectEventTypes) {
|
||||
warning(
|
||||
!customBubblingEventTypes[directTypeName],
|
||||
'Event cannot be both direct and bubbling: %s',
|
||||
directTypeName,
|
||||
);
|
||||
allTypesByEventName[directTypeName] = customDirectEventTypes[directTypeName];
|
||||
}
|
||||
|
||||
var ReactNativeBridgeEventPlugin = {
|
||||
eventTypes: {...customBubblingEventTypes, ...customDirectEventTypes},
|
||||
|
||||
/**
|
||||
* @see {EventPluginHub.extractEvents}
|
||||
*/
|
||||
extractEvents: function(
|
||||
topLevelType: string,
|
||||
targetInst: Object,
|
||||
nativeEvent: Event,
|
||||
nativeEventTarget: Object,
|
||||
): ?Object {
|
||||
var bubbleDispatchConfig = customBubblingEventTypes[topLevelType];
|
||||
var directDispatchConfig = customDirectEventTypes[topLevelType];
|
||||
var event = SyntheticEvent.getPooled(
|
||||
bubbleDispatchConfig || directDispatchConfig,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
);
|
||||
if (bubbleDispatchConfig) {
|
||||
EventPropagators.accumulateTwoPhaseDispatches(event);
|
||||
} else if (directDispatchConfig) {
|
||||
EventPropagators.accumulateDirectDispatches(event);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeBridgeEventPlugin;
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeComponentEnvironment
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeDOMIDOperations = require('ReactNativeDOMIDOperations');
|
||||
var ReactNativeReconcileTransaction = require('ReactNativeReconcileTransaction');
|
||||
|
||||
var ReactNativeComponentEnvironment = {
|
||||
processChildrenUpdates: ReactNativeDOMIDOperations.dangerouslyProcessChildrenUpdates,
|
||||
|
||||
replaceNodeWithMarkup: ReactNativeDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID,
|
||||
|
||||
/**
|
||||
* @param {DOMElement} Element to clear.
|
||||
*/
|
||||
clearNode: function(/*containerView*/) {},
|
||||
|
||||
ReactReconcileTransaction: ReactNativeReconcileTransaction,
|
||||
};
|
||||
|
||||
module.exports = ReactNativeComponentEnvironment;
|
@ -1,92 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeComponentTree
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
var instanceCache = {};
|
||||
var instanceProps = {};
|
||||
|
||||
/**
|
||||
* Drill down (through composites and empty components) until we get a host or
|
||||
* host text component.
|
||||
*
|
||||
* This is pretty polymorphic but unavoidable with the current structure we have
|
||||
* for `_renderedChildren`.
|
||||
*/
|
||||
function getRenderedHostOrTextFromComponent(component) {
|
||||
var rendered;
|
||||
while ((rendered = component._renderedComponent)) {
|
||||
component = rendered;
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate `_hostNode` on the rendered host/text component with the given
|
||||
* DOM node. The passed `inst` can be a composite.
|
||||
*/
|
||||
function precacheNode(inst, tag) {
|
||||
var nativeInst = getRenderedHostOrTextFromComponent(inst);
|
||||
instanceCache[tag] = nativeInst;
|
||||
}
|
||||
|
||||
function precacheFiberNode(hostInst, tag) {
|
||||
instanceCache[tag] = hostInst;
|
||||
}
|
||||
|
||||
function uncacheNode(inst) {
|
||||
var tag = inst._rootNodeID;
|
||||
if (tag) {
|
||||
delete instanceCache[tag];
|
||||
}
|
||||
}
|
||||
|
||||
function uncacheFiberNode(tag) {
|
||||
delete instanceCache[tag];
|
||||
delete instanceProps[tag];
|
||||
}
|
||||
|
||||
function getInstanceFromTag(tag) {
|
||||
return instanceCache[tag] || null;
|
||||
}
|
||||
|
||||
function getTagFromInstance(inst) {
|
||||
// TODO (bvaughn) Clean up once Stack is deprecated
|
||||
var tag = typeof inst.tag !== 'number'
|
||||
? inst._rootNodeID
|
||||
: inst.stateNode._nativeTag;
|
||||
invariant(tag, 'All native instances should have a tag.');
|
||||
return tag;
|
||||
}
|
||||
|
||||
function getFiberCurrentPropsFromNode(stateNode) {
|
||||
return instanceProps[stateNode._nativeTag] || null;
|
||||
}
|
||||
|
||||
function updateFiberProps(tag, props) {
|
||||
instanceProps[tag] = props;
|
||||
}
|
||||
|
||||
var ReactNativeComponentTree = {
|
||||
getClosestInstanceFromNode: getInstanceFromTag,
|
||||
getInstanceFromNode: getInstanceFromTag,
|
||||
getNodeFromInstance: getTagFromInstance,
|
||||
precacheFiberNode,
|
||||
precacheNode,
|
||||
uncacheFiberNode,
|
||||
uncacheNode,
|
||||
getFiberCurrentPropsFromNode,
|
||||
updateFiberProps,
|
||||
};
|
||||
|
||||
module.exports = ReactNativeComponentTree;
|
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeContainerInfo
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function ReactNativeContainerInfo(tag: number) {
|
||||
var info = {
|
||||
_tag: tag,
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
module.exports = ReactNativeContainerInfo;
|
@ -1,85 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeDOMIDOperations
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
/**
|
||||
* Updates a component's children by processing a series of updates.
|
||||
* For each of the update/create commands, the `fromIndex` refers to the index
|
||||
* that the item existed at *before* any of the updates are applied, and the
|
||||
* `toIndex` refers to the index after *all* of the updates are applied
|
||||
* (including deletes/moves). TODO: refactor so this can be shared with
|
||||
* DOMChildrenOperations.
|
||||
*
|
||||
* @param {ReactNativeBaseComponent} updates List of update configurations.
|
||||
* @param {array<string>} markup List of markup strings - in the case of React
|
||||
* IOS, the ids of new components assumed to be already created.
|
||||
*/
|
||||
var dangerouslyProcessChildrenUpdates = function(inst, childrenUpdates) {
|
||||
if (!childrenUpdates.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var containerTag = ReactNativeComponentTree.getNodeFromInstance(inst);
|
||||
|
||||
var moveFromIndices;
|
||||
var moveToIndices;
|
||||
var addChildTags;
|
||||
var addAtIndices;
|
||||
var removeAtIndices;
|
||||
|
||||
for (var i = 0; i < childrenUpdates.length; i++) {
|
||||
var update = childrenUpdates[i];
|
||||
if (update.type === 'MOVE_EXISTING') {
|
||||
(moveFromIndices || (moveFromIndices = [])).push(update.fromIndex);
|
||||
(moveToIndices || (moveToIndices = [])).push(update.toIndex);
|
||||
} else if (update.type === 'REMOVE_NODE') {
|
||||
(removeAtIndices || (removeAtIndices = [])).push(update.fromIndex);
|
||||
} else if (update.type === 'INSERT_MARKUP') {
|
||||
var mountImage = update.content;
|
||||
var tag = mountImage;
|
||||
(addAtIndices || (addAtIndices = [])).push(update.toIndex);
|
||||
(addChildTags || (addChildTags = [])).push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
UIManager.manageChildren(
|
||||
containerTag,
|
||||
moveFromIndices,
|
||||
moveToIndices,
|
||||
addChildTags,
|
||||
addAtIndices,
|
||||
removeAtIndices,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Operations used to process updates to DOM nodes. This is made injectable via
|
||||
* `ReactComponent.DOMIDOperations`.
|
||||
*/
|
||||
var ReactNativeDOMIDOperations = {
|
||||
dangerouslyProcessChildrenUpdates,
|
||||
|
||||
/**
|
||||
* Replaces a view that exists in the document with markup.
|
||||
*
|
||||
* @param {string} id ID of child to be replaced.
|
||||
* @param {string} markup Mount image to replace child with id.
|
||||
*/
|
||||
dangerouslyReplaceNodeWithMarkupByID: function(id, mountImage) {
|
||||
var oldTag = id;
|
||||
UIManager.replaceExistingNonRootView(oldTag, mountImage);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeDOMIDOperations;
|
@ -1,201 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeEventEmitter
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var EventPluginRegistry = require('EventPluginRegistry');
|
||||
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var ReactGenericBatching = require('ReactGenericBatching');
|
||||
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
/**
|
||||
* Version of `ReactBrowserEventEmitter` that works on the receiving side of a
|
||||
* serialized worker boundary.
|
||||
*/
|
||||
|
||||
// Shared default empty native event - conserve memory.
|
||||
var EMPTY_NATIVE_EVENT = {};
|
||||
|
||||
/**
|
||||
* Selects a subsequence of `Touch`es, without destroying `touches`.
|
||||
*
|
||||
* @param {Array<Touch>} touches Deserialized touch objects.
|
||||
* @param {Array<number>} indices Indices by which to pull subsequence.
|
||||
* @return {Array<Touch>} Subsequence of touch objects.
|
||||
*/
|
||||
var touchSubsequence = function(touches, indices) {
|
||||
var ret = [];
|
||||
for (var i = 0; i < indices.length; i++) {
|
||||
ret.push(touches[indices[i]]);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Pool all of this.
|
||||
*
|
||||
* Destroys `touches` by removing touch objects at indices `indices`. This is
|
||||
* to maintain compatibility with W3C touch "end" events, where the active
|
||||
* touches don't include the set that has just been "ended".
|
||||
*
|
||||
* @param {Array<Touch>} touches Deserialized touch objects.
|
||||
* @param {Array<number>} indices Indices to remove from `touches`.
|
||||
* @return {Array<Touch>} Subsequence of removed touch objects.
|
||||
*/
|
||||
var removeTouchesAtIndices = function(
|
||||
touches: Array<Object>,
|
||||
indices: Array<number>,
|
||||
): Array<Object> {
|
||||
var rippedOut = [];
|
||||
// use an unsafe downcast to alias to nullable elements,
|
||||
// so we can delete and then compact.
|
||||
var temp: Array<?Object> = (touches: Array<any>);
|
||||
for (var i = 0; i < indices.length; i++) {
|
||||
var index = indices[i];
|
||||
rippedOut.push(touches[index]);
|
||||
temp[index] = null;
|
||||
}
|
||||
var fillAt = 0;
|
||||
for (var j = 0; j < temp.length; j++) {
|
||||
var cur = temp[j];
|
||||
if (cur !== null) {
|
||||
temp[fillAt++] = cur;
|
||||
}
|
||||
}
|
||||
temp.length = fillAt;
|
||||
return rippedOut;
|
||||
};
|
||||
|
||||
var ReactNativeEventEmitter = {
|
||||
...ReactEventEmitterMixin,
|
||||
|
||||
registrationNames: EventPluginRegistry.registrationNameModules,
|
||||
|
||||
getListener: EventPluginHub.getListener,
|
||||
|
||||
/**
|
||||
* Internal version of `receiveEvent` in terms of normalized (non-tag)
|
||||
* `rootNodeID`.
|
||||
*
|
||||
* @see receiveEvent.
|
||||
*
|
||||
* @param {rootNodeID} rootNodeID React root node ID that event occurred on.
|
||||
* @param {TopLevelType} topLevelType Top level type of event.
|
||||
* @param {object} nativeEventParam Object passed from native.
|
||||
*/
|
||||
_receiveRootNodeIDEvent: function(
|
||||
rootNodeID: number,
|
||||
topLevelType: string,
|
||||
nativeEventParam: Object,
|
||||
) {
|
||||
var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
|
||||
var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID);
|
||||
ReactGenericBatching.batchedUpdates(function() {
|
||||
ReactNativeEventEmitter.handleTopLevel(
|
||||
topLevelType,
|
||||
inst,
|
||||
nativeEvent,
|
||||
nativeEvent.target,
|
||||
);
|
||||
});
|
||||
// React Native doesn't use ReactControlledComponent but if it did, here's
|
||||
// where it would do it.
|
||||
},
|
||||
|
||||
/**
|
||||
* Publicly exposed method on module for native objc to invoke when a top
|
||||
* level event is extracted.
|
||||
* @param {rootNodeID} rootNodeID React root node ID that event occurred on.
|
||||
* @param {TopLevelType} topLevelType Top level type of event.
|
||||
* @param {object} nativeEventParam Object passed from native.
|
||||
*/
|
||||
receiveEvent: function(
|
||||
tag: number,
|
||||
topLevelType: string,
|
||||
nativeEventParam: Object,
|
||||
) {
|
||||
var rootNodeID = tag;
|
||||
ReactNativeEventEmitter._receiveRootNodeIDEvent(
|
||||
rootNodeID,
|
||||
topLevelType,
|
||||
nativeEventParam,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Simple multi-wrapper around `receiveEvent` that is intended to receive an
|
||||
* efficient representation of `Touch` objects, and other information that
|
||||
* can be used to construct W3C compliant `Event` and `Touch` lists.
|
||||
*
|
||||
* This may create dispatch behavior that differs than web touch handling. We
|
||||
* loop through each of the changed touches and receive it as a single event.
|
||||
* So two `touchStart`/`touchMove`s that occur simultaneously are received as
|
||||
* two separate touch event dispatches - when they arguably should be one.
|
||||
*
|
||||
* This implementation reuses the `Touch` objects themselves as the `Event`s
|
||||
* since we dispatch an event for each touch (though that might not be spec
|
||||
* compliant). The main purpose of reusing them is to save allocations.
|
||||
*
|
||||
* TODO: Dispatch multiple changed touches in one event. The bubble path
|
||||
* could be the first common ancestor of all the `changedTouches`.
|
||||
*
|
||||
* One difference between this behavior and W3C spec: cancelled touches will
|
||||
* not appear in `.touches`, or in any future `.touches`, though they may
|
||||
* still be "actively touching the surface".
|
||||
*
|
||||
* Web desktop polyfills only need to construct a fake touch event with
|
||||
* identifier 0, also abandoning traditional click handlers.
|
||||
*/
|
||||
receiveTouches: function(
|
||||
eventTopLevelType: string,
|
||||
touches: Array<Object>,
|
||||
changedIndices: Array<number>,
|
||||
) {
|
||||
var changedTouches = eventTopLevelType === 'topTouchEnd' ||
|
||||
eventTopLevelType === 'topTouchCancel'
|
||||
? removeTouchesAtIndices(touches, changedIndices)
|
||||
: touchSubsequence(touches, changedIndices);
|
||||
|
||||
for (var jj = 0; jj < changedTouches.length; jj++) {
|
||||
var touch = changedTouches[jj];
|
||||
// Touch objects can fulfill the role of `DOM` `Event` objects if we set
|
||||
// the `changedTouches`/`touches`. This saves allocations.
|
||||
touch.changedTouches = changedTouches;
|
||||
touch.touches = touches;
|
||||
var nativeEvent = touch;
|
||||
var rootNodeID = null;
|
||||
var target = nativeEvent.target;
|
||||
if (target !== null && target !== undefined) {
|
||||
if (target < ReactNativeTagHandles.tagsStartAt) {
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
false,
|
||||
'A view is reporting that a touch occurred on tag zero.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
rootNodeID = target;
|
||||
}
|
||||
}
|
||||
ReactNativeEventEmitter._receiveRootNodeIDEvent(
|
||||
rootNodeID,
|
||||
eventTopLevelType,
|
||||
nativeEvent,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeEventEmitter;
|
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeEventPluginOrder
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeEventPluginOrder = [
|
||||
'ResponderEventPlugin',
|
||||
'ReactNativeBridgeEventPlugin',
|
||||
];
|
||||
|
||||
module.exports = ReactNativeEventPluginOrder;
|
@ -1,474 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeFiber
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactFiberErrorLogger = require('ReactFiberErrorLogger');
|
||||
const ReactFiberReconciler = require('ReactFiberReconciler');
|
||||
const ReactGenericBatching = require('ReactGenericBatching');
|
||||
const ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||
const ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
const ReactNativeFiberErrorDialog = require('ReactNativeFiberErrorDialog');
|
||||
const ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent');
|
||||
const ReactNativeInjection = require('ReactNativeInjection');
|
||||
const ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
|
||||
const ReactPortal = require('ReactPortal');
|
||||
const ReactVersion = require('ReactVersion');
|
||||
const UIManager = require('UIManager');
|
||||
|
||||
const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
||||
const emptyObject = require('fbjs/lib/emptyObject');
|
||||
const findNodeHandle = require('findNodeHandle');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
const {injectInternals} = require('ReactFiberDevToolsHook');
|
||||
|
||||
import type {Element} from 'React';
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {
|
||||
ReactNativeBaseComponentViewConfig,
|
||||
} from 'ReactNativeViewConfigRegistry';
|
||||
import type {ReactNodeList} from 'ReactTypes';
|
||||
const {
|
||||
precacheFiberNode,
|
||||
uncacheFiberNode,
|
||||
updateFiberProps,
|
||||
} = ReactNativeComponentTree;
|
||||
|
||||
ReactNativeInjection.inject();
|
||||
|
||||
type Container = number;
|
||||
export type Instance = {
|
||||
_children: Array<Instance | number>,
|
||||
_nativeTag: number,
|
||||
viewConfig: ReactNativeBaseComponentViewConfig,
|
||||
};
|
||||
type Props = Object;
|
||||
type TextInstance = number;
|
||||
|
||||
function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
|
||||
if (typeof node === 'number') {
|
||||
// Leaf node (eg text)
|
||||
uncacheFiberNode(node);
|
||||
} else {
|
||||
uncacheFiberNode((node: any)._nativeTag);
|
||||
|
||||
(node: any)._children.forEach(recursivelyUncacheFiberNode);
|
||||
}
|
||||
}
|
||||
|
||||
const NativeRenderer = ReactFiberReconciler({
|
||||
appendChild(
|
||||
parentInstance: Instance | Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
const childTag = typeof child === 'number' ? child : child._nativeTag;
|
||||
|
||||
if (typeof parentInstance === 'number') {
|
||||
// Root container
|
||||
UIManager.setChildren(
|
||||
parentInstance, // containerTag
|
||||
[childTag], // reactTags
|
||||
);
|
||||
} else {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance._children.push(child);
|
||||
},
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
UIManager.updateView(
|
||||
instance._nativeTag, // reactTag
|
||||
viewConfig.uiViewClassName, // viewName
|
||||
updatePayload, // props
|
||||
);
|
||||
},
|
||||
|
||||
createInstance(
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: {},
|
||||
internalInstanceHandle: Object,
|
||||
): Instance {
|
||||
const tag = ReactNativeTagHandles.allocateTag();
|
||||
const viewConfig = ReactNativeViewConfigRegistry.get(type);
|
||||
|
||||
if (__DEV__) {
|
||||
for (const key in viewConfig.validAttributes) {
|
||||
if (props.hasOwnProperty(key)) {
|
||||
deepFreezeAndThrowOnMutationInDev(props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: {},
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
const tag = ReactNativeTagHandles.allocateTag();
|
||||
|
||||
UIManager.createView(
|
||||
tag, // reactTag
|
||||
'RCTRawText', // viewName
|
||||
rootContainerInstance, // rootTag
|
||||
{text: text}, // props
|
||||
);
|
||||
|
||||
precacheFiberNode(internalInstanceHandle, tag);
|
||||
|
||||
return tag;
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
},
|
||||
|
||||
getRootHostContext(): {} {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getChildHostContext(): {} {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance;
|
||||
},
|
||||
|
||||
insertBefore(
|
||||
parentInstance: Instance | 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',
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
prepareForCommit(): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
prepareUpdate(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: {},
|
||||
): null | Object {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
removeChild(
|
||||
parentInstance: Instance | Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
recursivelyUncacheFiberNode(child);
|
||||
|
||||
if (typeof parentInstance === 'number') {
|
||||
UIManager.manageChildren(
|
||||
parentInstance, // containerID
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[0], // removeAtIndices
|
||||
);
|
||||
} else {
|
||||
const children = parentInstance._children;
|
||||
const index = children.indexOf(child);
|
||||
|
||||
children.splice(index, 1);
|
||||
|
||||
UIManager.manageChildren(
|
||||
parentInstance._nativeTag, // containerID
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[index], // removeAtIndices
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
resetAfterCommit(): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
resetTextContent(instance: Instance): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
scheduleAnimationCallback: global.requestAnimationFrame,
|
||||
|
||||
scheduleDeferredCallback: global.requestIdleCallback,
|
||||
|
||||
shouldSetTextContent(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;
|
||||
},
|
||||
|
||||
useSyncScheduling: true,
|
||||
});
|
||||
|
||||
ReactGenericBatching.injection.injectFiberBatchedUpdates(
|
||||
NativeRenderer.batchedUpdates,
|
||||
);
|
||||
|
||||
const roots = new Map();
|
||||
|
||||
findNodeHandle.injection.injectFindNode((fiber: Fiber) =>
|
||||
NativeRenderer.findHostInstance(fiber),
|
||||
);
|
||||
findNodeHandle.injection.injectFindRootNodeID(instance => instance);
|
||||
|
||||
// Intercept lifecycle errors and ensure they are shown with the correct stack
|
||||
// trace within the native redbox component.
|
||||
ReactFiberErrorLogger.injection.injectDialog(
|
||||
ReactNativeFiberErrorDialog.showDialog,
|
||||
);
|
||||
|
||||
const ReactNative = {
|
||||
// External users of findNodeHandle() expect the host tag number return type.
|
||||
// The injected findNodeHandle() strategy returns the instance wrapper though.
|
||||
// See NativeMethodsMixin#setNativeProps for more info on why this is done.
|
||||
findNodeHandle(componentOrHandle: any): ?number {
|
||||
const instance: any = findNodeHandle(componentOrHandle);
|
||||
if (instance == null || typeof instance === 'number') {
|
||||
return instance;
|
||||
}
|
||||
return instance._nativeTag;
|
||||
},
|
||||
|
||||
render(element: Element<any>, containerTag: any, callback: ?Function) {
|
||||
let root = roots.get(containerTag);
|
||||
|
||||
if (!root) {
|
||||
// TODO (bvaughn): If we decide to keep the wrapper component,
|
||||
// We could create a wrapper for containerTag as well to reduce special casing.
|
||||
root = NativeRenderer.createContainer(containerTag);
|
||||
roots.set(containerTag, root);
|
||||
}
|
||||
NativeRenderer.updateContainer(element, root, null, callback);
|
||||
|
||||
return NativeRenderer.getPublicRootInstance(root);
|
||||
},
|
||||
|
||||
unmountComponentAtNode(containerTag: number) {
|
||||
const root = roots.get(containerTag);
|
||||
if (root) {
|
||||
// TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
|
||||
NativeRenderer.updateContainer(null, root, null, () => {
|
||||
roots.delete(containerTag);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
unmountComponentAtNodeAndRemoveContainer(containerTag: number) {
|
||||
ReactNative.unmountComponentAtNode(containerTag);
|
||||
|
||||
// Call back into native to remove all of the subviews from this container
|
||||
UIManager.removeRootView(containerTag);
|
||||
},
|
||||
|
||||
unstable_createPortal(
|
||||
children: ReactNodeList,
|
||||
containerTag: number,
|
||||
key: ?string = null,
|
||||
) {
|
||||
return ReactPortal.createPortal(children, containerTag, null, key);
|
||||
},
|
||||
|
||||
unstable_batchedUpdates: ReactGenericBatching.batchedUpdates,
|
||||
};
|
||||
|
||||
if (typeof injectInternals === 'function') {
|
||||
injectInternals({
|
||||
findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode,
|
||||
findHostInstanceByFiber: NativeRenderer.findHostInstance,
|
||||
// This is an enum because we may add more (e.g. profiler build)
|
||||
bundleType: __DEV__ ? 1 : 0,
|
||||
version: ReactVersion,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = ReactNative;
|
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeFiberErrorDialog
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ExceptionsManager = require('ExceptionsManager');
|
||||
|
||||
import type {CapturedError} from 'ReactFiberScheduler';
|
||||
|
||||
/**
|
||||
* Intercept lifecycle errors and ensure they are shown with the correct stack
|
||||
* trace within the native redbox component.
|
||||
*/
|
||||
function ReactNativeFiberErrorDialog(capturedError: CapturedError): boolean {
|
||||
const {componentStack, error} = capturedError;
|
||||
|
||||
let errorMessage: string;
|
||||
let errorStack: string;
|
||||
let errorType: Class<Error>;
|
||||
|
||||
// Typically Errors are thrown but eg strings or null can be thrown as well.
|
||||
if (error && typeof error === 'object') {
|
||||
const {message, name} = error;
|
||||
|
||||
const summary = message ? `${name}: ${message}` : name;
|
||||
|
||||
errorMessage = `${summary}\n\nThis error is located at:${componentStack}`;
|
||||
errorStack = error.stack;
|
||||
errorType = error.constructor;
|
||||
} else {
|
||||
errorMessage = `Unspecified error at:${componentStack}`;
|
||||
errorStack = '';
|
||||
errorType = Error;
|
||||
}
|
||||
|
||||
const newError = new errorType(errorMessage);
|
||||
newError.stack = errorStack;
|
||||
|
||||
ExceptionsManager.handleException(newError, false);
|
||||
|
||||
// Return false here to prevent ReactFiberErrorLogger default behavior of
|
||||
// logging error details to console.error. Calls to console.error are
|
||||
// automatically routed to the native redbox controller, which we've already
|
||||
// done above by calling ExceptionsManager.
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports.showDialog = ReactNativeFiberErrorDialog;
|
@ -1,101 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeFiberHostComponent
|
||||
* @flow
|
||||
* @preventMunge
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||
var TextInputState = require('TextInputState');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var {mountSafeCallback, warnForStyleProps} = require('NativeMethodsMixinUtils');
|
||||
|
||||
import type {
|
||||
MeasureInWindowOnSuccessCallback,
|
||||
MeasureLayoutOnSuccessCallback,
|
||||
MeasureOnSuccessCallback,
|
||||
NativeMethodsInterface,
|
||||
} from 'NativeMethodsMixinUtils';
|
||||
import type {Instance} from 'ReactNativeFiber';
|
||||
import type {
|
||||
ReactNativeBaseComponentViewConfig,
|
||||
} from 'ReactNativeViewConfigRegistry';
|
||||
|
||||
/**
|
||||
* This component defines the same methods as NativeMethodsMixin but without the
|
||||
* findNodeHandle wrapper. This wrapper is unnecessary for HostComponent views
|
||||
* and would also result in a circular require.js dependency (since
|
||||
* ReactNativeFiber depends on this component and NativeMethodsMixin depends on
|
||||
* ReactNativeFiber).
|
||||
*/
|
||||
class ReactNativeFiberHostComponent implements NativeMethodsInterface {
|
||||
_children: Array<Instance | number>;
|
||||
_nativeTag: number;
|
||||
viewConfig: ReactNativeBaseComponentViewConfig;
|
||||
|
||||
constructor(tag: number, viewConfig: ReactNativeBaseComponentViewConfig) {
|
||||
this._nativeTag = tag;
|
||||
this._children = [];
|
||||
this.viewConfig = viewConfig;
|
||||
}
|
||||
|
||||
blur() {
|
||||
TextInputState.blurTextInput(this._nativeTag);
|
||||
}
|
||||
|
||||
focus() {
|
||||
TextInputState.focusTextInput(this._nativeTag);
|
||||
}
|
||||
|
||||
measure(callback: MeasureOnSuccessCallback) {
|
||||
UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
|
||||
}
|
||||
|
||||
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
|
||||
UIManager.measureInWindow(
|
||||
this._nativeTag,
|
||||
mountSafeCallback(this, callback),
|
||||
);
|
||||
}
|
||||
|
||||
measureLayout(
|
||||
relativeToNativeNode: number,
|
||||
onSuccess: MeasureLayoutOnSuccessCallback,
|
||||
onFail: () => void /* currently unused */,
|
||||
) {
|
||||
UIManager.measureLayout(
|
||||
this._nativeTag,
|
||||
relativeToNativeNode,
|
||||
mountSafeCallback(this, onFail),
|
||||
mountSafeCallback(this, onSuccess),
|
||||
);
|
||||
}
|
||||
|
||||
setNativeProps(nativeProps: Object) {
|
||||
if (__DEV__) {
|
||||
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
|
||||
}
|
||||
|
||||
var updatePayload = ReactNativeAttributePayload.create(
|
||||
nativeProps,
|
||||
this.viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
UIManager.updateView(
|
||||
this._nativeTag,
|
||||
this.viewConfig.uiViewClassName,
|
||||
updatePayload,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReactNativeFiberHostComponent;
|
@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeGlobalResponderHandler
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var ReactNativeGlobalResponderHandler = {
|
||||
onChange: function(from, to, blockNativeResponder) {
|
||||
if (to !== null) {
|
||||
// TODO (bvaughn) Clean up once Stack is deprecated
|
||||
var tag = typeof to.tag !== 'number'
|
||||
? to._rootNodeID
|
||||
: to.stateNode._nativeTag;
|
||||
UIManager.setJSResponder(tag, blockNativeResponder);
|
||||
} else {
|
||||
UIManager.clearJSResponder();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeGlobalResponderHandler;
|
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeInjection
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Make sure essential globals are available and are patched correctly. Please don't remove this
|
||||
* line. Bundles created by react-packager `require` it before executing any application code. This
|
||||
* ensures it exists in the dependency graph and can be `require`d.
|
||||
* TODO: require this in packager, not in React #10932517
|
||||
*/
|
||||
require('InitializeCore');
|
||||
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
var RCTEventEmitter = require('RCTEventEmitter');
|
||||
var ReactNativeBridgeEventPlugin = require('ReactNativeBridgeEventPlugin');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
|
||||
var ReactNativeEventPluginOrder = require('ReactNativeEventPluginOrder');
|
||||
var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler');
|
||||
var ResponderEventPlugin = require('ResponderEventPlugin');
|
||||
|
||||
function inject() {
|
||||
/**
|
||||
* Register the event emitter with the native bridge
|
||||
*/
|
||||
RCTEventEmitter.register(ReactNativeEventEmitter);
|
||||
|
||||
/**
|
||||
* Inject module for resolving DOM hierarchy and plugin ordering.
|
||||
*/
|
||||
EventPluginHub.injection.injectEventPluginOrder(ReactNativeEventPluginOrder);
|
||||
EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
|
||||
|
||||
ResponderEventPlugin.injection.injectGlobalResponderHandler(
|
||||
ReactNativeGlobalResponderHandler,
|
||||
);
|
||||
|
||||
/**
|
||||
* Some important event plugins included by default (without having to require
|
||||
* them).
|
||||
*/
|
||||
EventPluginHub.injection.injectEventPluginsByName({
|
||||
ResponderEventPlugin: ResponderEventPlugin,
|
||||
ReactNativeBridgeEventPlugin: ReactNativeBridgeEventPlugin,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
inject: inject,
|
||||
};
|
@ -1,227 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeMount
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactNativeContainerInfo = require('ReactNativeContainerInfo');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var ReactReconciler = require('ReactReconciler');
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var emptyObject = require('fbjs/lib/emptyObject');
|
||||
var instantiateReactComponent = require('instantiateReactComponent');
|
||||
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
|
||||
|
||||
/**
|
||||
* Temporary (?) hack so that we can store all top-level pending updates on
|
||||
* composites instead of having to worry about different types of components
|
||||
* here.
|
||||
*/
|
||||
var TopLevelWrapper = function() {};
|
||||
TopLevelWrapper.prototype.isReactComponent = {};
|
||||
if (__DEV__) {
|
||||
TopLevelWrapper.displayName = 'TopLevelWrapper';
|
||||
}
|
||||
TopLevelWrapper.prototype.render = function() {
|
||||
return this.props.child;
|
||||
};
|
||||
TopLevelWrapper.isReactTopLevelWrapper = true;
|
||||
|
||||
/**
|
||||
* Mounts this component and inserts it into the DOM.
|
||||
*
|
||||
* @param {ReactComponent} componentInstance The instance to mount.
|
||||
* @param {number} rootID ID of the root node.
|
||||
* @param {number} containerTag container element to mount into.
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
*/
|
||||
function mountComponentIntoNode(componentInstance, containerTag, transaction) {
|
||||
var markup = ReactReconciler.mountComponent(
|
||||
componentInstance,
|
||||
transaction,
|
||||
null,
|
||||
ReactNativeContainerInfo(containerTag),
|
||||
emptyObject,
|
||||
0 /* parentDebugID */,
|
||||
);
|
||||
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
|
||||
ReactNativeMount._mountImageIntoNode(markup, containerTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batched mount.
|
||||
*
|
||||
* @param {ReactComponent} componentInstance The instance to mount.
|
||||
* @param {number} rootID ID of the root node.
|
||||
* @param {number} containerTag container element to mount into.
|
||||
*/
|
||||
function batchedMountComponentIntoNode(componentInstance, containerTag) {
|
||||
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
|
||||
transaction.perform(
|
||||
mountComponentIntoNode,
|
||||
null,
|
||||
componentInstance,
|
||||
containerTag,
|
||||
transaction,
|
||||
);
|
||||
ReactUpdates.ReactReconcileTransaction.release(transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* As soon as `ReactMount` is refactored to not rely on the DOM, we can share
|
||||
* code between the two. For now, we'll hard code the ID logic.
|
||||
*/
|
||||
var ReactNativeMount = {
|
||||
_instancesByContainerID: {},
|
||||
|
||||
// these two functions are needed by React Devtools
|
||||
findNodeHandle: require('findNodeHandle'),
|
||||
|
||||
/**
|
||||
* @param {ReactComponent} instance Instance to render.
|
||||
* @param {containerTag} containerView Handle to native view tag
|
||||
*/
|
||||
renderComponent: function(
|
||||
nextElement: ReactElement<*>,
|
||||
containerTag: number,
|
||||
callback?: ?() => void,
|
||||
): ?ReactComponent<any, any, any> {
|
||||
var nextWrappedElement = React.createElement(TopLevelWrapper, {
|
||||
child: nextElement,
|
||||
});
|
||||
|
||||
var topRootNodeID = containerTag;
|
||||
var prevComponent = ReactNativeMount._instancesByContainerID[topRootNodeID];
|
||||
if (prevComponent) {
|
||||
var prevWrappedElement = prevComponent._currentElement;
|
||||
var prevElement = prevWrappedElement.props.child;
|
||||
if (shouldUpdateReactComponent(prevElement, nextElement)) {
|
||||
ReactUpdateQueue.enqueueElementInternal(
|
||||
prevComponent,
|
||||
nextWrappedElement,
|
||||
emptyObject,
|
||||
);
|
||||
if (callback) {
|
||||
ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
|
||||
}
|
||||
return prevComponent;
|
||||
} else {
|
||||
ReactNativeMount.unmountComponentAtNode(containerTag);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) {
|
||||
console.error('You cannot render into anything but a top root');
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactNativeTagHandles.assertRootTag(containerTag);
|
||||
|
||||
var instance = instantiateReactComponent(nextWrappedElement, false);
|
||||
ReactNativeMount._instancesByContainerID[containerTag] = instance;
|
||||
|
||||
if (callback) {
|
||||
var nonNullCallback = callback;
|
||||
instance._pendingCallbacks = [
|
||||
function() {
|
||||
nonNullCallback.call(instance._renderedComponent.getPublicInstance());
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// The initial render is synchronous but any updates that happen during
|
||||
// rendering, in componentWillMount or componentDidMount, will be batched
|
||||
// according to the current batching strategy.
|
||||
|
||||
ReactUpdates.batchedUpdates(
|
||||
batchedMountComponentIntoNode,
|
||||
instance,
|
||||
containerTag,
|
||||
);
|
||||
var component = instance._renderedComponent.getPublicInstance();
|
||||
return component;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {View} view View tree image.
|
||||
* @param {number} containerViewID View to insert sub-view into.
|
||||
*/
|
||||
_mountImageIntoNode: function(mountImage: number, containerID: number) {
|
||||
// Since we now know that the `mountImage` has been mounted, we can
|
||||
// mark it as such.
|
||||
var childTag = mountImage;
|
||||
UIManager.setChildren(containerID, [childTag]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Standard unmounting of the component that is rendered into `containerID`,
|
||||
* but will also execute a command to remove the actual container view
|
||||
* itself. This is useful when a client is cleaning up a React tree, and also
|
||||
* knows that the container will no longer be needed. When executing
|
||||
* asynchronously, it's easier to just have this method be the one that calls
|
||||
* for removal of the view.
|
||||
*/
|
||||
unmountComponentAtNodeAndRemoveContainer: function(containerTag: number) {
|
||||
ReactNativeMount.unmountComponentAtNode(containerTag);
|
||||
// call back into native to remove all of the subviews from this container
|
||||
UIManager.removeRootView(containerTag);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmount component at container ID by iterating through each child component
|
||||
* that has been rendered and unmounting it. There should just be one child
|
||||
* component at this time.
|
||||
*/
|
||||
unmountComponentAtNode: function(containerTag: number): boolean {
|
||||
if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) {
|
||||
console.error('You cannot render into anything but a top root');
|
||||
return false;
|
||||
}
|
||||
|
||||
var instance = ReactNativeMount._instancesByContainerID[containerTag];
|
||||
if (!instance) {
|
||||
return false;
|
||||
}
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onBeginFlush();
|
||||
}
|
||||
ReactNativeMount.unmountComponentFromNode(instance, containerTag);
|
||||
delete ReactNativeMount._instancesByContainerID[containerTag];
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onEndFlush();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmounts a component and sends messages back to iOS to remove its subviews.
|
||||
*
|
||||
* @param {ReactComponent} instance React component instance.
|
||||
* @param {string} containerID ID of container we're removing from.
|
||||
* @final
|
||||
* @internal
|
||||
* @see {ReactNativeMount.unmountComponentAtNode}
|
||||
*/
|
||||
unmountComponentFromNode: function(
|
||||
instance: ReactComponent<any, any, any>,
|
||||
containerID: number,
|
||||
) {
|
||||
// Call back into native to remove all of the subviews from this container
|
||||
ReactReconciler.unmountComponent(instance);
|
||||
UIManager.removeSubviewsFromContainerWithID(containerID);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeMount;
|
@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativePropRegistry
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var objects = {};
|
||||
var uniqueID = 1;
|
||||
var emptyObject = {};
|
||||
|
||||
class ReactNativePropRegistry {
|
||||
static register(object: Object): number {
|
||||
var id = ++uniqueID;
|
||||
if (__DEV__) {
|
||||
Object.freeze(object);
|
||||
}
|
||||
objects[id] = object;
|
||||
return id;
|
||||
}
|
||||
|
||||
static getByID(id: number): Object {
|
||||
if (!id) {
|
||||
// Used in the style={[condition && id]} pattern,
|
||||
// we want it to be a no-op when the value is false or null
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
var object = objects[id];
|
||||
if (!object) {
|
||||
console.warn('Invalid style with id `' + id + '`. Skipping ...');
|
||||
return emptyObject;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReactNativePropRegistry;
|
@ -1,132 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeReconcileTransaction
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var CallbackQueue = require('CallbackQueue');
|
||||
var PooledClass = require('PooledClass');
|
||||
var Transaction = require('Transaction');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
|
||||
/**
|
||||
* Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during
|
||||
* the performing of the transaction.
|
||||
*/
|
||||
var ON_DOM_READY_QUEUEING = {
|
||||
/**
|
||||
* Initializes the internal `onDOMReady` queue.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.reactMountReady.reset();
|
||||
},
|
||||
|
||||
/**
|
||||
* After DOM is flushed, invoke all registered `onDOMReady` callbacks.
|
||||
*/
|
||||
close: function() {
|
||||
this.reactMountReady.notifyAll();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Executed within the scope of the `Transaction` instance. Consider these as
|
||||
* being member methods, but with an implied ordering while being isolated from
|
||||
* each other.
|
||||
*/
|
||||
var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING];
|
||||
|
||||
if (__DEV__) {
|
||||
TRANSACTION_WRAPPERS.push({
|
||||
initialize: ReactInstrumentation.debugTool.onBeginFlush,
|
||||
close: ReactInstrumentation.debugTool.onEndFlush,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently:
|
||||
* - The order that these are listed in the transaction is critical:
|
||||
* - Suppresses events.
|
||||
* - Restores selection range.
|
||||
*
|
||||
* Future:
|
||||
* - Restore document/overflow scroll positions that were unintentionally
|
||||
* modified via DOM insertions above the top viewport boundary.
|
||||
* - Implement/integrate with customized constraint based layout system and keep
|
||||
* track of which dimensions must be remeasured.
|
||||
*
|
||||
* @class ReactNativeReconcileTransaction
|
||||
*/
|
||||
function ReactNativeReconcileTransaction() {
|
||||
this.reinitializeTransaction();
|
||||
this.reactMountReady = CallbackQueue.getPooled(null);
|
||||
}
|
||||
|
||||
var Mixin = {
|
||||
/**
|
||||
* @see Transaction
|
||||
* @abstract
|
||||
* @final
|
||||
* @return {array<object>} List of operation wrap procedures.
|
||||
* TODO: convert to array<TransactionWrapper>
|
||||
*/
|
||||
getTransactionWrappers: function() {
|
||||
return TRANSACTION_WRAPPERS;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {object} The queue to collect `onDOMReady` callbacks with.
|
||||
* TODO: convert to ReactMountReady
|
||||
*/
|
||||
getReactMountReady: function() {
|
||||
return this.reactMountReady;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {object} The queue to collect React async events.
|
||||
*/
|
||||
getUpdateQueue: function() {
|
||||
return ReactUpdateQueue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save current transaction state -- if the return value from this method is
|
||||
* passed to `rollback`, the transaction will be reset to that state.
|
||||
*/
|
||||
checkpoint: function() {
|
||||
// reactMountReady is the our only stateful wrapper
|
||||
return this.reactMountReady.checkpoint();
|
||||
},
|
||||
|
||||
rollback: function(checkpoint) {
|
||||
this.reactMountReady.rollback(checkpoint);
|
||||
},
|
||||
|
||||
/**
|
||||
* `PooledClass` looks for this, and will invoke this before allowing this
|
||||
* instance to be reused.
|
||||
*/
|
||||
destructor: function() {
|
||||
CallbackQueue.release(this.reactMountReady);
|
||||
this.reactMountReady = null;
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(
|
||||
ReactNativeReconcileTransaction.prototype,
|
||||
Transaction,
|
||||
ReactNativeReconcileTransaction,
|
||||
Mixin,
|
||||
);
|
||||
|
||||
PooledClass.addPoolingTo(ReactNativeReconcileTransaction);
|
||||
|
||||
module.exports = ReactNativeReconcileTransaction;
|
@ -1,87 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeStack
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeInjection = require('ReactNativeInjection');
|
||||
var ReactNativeMount = require('ReactNativeMount');
|
||||
var ReactNativeStackInjection = require('ReactNativeStackInjection');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var findNodeHandle = require('findNodeHandle');
|
||||
|
||||
ReactNativeInjection.inject();
|
||||
ReactNativeStackInjection.inject();
|
||||
|
||||
var render = function(
|
||||
element: ReactElement<any>,
|
||||
mountInto: number,
|
||||
callback?: ?() => void,
|
||||
): ?ReactComponent<any, any, any> {
|
||||
return ReactNativeMount.renderComponent(element, mountInto, callback);
|
||||
};
|
||||
|
||||
var ReactNative = {
|
||||
hasReactNativeInitialized: false,
|
||||
|
||||
// External users of findNodeHandle() expect the host tag number return type.
|
||||
// The injected findNodeHandle() strategy returns the instance wrapper though.
|
||||
// See NativeMethodsMixin#setNativeProps for more info on why this is done.
|
||||
findNodeHandle(componentOrHandle: any): ?number {
|
||||
const nodeHandle = findNodeHandle(componentOrHandle);
|
||||
if (nodeHandle == null || typeof nodeHandle === 'number') {
|
||||
return nodeHandle;
|
||||
}
|
||||
return nodeHandle.getHostNode();
|
||||
},
|
||||
|
||||
render: render,
|
||||
|
||||
unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode,
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer,
|
||||
};
|
||||
|
||||
// Inject the runtime into a devtools global hook regardless of browser.
|
||||
// Allows for debugging when the hook is injected on the page.
|
||||
/* globals __REACT_DEVTOOLS_GLOBAL_HOOK__ */
|
||||
if (
|
||||
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
|
||||
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function'
|
||||
) {
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
|
||||
ComponentTree: {
|
||||
getClosestInstanceFromNode: function(node) {
|
||||
return ReactNativeComponentTree.getClosestInstanceFromNode(node);
|
||||
},
|
||||
getNodeFromInstance: function(inst) {
|
||||
// inst is an internal instance (but could be a composite)
|
||||
while (inst._renderedComponent) {
|
||||
inst = inst._renderedComponent;
|
||||
}
|
||||
if (inst) {
|
||||
return ReactNativeComponentTree.getNodeFromInstance(inst);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
Mount: ReactNativeMount,
|
||||
Reconciler: require('ReactReconciler'),
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = ReactNative;
|
@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeStackInjection
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Make sure essential globals are available and are patched correctly. Please don't remove this
|
||||
* line. Bundles created by react-packager `require` it before executing any application code. This
|
||||
* ensures it exists in the dependency graph and can be `require`d.
|
||||
* TODO: require this in packager, not in React #10932517
|
||||
*/
|
||||
require('InitializeCore');
|
||||
|
||||
var React = require('react');
|
||||
var ReactComponentEnvironment = require('ReactComponentEnvironment');
|
||||
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
|
||||
var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
var ReactGenericBatching = require('ReactGenericBatching');
|
||||
var ReactHostComponent = require('ReactHostComponent');
|
||||
var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment');
|
||||
var ReactNativeTextComponent = require('ReactNativeTextComponent');
|
||||
var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var findNodeHandle = require('findNodeHandle');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
function inject() {
|
||||
ReactGenericBatching.injection.injectStackBatchedUpdates(
|
||||
ReactUpdates.batchedUpdates,
|
||||
);
|
||||
|
||||
ReactUpdates.injection.injectReconcileTransaction(
|
||||
ReactNativeComponentEnvironment.ReactReconcileTransaction,
|
||||
);
|
||||
|
||||
ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
|
||||
|
||||
ReactComponentEnvironment.injection.injectEnvironment(
|
||||
ReactNativeComponentEnvironment,
|
||||
);
|
||||
|
||||
var EmptyComponent = instantiate => {
|
||||
// Can't import View at the top because it depends on React to make its composite
|
||||
var View = require('View');
|
||||
return new ReactSimpleEmptyComponent(
|
||||
React.createElement(View, {
|
||||
collapsable: true,
|
||||
style: {position: 'absolute'},
|
||||
}),
|
||||
instantiate,
|
||||
);
|
||||
};
|
||||
|
||||
findNodeHandle.injection.injectFindNode(instance => instance);
|
||||
findNodeHandle.injection.injectFindRootNodeID(instance => instance);
|
||||
|
||||
ReactEmptyComponent.injection.injectEmptyComponentFactory(EmptyComponent);
|
||||
|
||||
ReactHostComponent.injection.injectTextComponentClass(
|
||||
ReactNativeTextComponent,
|
||||
);
|
||||
ReactHostComponent.injection.injectGenericComponentClass(function(tag) {
|
||||
// Show a nicer error message for non-function tags
|
||||
var info = '';
|
||||
if (typeof tag === 'string' && /^[a-z]/.test(tag)) {
|
||||
info += ' Each component name should start with an uppercase letter.';
|
||||
}
|
||||
invariant(false, 'Expected a component class, got %s.%s', tag, info);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
inject: inject,
|
||||
};
|
@ -1,58 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeTagHandles
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
/**
|
||||
* Keeps track of allocating and associating native "tags" which are numeric,
|
||||
* unique view IDs. All the native tags are negative numbers, to avoid
|
||||
* collisions, but in the JS we keep track of them as positive integers to store
|
||||
* them effectively in Arrays. So we must refer to them as "inverses" of the
|
||||
* native tags (that are * normally negative).
|
||||
*
|
||||
* It *must* be the case that every `rootNodeID` always maps to the exact same
|
||||
* `tag` forever. The easiest way to accomplish this is to never delete
|
||||
* anything from this table.
|
||||
* Why: Because `dangerouslyReplaceNodeWithMarkupByID` relies on being able to
|
||||
* unmount a component with a `rootNodeID`, then mount a new one in its place,
|
||||
*/
|
||||
var INITIAL_TAG_COUNT = 1;
|
||||
var ReactNativeTagHandles = {
|
||||
tagsStartAt: INITIAL_TAG_COUNT,
|
||||
tagCount: INITIAL_TAG_COUNT,
|
||||
|
||||
allocateTag: function(): number {
|
||||
// Skip over root IDs as those are reserved for native
|
||||
while (this.reactTagIsNativeTopRootID(ReactNativeTagHandles.tagCount)) {
|
||||
ReactNativeTagHandles.tagCount++;
|
||||
}
|
||||
var tag = ReactNativeTagHandles.tagCount;
|
||||
ReactNativeTagHandles.tagCount++;
|
||||
return tag;
|
||||
},
|
||||
|
||||
assertRootTag: function(tag: number): void {
|
||||
invariant(
|
||||
this.reactTagIsNativeTopRootID(tag),
|
||||
'Expect a native root tag, instead got %s',
|
||||
tag,
|
||||
);
|
||||
},
|
||||
|
||||
reactTagIsNativeTopRootID: function(reactTag: number): boolean {
|
||||
// We reserve all tags that are 1 mod 10 for native root views
|
||||
return reactTag % 10 === 1;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeTagHandles;
|
@ -1,79 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeTextComponent
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
var ReactNativeTextComponent = function(text) {
|
||||
// This is really a ReactText (ReactNode), not a ReactElement
|
||||
this._currentElement = text;
|
||||
this._stringText = '' + text;
|
||||
this._hostParent = null;
|
||||
this._rootNodeID = 0;
|
||||
};
|
||||
|
||||
Object.assign(ReactNativeTextComponent.prototype, {
|
||||
mountComponent: function(
|
||||
transaction,
|
||||
hostParent,
|
||||
hostContainerInfo,
|
||||
context,
|
||||
) {
|
||||
// TODO: hostParent should have this context already. Stop abusing context.
|
||||
invariant(
|
||||
context.isInAParentText,
|
||||
'RawText "%s" must be wrapped in an explicit <Text> component.',
|
||||
this._stringText,
|
||||
);
|
||||
this._hostParent = hostParent;
|
||||
var tag = ReactNativeTagHandles.allocateTag();
|
||||
this._rootNodeID = tag;
|
||||
var nativeTopRootTag = hostContainerInfo._tag;
|
||||
UIManager.createView(tag, 'RCTRawText', nativeTopRootTag, {
|
||||
text: this._stringText,
|
||||
});
|
||||
|
||||
ReactNativeComponentTree.precacheNode(this, tag);
|
||||
|
||||
return tag;
|
||||
},
|
||||
|
||||
getHostNode: function() {
|
||||
return this._rootNodeID;
|
||||
},
|
||||
|
||||
receiveComponent: function(nextText, transaction, context) {
|
||||
if (nextText !== this._currentElement) {
|
||||
this._currentElement = nextText;
|
||||
var nextStringText = '' + nextText;
|
||||
if (nextStringText !== this._stringText) {
|
||||
this._stringText = nextStringText;
|
||||
UIManager.updateView(this._rootNodeID, 'RCTRawText', {
|
||||
text: this._stringText,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unmountComponent: function() {
|
||||
ReactNativeComponentTree.uncacheNode(this);
|
||||
this._currentElement = null;
|
||||
this._stringText = null;
|
||||
this._rootNodeID = 0;
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = ReactNativeTextComponent;
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeViewConfigRegistry
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
export type ReactNativeBaseComponentViewConfig = {
|
||||
validAttributes: Object,
|
||||
uiViewClassName: string,
|
||||
propTypes?: Object,
|
||||
};
|
||||
|
||||
const viewConfigs = new Map();
|
||||
|
||||
const prefix = 'topsecret-';
|
||||
|
||||
const ReactNativeViewConfigRegistry = {
|
||||
register(viewConfig: ReactNativeBaseComponentViewConfig) {
|
||||
const name = viewConfig.uiViewClassName;
|
||||
invariant(
|
||||
!viewConfigs.has(name),
|
||||
'Tried to register two views with the same name %s',
|
||||
name,
|
||||
);
|
||||
const secretName = prefix + name;
|
||||
viewConfigs.set(secretName, viewConfig);
|
||||
return secretName;
|
||||
},
|
||||
get(secretName: string) {
|
||||
const config = viewConfigs.get(secretName);
|
||||
invariant(config, 'View config not found for name %s', secretName);
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeViewConfigRegistry;
|
@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
|
||||
|
||||
module.exports = ReactNativeFeatureFlags.useFiber
|
||||
? require('ReactNativeFiber')
|
||||
: require('ReactNativeStack');
|
@ -1,236 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||
var ReactNativePropRegistry = require('ReactNativePropRegistry');
|
||||
|
||||
var diff = ReactNativeAttributePayload.diff;
|
||||
|
||||
describe('ReactNativeAttributePayload', () => {
|
||||
it('should work with simple example', () => {
|
||||
expect(diff({a: 1, c: 3}, {b: 2, c: 3}, {a: true, b: true})).toEqual({
|
||||
a: null,
|
||||
b: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip fields that are equal', () => {
|
||||
expect(
|
||||
diff(
|
||||
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
|
||||
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
|
||||
{a: true, b: true, c: true, d: true, e: true, f: true},
|
||||
),
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('should remove fields', () => {
|
||||
expect(diff({a: 1}, {}, {a: true})).toEqual({a: null});
|
||||
});
|
||||
|
||||
it('should remove fields that are set to undefined', () => {
|
||||
expect(diff({a: 1}, {a: undefined}, {a: true})).toEqual({a: null});
|
||||
});
|
||||
|
||||
it('should ignore invalid fields', () => {
|
||||
expect(diff({a: 1}, {b: 2}, {})).toEqual(null);
|
||||
});
|
||||
|
||||
it('should use the diff attribute', () => {
|
||||
var diffA = jest.fn((a, b) => true);
|
||||
var diffB = jest.fn((a, b) => false);
|
||||
expect(
|
||||
diff(
|
||||
{a: [1], b: [3]},
|
||||
{a: [2], b: [4]},
|
||||
{a: {diff: diffA}, b: {diff: diffB}},
|
||||
),
|
||||
).toEqual({a: [2]});
|
||||
expect(diffA).toBeCalledWith([1], [2]);
|
||||
expect(diffB).toBeCalledWith([3], [4]);
|
||||
});
|
||||
|
||||
it('should not use the diff attribute on addition/removal', () => {
|
||||
var diffA = jest.fn();
|
||||
var diffB = jest.fn();
|
||||
expect(
|
||||
diff({a: [1]}, {b: [2]}, {a: {diff: diffA}, b: {diff: diffB}}),
|
||||
).toEqual({a: null, b: [2]});
|
||||
expect(diffA).not.toBeCalled();
|
||||
expect(diffB).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should do deep diffs of Objects by default', () => {
|
||||
expect(
|
||||
diff(
|
||||
{a: [1], b: {k: [3, 4]}, c: {k: [4, 4]}},
|
||||
{a: [2], b: {k: [3, 4]}, c: {k: [4, 5]}},
|
||||
{a: true, b: true, c: true},
|
||||
),
|
||||
).toEqual({a: [2], c: {k: [4, 5]}});
|
||||
});
|
||||
|
||||
it('should work with undefined styles', () => {
|
||||
expect(
|
||||
diff(
|
||||
{style: {a: '#ffffff', b: 1}},
|
||||
{style: undefined},
|
||||
{style: {b: true}},
|
||||
),
|
||||
).toEqual({b: null});
|
||||
expect(
|
||||
diff(
|
||||
{style: undefined},
|
||||
{style: {a: '#ffffff', b: 1}},
|
||||
{style: {b: true}},
|
||||
),
|
||||
).toEqual({b: 1});
|
||||
expect(
|
||||
diff({style: undefined}, {style: undefined}, {style: {b: true}}),
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('should work with empty styles', () => {
|
||||
expect(diff({a: 1, c: 3}, {}, {a: true, b: true})).toEqual({a: null});
|
||||
expect(diff({}, {a: 1, c: 3}, {a: true, b: true})).toEqual({a: 1});
|
||||
expect(diff({}, {}, {a: true, b: true})).toEqual(null);
|
||||
});
|
||||
|
||||
it('should flatten nested styles and predefined styles', () => {
|
||||
var validStyleAttribute = {someStyle: {foo: true, bar: true}};
|
||||
|
||||
expect(
|
||||
diff({}, {someStyle: [{foo: 1}, {bar: 2}]}, validStyleAttribute),
|
||||
).toEqual({foo: 1, bar: 2});
|
||||
|
||||
expect(
|
||||
diff({someStyle: [{foo: 1}, {bar: 2}]}, {}, validStyleAttribute),
|
||||
).toEqual({foo: null, bar: null});
|
||||
|
||||
var barStyle = ReactNativePropRegistry.register({
|
||||
bar: 3,
|
||||
});
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{},
|
||||
{someStyle: [[{foo: 1}, {foo: 2}], barStyle]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: 2, bar: 3});
|
||||
});
|
||||
|
||||
it('should reset a value to a previous if it is removed', () => {
|
||||
var validStyleAttribute = {someStyle: {foo: true, bar: true}};
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{someStyle: [{foo: 1}, {foo: 3}]},
|
||||
{someStyle: [{foo: 1}, {bar: 2}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: 1, bar: 2});
|
||||
});
|
||||
|
||||
it('should not clear removed props if they are still in another slot', () => {
|
||||
var validStyleAttribute = {someStyle: {foo: true, bar: true}};
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{someStyle: [{}, {foo: 3, bar: 2}]},
|
||||
{someStyle: [{foo: 3}, {bar: 2}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: 3}); // this should ideally be null. heuristic tradeoff.
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{someStyle: [{}, {foo: 3, bar: 2}]},
|
||||
{someStyle: [{foo: 1, bar: 1}, {bar: 2}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({bar: 2, foo: 1});
|
||||
});
|
||||
|
||||
it('should clear a prop if a later style is explicit null/undefined', () => {
|
||||
var validStyleAttribute = {someStyle: {foo: true, bar: true}};
|
||||
expect(
|
||||
diff(
|
||||
{someStyle: [{}, {foo: 3, bar: 2}]},
|
||||
{someStyle: [{foo: 1}, {bar: 2, foo: null}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: null});
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{someStyle: [{foo: 3}, {foo: null, bar: 2}]},
|
||||
{someStyle: [{foo: null}, {bar: 2}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: null});
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{someStyle: [{foo: 1}, {foo: null}]},
|
||||
{someStyle: [{foo: 2}, {foo: null}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: null}); // this should ideally be null. heuristic.
|
||||
|
||||
// Test the same case with object equality because an early bailout doesn't
|
||||
// work in this case.
|
||||
var fooObj = {foo: 3};
|
||||
expect(
|
||||
diff(
|
||||
{someStyle: [{foo: 1}, fooObj]},
|
||||
{someStyle: [{foo: 2}, fooObj]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: 3}); // this should ideally be null. heuristic.
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{someStyle: [{foo: 1}, {foo: 3}]},
|
||||
{someStyle: [{foo: 2}, {foo: undefined}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: null}); // this should ideally be null. heuristic.
|
||||
});
|
||||
|
||||
// Function properties are just markers to native that events should be sent.
|
||||
it('should convert functions to booleans', () => {
|
||||
// Note that if the property changes from one function to another, we don't
|
||||
// need to send an update.
|
||||
expect(
|
||||
diff(
|
||||
{
|
||||
a: function() {
|
||||
return 1;
|
||||
},
|
||||
b: function() {
|
||||
return 2;
|
||||
},
|
||||
c: 3,
|
||||
},
|
||||
{
|
||||
b: function() {
|
||||
return 9;
|
||||
},
|
||||
c: function() {
|
||||
return 3;
|
||||
},
|
||||
},
|
||||
{a: true, b: true, c: true},
|
||||
),
|
||||
).toEqual({a: null, c: true});
|
||||
});
|
||||
});
|
@ -1,366 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var PropTypes;
|
||||
var RCTEventEmitter;
|
||||
var React;
|
||||
var ReactNative;
|
||||
var ResponderEventPlugin;
|
||||
var UIManager;
|
||||
var createReactNativeComponentClass;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
PropTypes = require('prop-types');
|
||||
RCTEventEmitter = require('RCTEventEmitter');
|
||||
React = require('react');
|
||||
ReactNative = require('ReactNative');
|
||||
ResponderEventPlugin = require('ResponderEventPlugin');
|
||||
UIManager = require('UIManager');
|
||||
createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
});
|
||||
|
||||
it('handles events', () => {
|
||||
expect(RCTEventEmitter.register.mock.calls.length).toBe(1);
|
||||
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
var log = [];
|
||||
ReactNative.render(
|
||||
<View
|
||||
foo="outer"
|
||||
onTouchEnd={() => log.push('outer touchend')}
|
||||
onTouchEndCapture={() => log.push('outer touchend capture')}
|
||||
onTouchStart={() => log.push('outer touchstart')}
|
||||
onTouchStartCapture={() => log.push('outer touchstart capture')}>
|
||||
<View
|
||||
foo="inner"
|
||||
onTouchEndCapture={() => log.push('inner touchend capture')}
|
||||
onTouchEnd={() => log.push('inner touchend')}
|
||||
onTouchStartCapture={() => log.push('inner touchstart capture')}
|
||||
onTouchStart={() => log.push('inner touchstart')}
|
||||
/>
|
||||
</View>,
|
||||
1,
|
||||
);
|
||||
|
||||
expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
|
||||
expect(UIManager.createView.mock.calls.length).toBe(2);
|
||||
|
||||
// Don't depend on the order of createView() calls.
|
||||
// Stack creates views outside-in; fiber creates them inside-out.
|
||||
var innerTag = UIManager.createView.mock.calls.find(
|
||||
args => args[3].foo === 'inner',
|
||||
)[0];
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchStart',
|
||||
[{target: innerTag, identifier: 17}],
|
||||
[0],
|
||||
);
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchEnd',
|
||||
[{target: innerTag, identifier: 17}],
|
||||
[0],
|
||||
);
|
||||
|
||||
expect(log).toEqual([
|
||||
'outer touchstart capture',
|
||||
'inner touchstart capture',
|
||||
'inner touchstart',
|
||||
'outer touchstart',
|
||||
'outer touchend capture',
|
||||
'inner touchend capture',
|
||||
'inner touchend',
|
||||
'outer touchend',
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles events on text nodes', () => {
|
||||
expect(RCTEventEmitter.register.mock.calls.length).toBe(1);
|
||||
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
|
||||
|
||||
var Text = createReactNativeComponentClass({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'Text',
|
||||
});
|
||||
|
||||
class ContextHack extends React.Component {
|
||||
static childContextTypes = {isInAParentText: PropTypes.bool};
|
||||
getChildContext() {
|
||||
return {isInAParentText: true};
|
||||
}
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
var log = [];
|
||||
ReactNative.render(
|
||||
<ContextHack>
|
||||
<Text>
|
||||
<Text
|
||||
onTouchEnd={() => log.push('string touchend')}
|
||||
onTouchEndCapture={() => log.push('string touchend capture')}
|
||||
onTouchStart={() => log.push('string touchstart')}
|
||||
onTouchStartCapture={() => log.push('string touchstart capture')}>
|
||||
Text Content
|
||||
</Text>
|
||||
<Text
|
||||
onTouchEnd={() => log.push('number touchend')}
|
||||
onTouchEndCapture={() => log.push('number touchend capture')}
|
||||
onTouchStart={() => log.push('number touchstart')}
|
||||
onTouchStartCapture={() => log.push('number touchstart capture')}>
|
||||
{123}
|
||||
</Text>
|
||||
</Text>
|
||||
</ContextHack>,
|
||||
1,
|
||||
);
|
||||
|
||||
expect(UIManager.createView.mock.calls.length).toBe(5);
|
||||
|
||||
// Don't depend on the order of createView() calls.
|
||||
// Stack creates views outside-in; fiber creates them inside-out.
|
||||
var innerTagString = UIManager.createView.mock.calls.find(
|
||||
args => args[3] && args[3].text === 'Text Content',
|
||||
)[0];
|
||||
var innerTagNumber = UIManager.createView.mock.calls.find(
|
||||
args => args[3] && args[3].text === '123',
|
||||
)[0];
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchStart',
|
||||
[{target: innerTagString, identifier: 17}],
|
||||
[0],
|
||||
);
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchEnd',
|
||||
[{target: innerTagString, identifier: 17}],
|
||||
[0],
|
||||
);
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchStart',
|
||||
[{target: innerTagNumber, identifier: 18}],
|
||||
[0],
|
||||
);
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchEnd',
|
||||
[{target: innerTagNumber, identifier: 18}],
|
||||
[0],
|
||||
);
|
||||
|
||||
expect(log).toEqual([
|
||||
'string touchstart capture',
|
||||
'string touchstart',
|
||||
'string touchend capture',
|
||||
'string touchend',
|
||||
'number touchstart capture',
|
||||
'number touchstart',
|
||||
'number touchend capture',
|
||||
'number touchend',
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles when a responder is unmounted while a touch sequence is in progress', () => {
|
||||
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: {id: true},
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
function getViewById(id) {
|
||||
return UIManager.createView.mock.calls.find(
|
||||
args => args[3] && args[3].id === id,
|
||||
)[0];
|
||||
}
|
||||
|
||||
function getResponderId() {
|
||||
const responder = ResponderEventPlugin._getResponder();
|
||||
if (responder === null) {
|
||||
return null;
|
||||
}
|
||||
const props = typeof responder.tag === 'number'
|
||||
? responder.memoizedProps
|
||||
: responder._currentElement.props;
|
||||
return props ? props.id : null;
|
||||
}
|
||||
|
||||
var log = [];
|
||||
ReactNative.render(
|
||||
<View id="parent">
|
||||
<View key={1}>
|
||||
<View
|
||||
id="one"
|
||||
onResponderEnd={() => log.push('one responder end')}
|
||||
onResponderStart={() => log.push('one responder start')}
|
||||
onStartShouldSetResponder={() => true}
|
||||
/>
|
||||
</View>
|
||||
<View key={2}>
|
||||
<View
|
||||
id="two"
|
||||
onResponderEnd={() => log.push('two responder end')}
|
||||
onResponderStart={() => log.push('two responder start')}
|
||||
onStartShouldSetResponder={() => true}
|
||||
/>
|
||||
</View>
|
||||
</View>,
|
||||
1,
|
||||
);
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchStart',
|
||||
[{target: getViewById('one'), identifier: 17}],
|
||||
[0],
|
||||
);
|
||||
|
||||
expect(getResponderId()).toBe('one');
|
||||
expect(log).toEqual(['one responder start']);
|
||||
log.splice(0);
|
||||
|
||||
ReactNative.render(
|
||||
<View id="parent">
|
||||
<View key={2}>
|
||||
<View
|
||||
id="two"
|
||||
onResponderEnd={() => log.push('two responder end')}
|
||||
onResponderStart={() => log.push('two responder start')}
|
||||
onStartShouldSetResponder={() => true}
|
||||
/>
|
||||
</View>
|
||||
</View>,
|
||||
1,
|
||||
);
|
||||
|
||||
// TODO Verify the onResponderEnd listener has been called (before the unmount)
|
||||
// expect(log).toEqual(['one responder end']);
|
||||
// log.splice(0);
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchEnd',
|
||||
[{target: getViewById('two'), identifier: 17}],
|
||||
[0],
|
||||
);
|
||||
|
||||
expect(getResponderId()).toBeNull();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchStart',
|
||||
[{target: getViewById('two'), identifier: 17}],
|
||||
[0],
|
||||
);
|
||||
|
||||
expect(getResponderId()).toBe('two');
|
||||
expect(log).toEqual(['two responder start']);
|
||||
});
|
||||
|
||||
it('handles events without target', () => {
|
||||
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: {id: true},
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
function getViewById(id) {
|
||||
return UIManager.createView.mock.calls.find(
|
||||
args => args[3] && args[3].id === id,
|
||||
)[0];
|
||||
}
|
||||
|
||||
function getResponderId() {
|
||||
const responder = ResponderEventPlugin._getResponder();
|
||||
if (responder === null) {
|
||||
return null;
|
||||
}
|
||||
const props = typeof responder.tag === 'number'
|
||||
? responder.memoizedProps
|
||||
: responder._currentElement.props;
|
||||
return props ? props.id : null;
|
||||
}
|
||||
|
||||
var log = [];
|
||||
|
||||
function render(renderFirstComponent) {
|
||||
ReactNative.render(
|
||||
<View id="parent">
|
||||
<View key={1}>
|
||||
{renderFirstComponent
|
||||
? <View
|
||||
id="one"
|
||||
onResponderEnd={() => log.push('one responder end')}
|
||||
onResponderStart={() => log.push('one responder start')}
|
||||
onStartShouldSetResponder={() => true}
|
||||
/>
|
||||
: null}
|
||||
</View>
|
||||
<View key={2}>
|
||||
<View
|
||||
id="two"
|
||||
onResponderEnd={() => log.push('two responder end')}
|
||||
onResponderStart={() => log.push('two responder start')}
|
||||
onStartShouldSetResponder={() => true}
|
||||
/>
|
||||
</View>
|
||||
</View>,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
render(true);
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchStart',
|
||||
[{target: getViewById('one'), identifier: 17}],
|
||||
[0],
|
||||
);
|
||||
|
||||
// Unmounting component 'one'.
|
||||
render(false);
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchEnd',
|
||||
[{target: getViewById('one'), identifier: 17}],
|
||||
[0],
|
||||
);
|
||||
|
||||
expect(getResponderId()).toBe(null);
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchStart',
|
||||
[{target: getViewById('two'), identifier: 18}],
|
||||
[0],
|
||||
);
|
||||
|
||||
expect(getResponderId()).toBe('two');
|
||||
|
||||
EventEmitter.receiveTouches(
|
||||
'topTouchEnd',
|
||||
[{target: getViewById('two'), identifier: 18}],
|
||||
[0],
|
||||
);
|
||||
|
||||
expect(getResponderId()).toBe(null);
|
||||
|
||||
expect(log).toEqual([
|
||||
'one responder start',
|
||||
'two responder start',
|
||||
'two responder end',
|
||||
]);
|
||||
});
|
@ -1,111 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNative;
|
||||
var createReactNativeComponentClass;
|
||||
var UIManager;
|
||||
|
||||
describe('ReactNative', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
React = require('react');
|
||||
ReactNative = require('ReactNative');
|
||||
UIManager = require('UIManager');
|
||||
createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
});
|
||||
|
||||
it('should be able to create and render a native component', () => {
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
ReactNative.render(<View foo="test" />, 1);
|
||||
expect(UIManager.createView).toBeCalled();
|
||||
expect(UIManager.setChildren).toBeCalled();
|
||||
expect(UIManager.manageChildren).not.toBeCalled();
|
||||
expect(UIManager.updateView).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should be able to create and update a native component', () => {
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
ReactNative.render(<View foo="foo" />, 11);
|
||||
|
||||
expect(UIManager.createView.mock.calls.length).toBe(1);
|
||||
expect(UIManager.setChildren.mock.calls.length).toBe(1);
|
||||
expect(UIManager.manageChildren).not.toBeCalled();
|
||||
expect(UIManager.updateView).not.toBeCalled();
|
||||
|
||||
ReactNative.render(<View foo="bar" />, 11);
|
||||
|
||||
expect(UIManager.createView.mock.calls.length).toBe(1);
|
||||
expect(UIManager.setChildren.mock.calls.length).toBe(1);
|
||||
expect(UIManager.manageChildren).not.toBeCalled();
|
||||
expect(UIManager.updateView).toBeCalledWith(2, 'View', {foo: 'bar'});
|
||||
});
|
||||
|
||||
it('returns the correct instance and calls it in the callback', () => {
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
var a;
|
||||
var b;
|
||||
var c = ReactNative.render(
|
||||
<View foo="foo" ref={v => (a = v)} />,
|
||||
11,
|
||||
function() {
|
||||
b = this;
|
||||
},
|
||||
);
|
||||
|
||||
expect(a).toBeTruthy();
|
||||
expect(a).toBe(b);
|
||||
expect(a).toBe(c);
|
||||
});
|
||||
|
||||
it('renders and reorders children', () => {
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: {title: true},
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
var chars = this.props.chars.split('');
|
||||
return (
|
||||
<View>
|
||||
{chars.map(text => <View key={text} title={text} />)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Mini multi-child stress test: lots of reorders, some adds, some removes.
|
||||
var before = 'abcdefghijklmnopqrst';
|
||||
var after = 'mxhpgwfralkeoivcstzy';
|
||||
|
||||
ReactNative.render(<Component chars={before} />, 11);
|
||||
expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
|
||||
|
||||
ReactNative.render(<Component chars={after} />, 11);
|
||||
expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`handles events 1`] = `
|
||||
"<native root> {}
|
||||
View {\\"foo\\":\\"outer\\"}
|
||||
View {\\"foo\\":\\"inner\\"}"
|
||||
`;
|
@ -1,51 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ReactNative renders and reorders children 1`] = `
|
||||
"<native root> {}
|
||||
View null
|
||||
View {\\"title\\":\\"a\\"}
|
||||
View {\\"title\\":\\"b\\"}
|
||||
View {\\"title\\":\\"c\\"}
|
||||
View {\\"title\\":\\"d\\"}
|
||||
View {\\"title\\":\\"e\\"}
|
||||
View {\\"title\\":\\"f\\"}
|
||||
View {\\"title\\":\\"g\\"}
|
||||
View {\\"title\\":\\"h\\"}
|
||||
View {\\"title\\":\\"i\\"}
|
||||
View {\\"title\\":\\"j\\"}
|
||||
View {\\"title\\":\\"k\\"}
|
||||
View {\\"title\\":\\"l\\"}
|
||||
View {\\"title\\":\\"m\\"}
|
||||
View {\\"title\\":\\"n\\"}
|
||||
View {\\"title\\":\\"o\\"}
|
||||
View {\\"title\\":\\"p\\"}
|
||||
View {\\"title\\":\\"q\\"}
|
||||
View {\\"title\\":\\"r\\"}
|
||||
View {\\"title\\":\\"s\\"}
|
||||
View {\\"title\\":\\"t\\"}"
|
||||
`;
|
||||
|
||||
exports[`ReactNative renders and reorders children 2`] = `
|
||||
"<native root> {}
|
||||
View null
|
||||
View {\\"title\\":\\"m\\"}
|
||||
View {\\"title\\":\\"x\\"}
|
||||
View {\\"title\\":\\"h\\"}
|
||||
View {\\"title\\":\\"p\\"}
|
||||
View {\\"title\\":\\"g\\"}
|
||||
View {\\"title\\":\\"w\\"}
|
||||
View {\\"title\\":\\"f\\"}
|
||||
View {\\"title\\":\\"r\\"}
|
||||
View {\\"title\\":\\"a\\"}
|
||||
View {\\"title\\":\\"l\\"}
|
||||
View {\\"title\\":\\"k\\"}
|
||||
View {\\"title\\":\\"e\\"}
|
||||
View {\\"title\\":\\"o\\"}
|
||||
View {\\"title\\":\\"i\\"}
|
||||
View {\\"title\\":\\"v\\"}
|
||||
View {\\"title\\":\\"c\\"}
|
||||
View {\\"title\\":\\"s\\"}
|
||||
View {\\"title\\":\\"t\\"}
|
||||
View {\\"title\\":\\"z\\"}
|
||||
View {\\"title\\":\\"y\\"}"
|
||||
`;
|
@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule createReactNativeComponentClass
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactNativeBaseComponent = require('ReactNativeBaseComponent');
|
||||
const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
|
||||
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
|
||||
|
||||
// See also ReactNativeBaseComponent
|
||||
type ReactNativeBaseComponentViewConfig = {
|
||||
validAttributes: Object,
|
||||
uiViewClassName: string,
|
||||
propTypes?: Object,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} config iOS View configuration.
|
||||
* @private
|
||||
*/
|
||||
const createReactNativeFiberComponentClass = function(
|
||||
viewConfig: ReactNativeBaseComponentViewConfig,
|
||||
): string {
|
||||
return ReactNativeViewConfigRegistry.register(viewConfig);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} config iOS View configuration.
|
||||
* @private
|
||||
*/
|
||||
const createReactNativeComponentClass = function(
|
||||
viewConfig: ReactNativeBaseComponentViewConfig,
|
||||
): ReactClass<any> {
|
||||
const Constructor = function(element) {
|
||||
this._currentElement = element;
|
||||
this._topLevelWrapper = null;
|
||||
this._hostParent = null;
|
||||
this._hostContainerInfo = null;
|
||||
this._rootNodeID = 0;
|
||||
this._renderedChildren = null;
|
||||
};
|
||||
Constructor.displayName = viewConfig.uiViewClassName;
|
||||
Constructor.viewConfig = viewConfig;
|
||||
Constructor.propTypes = viewConfig.propTypes;
|
||||
Constructor.prototype = new ReactNativeBaseComponent(viewConfig);
|
||||
Constructor.prototype.constructor = Constructor;
|
||||
|
||||
return ((Constructor: any): ReactClass<any>);
|
||||
};
|
||||
|
||||
module.exports = ReactNativeFeatureFlags.useFiber
|
||||
? createReactNativeFiberComponentClass
|
||||
: createReactNativeComponentClass;
|
@ -1,139 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule findNodeHandle
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
var {ReactCurrentOwner} = require('ReactGlobalSharedState');
|
||||
|
||||
var getComponentName = require('getComponentName');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {ReactInstance} from 'ReactInstanceType';
|
||||
|
||||
/**
|
||||
* ReactNative vs ReactWeb
|
||||
* -----------------------
|
||||
* React treats some pieces of data opaquely. This means that the information
|
||||
* is first class (it can be passed around), but cannot be inspected. This
|
||||
* allows us to build infrastructure that reasons about resources, without
|
||||
* making assumptions about the nature of those resources, and this allows that
|
||||
* infra to be shared across multiple platforms, where the resources are very
|
||||
* different. General infra (such as `ReactMultiChild`) reasons opaquely about
|
||||
* the data, but platform specific code (such as `ReactNativeBaseComponent`) can
|
||||
* make assumptions about the data.
|
||||
*
|
||||
*
|
||||
* `rootNodeID`, uniquely identifies a position in the generated native view
|
||||
* tree. Many layers of composite components (created with `React.createClass`)
|
||||
* can all share the same `rootNodeID`.
|
||||
*
|
||||
* `nodeHandle`: A sufficiently unambiguous way to refer to a lower level
|
||||
* resource (dom node, native view etc). The `rootNodeID` is sufficient for web
|
||||
* `nodeHandle`s, because the position in a tree is always enough to uniquely
|
||||
* identify a DOM node (we never have nodes in some bank outside of the
|
||||
* document). The same would be true for `ReactNative`, but we must maintain a
|
||||
* mapping that we can send efficiently serializable
|
||||
* strings across native boundaries.
|
||||
*
|
||||
* Opaque name TodaysWebReact FutureWebWorkerReact ReactNative
|
||||
* ----------------------------------------------------------------------------
|
||||
* nodeHandle N/A rootNodeID tag
|
||||
*/
|
||||
|
||||
let injectedFindNode;
|
||||
let injectedFindRootNodeID;
|
||||
|
||||
// TODO (bvaughn) Rename the findNodeHandle module to something more descriptive
|
||||
// eg findInternalHostInstance. This will reduce the likelihood of someone
|
||||
// accidentally deep-requiring this version.
|
||||
function findNodeHandle(componentOrHandle: any): any {
|
||||
if (__DEV__) {
|
||||
var owner =
|
||||
((ReactCurrentOwner.current: any): ReactInstance | Fiber | null);
|
||||
if (owner !== null) {
|
||||
const isFiber = typeof (owner: any).tag === 'number';
|
||||
const warnedAboutRefsInRender = isFiber
|
||||
? ((owner: any): Fiber).stateNode._warnedAboutRefsInRender
|
||||
: ((owner: any): ReactInstance)._warnedAboutRefsInRender;
|
||||
|
||||
warning(
|
||||
warnedAboutRefsInRender,
|
||||
'%s is accessing findNodeHandle inside its render(). ' +
|
||||
'render() should be a pure function of props and state. It should ' +
|
||||
'never access something that requires stale data from the previous ' +
|
||||
'render, such as refs. Move this logic to componentDidMount and ' +
|
||||
'componentDidUpdate instead.',
|
||||
getComponentName(owner) || 'A component',
|
||||
);
|
||||
|
||||
if (isFiber) {
|
||||
((owner: any): Fiber).stateNode._warnedAboutRefsInRender = true;
|
||||
} else {
|
||||
((owner: any): ReactInstance)._warnedAboutRefsInRender = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (componentOrHandle == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof componentOrHandle === 'number') {
|
||||
// Already a node handle
|
||||
return componentOrHandle;
|
||||
}
|
||||
|
||||
var component = componentOrHandle;
|
||||
|
||||
// TODO (balpert): Wrap iOS native components in a composite wrapper, then
|
||||
// ReactInstanceMap.get here will always succeed for mounted components
|
||||
var internalInstance = ReactInstanceMap.get(component);
|
||||
if (internalInstance) {
|
||||
return injectedFindNode(internalInstance);
|
||||
} else {
|
||||
var rootNodeID = injectedFindRootNodeID(component);
|
||||
if (rootNodeID) {
|
||||
return rootNodeID;
|
||||
} else {
|
||||
invariant(
|
||||
// Native
|
||||
(typeof component === 'object' &&
|
||||
('_rootNodeID' in component || // TODO (bvaughn) Clean up once Stack is deprecated
|
||||
'_nativeTag' in component)) ||
|
||||
// Composite
|
||||
(component.render != null && typeof component.render === 'function'),
|
||||
'findNodeHandle(...): Argument is not a component ' +
|
||||
'(type: %s, keys: %s)',
|
||||
typeof component,
|
||||
Object.keys(component),
|
||||
);
|
||||
invariant(
|
||||
false,
|
||||
'findNodeHandle(...): Unable to find node handle for unmounted ' +
|
||||
'component.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fiber and stack implementations differ; each must inject a strategy
|
||||
findNodeHandle.injection = {
|
||||
injectFindNode(findNode) {
|
||||
injectedFindNode = findNode;
|
||||
},
|
||||
injectFindRootNodeID(findRootNodeID) {
|
||||
injectedFindRootNodeID = findRootNodeID;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = findNodeHandle;
|
@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule takeSnapshot
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNative = require('ReactNative');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
import type {Element} from 'React';
|
||||
|
||||
/**
|
||||
* Capture an image of the screen, window or an individual view. The image
|
||||
* will be stored in a temporary file that will only exist for as long as the
|
||||
* app is running.
|
||||
*
|
||||
* The `view` argument can be the literal string `window` if you want to
|
||||
* capture the entire window, or it can be a reference to a specific
|
||||
* React Native component.
|
||||
*
|
||||
* The `options` argument may include:
|
||||
* - width/height (number) - the width and height of the image to capture.
|
||||
* - format (string) - either 'png' or 'jpeg'. Defaults to 'png'.
|
||||
* - quality (number) - the quality when using jpeg. 0.0 - 1.0 (default).
|
||||
*
|
||||
* Returns a Promise.
|
||||
* @platform ios
|
||||
*/
|
||||
function takeSnapshot(
|
||||
view?: 'window' | Element<any> | number,
|
||||
options?: {
|
||||
width?: number,
|
||||
height?: number,
|
||||
format?: 'png' | 'jpeg',
|
||||
quality?: number,
|
||||
},
|
||||
): Promise<any> {
|
||||
if (typeof view !== 'number' && view !== 'window') {
|
||||
view = ReactNative.findNodeHandle(view) || 'window';
|
||||
}
|
||||
|
||||
// Call the hidden '__takeSnapshot' method; the main one throws an error to
|
||||
// prevent accidental backwards-incompatible usage.
|
||||
return UIManager.__takeSnapshot(view, options);
|
||||
}
|
||||
|
||||
module.exports = takeSnapshot;
|
@ -1,391 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNoop
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a renderer of React that doesn't have a render target output.
|
||||
* It is useful to demonstrate the internals of the reconciler in isolation
|
||||
* and for testing semantics of reconciliation separate from the host
|
||||
* environment.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
|
||||
|
||||
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
|
||||
var ReactFiberReconciler = require('ReactFiberReconciler');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
var {
|
||||
AnimationPriority,
|
||||
} = require('ReactPriorityLevel');
|
||||
var emptyObject = require('fbjs/lib/emptyObject');
|
||||
|
||||
const UPDATE_SIGNAL = {};
|
||||
|
||||
var scheduledAnimationCallback = null;
|
||||
var scheduledDeferredCallback = null;
|
||||
|
||||
type Container = { rootID: string, children: Array<Instance | TextInstance> };
|
||||
type Props = { prop: any };
|
||||
type Instance = {| type: string, id: number, children: Array<Instance | TextInstance>, prop: any |};
|
||||
type TextInstance = {| text: string, id: number |};
|
||||
|
||||
var instanceCounter = 0;
|
||||
|
||||
var failInBeginPhase = false;
|
||||
|
||||
var NoopRenderer = ReactFiberReconciler({
|
||||
|
||||
getRootHostContext() {
|
||||
if (failInBeginPhase) {
|
||||
throw new Error('Error in host config.');
|
||||
}
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getChildHostContext() {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance;
|
||||
},
|
||||
|
||||
createInstance(type : string, props : Props) : Instance {
|
||||
const inst = {
|
||||
id: instanceCounter++,
|
||||
type: type,
|
||||
children: [],
|
||||
prop: props.prop,
|
||||
};
|
||||
// Hide from unit tests
|
||||
Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
|
||||
return inst;
|
||||
},
|
||||
|
||||
appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
|
||||
parentInstance.children.push(child);
|
||||
},
|
||||
|
||||
finalizeInitialChildren(domElement : Instance, type : string, props : Props) : boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
prepareUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : null | {} {
|
||||
return UPDATE_SIGNAL;
|
||||
},
|
||||
|
||||
commitMount(instance : Instance, type : string, newProps : Props) : void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitUpdate(instance : Instance, updatePayload : Object, type : string, oldProps : Props, newProps : Props) : void {
|
||||
instance.prop = newProps.prop;
|
||||
},
|
||||
|
||||
shouldSetTextContent(props : Props) : boolean {
|
||||
return (
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number'
|
||||
);
|
||||
},
|
||||
|
||||
resetTextContent(instance : Instance) : void {},
|
||||
|
||||
createTextInstance(
|
||||
text : string,
|
||||
rootContainerInstance : Container,
|
||||
hostContext : Object,
|
||||
internalInstanceHandle : Object
|
||||
) : TextInstance {
|
||||
var inst = { text : text, id: instanceCounter++ };
|
||||
// Hide from unit tests
|
||||
Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
|
||||
return inst;
|
||||
},
|
||||
|
||||
commitTextUpdate(textInstance : TextInstance, oldText : string, newText : string) : void {
|
||||
textInstance.text = newText;
|
||||
},
|
||||
|
||||
appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
|
||||
const index = parentInstance.children.indexOf(child);
|
||||
if (index !== -1) {
|
||||
parentInstance.children.splice(index, 1);
|
||||
}
|
||||
parentInstance.children.push(child);
|
||||
},
|
||||
|
||||
insertBefore(
|
||||
parentInstance : Instance | Container,
|
||||
child : Instance | TextInstance,
|
||||
beforeChild : Instance | TextInstance
|
||||
) : void {
|
||||
const index = parentInstance.children.indexOf(child);
|
||||
if (index !== -1) {
|
||||
parentInstance.children.splice(index, 1);
|
||||
}
|
||||
const beforeIndex = parentInstance.children.indexOf(beforeChild);
|
||||
if (beforeIndex === -1) {
|
||||
throw new Error('This child does not exist.');
|
||||
}
|
||||
parentInstance.children.splice(beforeIndex, 0, child);
|
||||
},
|
||||
|
||||
removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
|
||||
const index = parentInstance.children.indexOf(child);
|
||||
if (index === -1) {
|
||||
throw new Error('This child does not exist.');
|
||||
}
|
||||
parentInstance.children.splice(index, 1);
|
||||
},
|
||||
|
||||
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
scheduleAnimationCallback(callback) {
|
||||
if (scheduledAnimationCallback) {
|
||||
throw new Error(
|
||||
'Scheduling an animation callback twice is excessive. ' +
|
||||
'Instead, keep track of whether the callback has already been scheduled.'
|
||||
);
|
||||
}
|
||||
scheduledAnimationCallback = callback;
|
||||
},
|
||||
|
||||
scheduleDeferredCallback(callback) {
|
||||
if (scheduledDeferredCallback) {
|
||||
throw new Error(
|
||||
'Scheduling deferred callback twice is excessive. ' +
|
||||
'Instead, keep track of whether the callback has already been scheduled.'
|
||||
);
|
||||
}
|
||||
scheduledDeferredCallback = callback;
|
||||
},
|
||||
|
||||
prepareForCommit() : void {
|
||||
},
|
||||
|
||||
resetAfterCommit() : void {
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var rootContainers = new Map();
|
||||
var roots = new Map();
|
||||
var DEFAULT_ROOT_ID = '<default>';
|
||||
|
||||
var ReactNoop = {
|
||||
|
||||
getChildren(rootID : string = DEFAULT_ROOT_ID) {
|
||||
const container = rootContainers.get(rootID);
|
||||
if (container) {
|
||||
return container.children;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// Shortcut for testing a single root
|
||||
render(element : ReactElement<any>, callback: ?Function) {
|
||||
ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
|
||||
},
|
||||
|
||||
renderToRootWithID(element : ReactElement<any>, rootID : string, callback : ?Function) {
|
||||
let root = roots.get(rootID);
|
||||
if (!root) {
|
||||
const container = { rootID: rootID, children: [] };
|
||||
rootContainers.set(rootID, container);
|
||||
root = NoopRenderer.createContainer(container);
|
||||
roots.set(rootID, root);
|
||||
}
|
||||
NoopRenderer.updateContainer(element, root, null, callback);
|
||||
},
|
||||
|
||||
unmountRootWithID(rootID : string) {
|
||||
const root = roots.get(rootID);
|
||||
if (root) {
|
||||
NoopRenderer.updateContainer(null, root, null, () => {
|
||||
roots.delete(rootID);
|
||||
rootContainers.delete(rootID);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
findInstance(componentOrElement : Element | ?ReactComponent<any, any, any>) : null | Instance | TextInstance {
|
||||
if (componentOrElement == null) {
|
||||
return null;
|
||||
}
|
||||
// Unsound duck typing.
|
||||
const component = (componentOrElement : any);
|
||||
if (typeof component.id === 'number') {
|
||||
return component;
|
||||
}
|
||||
const inst = ReactInstanceMap.get(component);
|
||||
return inst ? NoopRenderer.findHostInstance(inst) : null;
|
||||
},
|
||||
|
||||
flushAnimationPri() {
|
||||
var cb = scheduledAnimationCallback;
|
||||
if (cb === null) {
|
||||
return;
|
||||
}
|
||||
scheduledAnimationCallback = null;
|
||||
cb();
|
||||
},
|
||||
|
||||
flushDeferredPri(timeout : number = Infinity) {
|
||||
var cb = scheduledDeferredCallback;
|
||||
if (cb === null) {
|
||||
return;
|
||||
}
|
||||
scheduledDeferredCallback = null;
|
||||
var timeRemaining = timeout;
|
||||
cb({
|
||||
timeRemaining() {
|
||||
// Simulate a fix amount of time progressing between each call.
|
||||
timeRemaining -= 5;
|
||||
if (timeRemaining < 0) {
|
||||
timeRemaining = 0;
|
||||
}
|
||||
return timeRemaining;
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
flush() {
|
||||
ReactNoop.flushAnimationPri();
|
||||
ReactNoop.flushDeferredPri();
|
||||
},
|
||||
|
||||
performAnimationWork(fn: Function) {
|
||||
NoopRenderer.performWithPriority(AnimationPriority, fn);
|
||||
},
|
||||
|
||||
batchedUpdates: NoopRenderer.batchedUpdates,
|
||||
|
||||
unbatchedUpdates: NoopRenderer.unbatchedUpdates,
|
||||
|
||||
syncUpdates: NoopRenderer.syncUpdates,
|
||||
|
||||
// Logs the current state of the tree.
|
||||
dumpTree(rootID : string = DEFAULT_ROOT_ID) {
|
||||
const root = roots.get(rootID);
|
||||
const rootContainer = rootContainers.get(rootID);
|
||||
if (!root || !rootContainer) {
|
||||
console.log('Nothing rendered yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
var bufferedLog = [];
|
||||
function log(...args) {
|
||||
bufferedLog.push(...args, '\n');
|
||||
}
|
||||
|
||||
function logHostInstances(children: Array<Instance | TextInstance>, depth) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
var indent = ' '.repeat(depth);
|
||||
if (typeof child.text === 'string') {
|
||||
log(indent + '- ' + child.text);
|
||||
} else {
|
||||
// $FlowFixMe - The child should've been refined now.
|
||||
log(indent + '- ' + child.type + '#' + child.id);
|
||||
// $FlowFixMe - The child should've been refined now.
|
||||
logHostInstances(child.children, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
function logContainer(container : Container, depth) {
|
||||
log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
|
||||
logHostInstances(container.children, depth + 1);
|
||||
}
|
||||
|
||||
function logUpdateQueue(updateQueue : UpdateQueue, depth) {
|
||||
log(
|
||||
' '.repeat(depth + 1) + 'QUEUED UPDATES'
|
||||
);
|
||||
const firstUpdate = updateQueue.first;
|
||||
if (!firstUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
log(
|
||||
' '.repeat(depth + 1) + '~',
|
||||
firstUpdate && firstUpdate.partialState,
|
||||
firstUpdate.callback ? 'with callback' : '',
|
||||
'[' + firstUpdate.priorityLevel + ']'
|
||||
);
|
||||
var next;
|
||||
while (next = firstUpdate.next) {
|
||||
log(
|
||||
' '.repeat(depth + 1) + '~',
|
||||
next.partialState,
|
||||
next.callback ? 'with callback' : '',
|
||||
'[' + firstUpdate.priorityLevel + ']'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function logFiber(fiber : Fiber, depth) {
|
||||
log(
|
||||
' '.repeat(depth) + '- ' + (fiber.type ? fiber.type.name || fiber.type : '[root]'),
|
||||
'[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']'
|
||||
);
|
||||
if (fiber.updateQueue) {
|
||||
logUpdateQueue(fiber.updateQueue, depth);
|
||||
}
|
||||
const childInProgress = fiber.progressedChild;
|
||||
if (childInProgress && childInProgress !== fiber.child) {
|
||||
log(' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority);
|
||||
logFiber(childInProgress, depth + 1);
|
||||
if (fiber.child) {
|
||||
log(' '.repeat(depth + 1) + 'CURRENT');
|
||||
}
|
||||
} else if (fiber.child && fiber.updateQueue) {
|
||||
log(' '.repeat(depth + 1) + 'CHILDREN');
|
||||
}
|
||||
if (fiber.child) {
|
||||
logFiber(fiber.child, depth + 1);
|
||||
}
|
||||
if (fiber.sibling) {
|
||||
logFiber(fiber.sibling, depth);
|
||||
}
|
||||
}
|
||||
|
||||
log('HOST INSTANCES:');
|
||||
logContainer(rootContainer, 0);
|
||||
log('FIBERS:');
|
||||
logFiber(root.current, 0);
|
||||
|
||||
console.log(...bufferedLog);
|
||||
},
|
||||
|
||||
simulateErrorInHostConfig(fn : () => void) {
|
||||
failInBeginPhase = true;
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
failInBeginPhase = false;
|
||||
}
|
||||
},
|
||||
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
|
||||
// Private. Used only by fixtures/fiber-debugger.
|
||||
// (To be fair, it's the only place where `react-noop-renderer` package is used at all.)
|
||||
ReactFiberInstrumentation,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNoop;
|
@ -1,170 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactDOMFrameScheduling
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// This a built-in polyfill for requestIdleCallback. It works by scheduling
|
||||
// a requestAnimationFrame, store the time for the start of the frame, then
|
||||
// schedule a postMessage which gets scheduled after paint. Within the
|
||||
// postMessage handler do as much work as possible until time + frame rate.
|
||||
// By separating the idle call into a separate event tick we ensure that
|
||||
// layout, paint and other browser work is counted against the available time.
|
||||
// The frame rate is dynamically adjusted.
|
||||
|
||||
import type {Deadline} from 'ReactFiberReconciler';
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
|
||||
|
||||
// TODO: There's no way to cancel these, because Fiber doesn't atm.
|
||||
let rAF: (callback: (time: number) => void) => number;
|
||||
let rIC: (callback: (deadline: Deadline) => void) => number;
|
||||
|
||||
if (!ExecutionEnvironment.canUseDOM) {
|
||||
rAF = function(frameCallback: (time: number) => void): number {
|
||||
setTimeout(frameCallback, 16);
|
||||
return 0;
|
||||
};
|
||||
|
||||
rIC = function(frameCallback: (deadline: Deadline) => void): number {
|
||||
setTimeout(() => {
|
||||
frameCallback({
|
||||
timeRemaining() {
|
||||
return Infinity;
|
||||
},
|
||||
});
|
||||
});
|
||||
return 0;
|
||||
};
|
||||
} else if (typeof requestAnimationFrame !== 'function') {
|
||||
invariant(
|
||||
false,
|
||||
'React depends on requestAnimationFrame. Make sure that you load a ' +
|
||||
'polyfill in older browsers.',
|
||||
);
|
||||
} else if (typeof requestIdleCallback !== 'function') {
|
||||
// Wrap requestAnimationFrame and polyfill requestIdleCallback.
|
||||
|
||||
var scheduledRAFCallback = null;
|
||||
var scheduledRICCallback = null;
|
||||
|
||||
var isIdleScheduled = false;
|
||||
var isAnimationFrameScheduled = false;
|
||||
|
||||
var frameDeadline = 0;
|
||||
// We start out assuming that we run at 30fps but then the heuristic tracking
|
||||
// will adjust this value to a faster fps if we get more frequent animation
|
||||
// frames.
|
||||
var previousFrameTime = 33;
|
||||
var activeFrameTime = 33;
|
||||
|
||||
var frameDeadlineObject = {
|
||||
timeRemaining: typeof performance === 'object' &&
|
||||
typeof performance.now === 'function'
|
||||
? function() {
|
||||
// We assume that if we have a performance timer that the rAF callback
|
||||
// gets a performance timer value. Not sure if this is always true.
|
||||
return frameDeadline - performance.now();
|
||||
}
|
||||
: function() {
|
||||
// As a fallback we use Date.now.
|
||||
return frameDeadline - Date.now();
|
||||
},
|
||||
};
|
||||
|
||||
// We use the postMessage trick to defer idle work until after the repaint.
|
||||
var messageKey = '__reactIdleCallback$' + Math.random().toString(36).slice(2);
|
||||
var idleTick = function(event) {
|
||||
if (event.source !== window || event.data !== messageKey) {
|
||||
return;
|
||||
}
|
||||
isIdleScheduled = false;
|
||||
var callback = scheduledRICCallback;
|
||||
scheduledRICCallback = null;
|
||||
if (callback) {
|
||||
callback(frameDeadlineObject);
|
||||
}
|
||||
};
|
||||
// Assumes that we have addEventListener in this environment. Might need
|
||||
// something better for old IE.
|
||||
window.addEventListener('message', idleTick, false);
|
||||
|
||||
var animationTick = function(rafTime) {
|
||||
isAnimationFrameScheduled = false;
|
||||
var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
|
||||
if (
|
||||
nextFrameTime < activeFrameTime &&
|
||||
previousFrameTime < activeFrameTime
|
||||
) {
|
||||
if (nextFrameTime < 8) {
|
||||
// Defensive coding. We don't support higher frame rates than 120hz.
|
||||
// If we get lower than that, it is probably a bug.
|
||||
nextFrameTime = 8;
|
||||
}
|
||||
// If one frame goes long, then the next one can be short to catch up.
|
||||
// If two frames are short in a row, then that's an indication that we
|
||||
// actually have a higher frame rate than what we're currently optimizing.
|
||||
// We adjust our heuristic dynamically accordingly. For example, if we're
|
||||
// running on 120hz display or 90hz VR display.
|
||||
// Take the max of the two in case one of them was an anomaly due to
|
||||
// missed frame deadlines.
|
||||
activeFrameTime = nextFrameTime < previousFrameTime
|
||||
? previousFrameTime
|
||||
: nextFrameTime;
|
||||
} else {
|
||||
previousFrameTime = nextFrameTime;
|
||||
}
|
||||
frameDeadline = rafTime + activeFrameTime;
|
||||
if (!isIdleScheduled) {
|
||||
isIdleScheduled = true;
|
||||
window.postMessage(messageKey, '*');
|
||||
}
|
||||
var callback = scheduledRAFCallback;
|
||||
scheduledRAFCallback = null;
|
||||
if (callback) {
|
||||
callback(rafTime);
|
||||
}
|
||||
};
|
||||
|
||||
rAF = function(callback: (time: number) => void): number {
|
||||
// This assumes that we only schedule one callback at a time because that's
|
||||
// how Fiber uses it.
|
||||
scheduledRAFCallback = callback;
|
||||
if (!isAnimationFrameScheduled) {
|
||||
// If rIC didn't already schedule one, we need to schedule a frame.
|
||||
isAnimationFrameScheduled = true;
|
||||
requestAnimationFrame(animationTick);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
rIC = function(callback: (deadline: Deadline) => void): number {
|
||||
// This assumes that we only schedule one callback at a time because that's
|
||||
// how Fiber uses it.
|
||||
scheduledRICCallback = callback;
|
||||
if (!isAnimationFrameScheduled) {
|
||||
// If rAF didn't already schedule one, we need to schedule a frame.
|
||||
// TODO: If this rAF doesn't materialize because the browser throttles, we
|
||||
// might want to still have setTimeout trigger rIC as a backup to ensure
|
||||
// that we keep performing work.
|
||||
isAnimationFrameScheduled = true;
|
||||
requestAnimationFrame(animationTick);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
} else {
|
||||
rAF = requestAnimationFrame;
|
||||
rIC = requestIdleCallback;
|
||||
}
|
||||
|
||||
exports.rAF = rAF;
|
||||
exports.rIC = rIC;
|
@ -1,438 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactDebugTool
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactInvalidSetStateWarningHook = require('ReactInvalidSetStateWarningHook');
|
||||
var ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook');
|
||||
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
|
||||
var {ReactComponentTreeHook} = require('ReactGlobalSharedState');
|
||||
|
||||
var performanceNow = require('fbjs/lib/performanceNow');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
import type {ReactElement} from 'ReactElementType';
|
||||
import type {DebugID} from 'ReactInstanceType';
|
||||
import type {Operation} from 'ReactHostOperationHistoryHook';
|
||||
|
||||
type Hook = any;
|
||||
|
||||
type TimerType =
|
||||
| 'ctor'
|
||||
| 'render'
|
||||
| 'componentWillMount'
|
||||
| 'componentWillUnmount'
|
||||
| 'componentWillReceiveProps'
|
||||
| 'shouldComponentUpdate'
|
||||
| 'componentWillUpdate'
|
||||
| 'componentDidUpdate'
|
||||
| 'componentDidMount';
|
||||
|
||||
type Measurement = {
|
||||
timerType: TimerType,
|
||||
instanceID: DebugID,
|
||||
duration: number,
|
||||
};
|
||||
|
||||
type TreeSnapshot = {
|
||||
[key: DebugID]: {
|
||||
displayName: string,
|
||||
text: string,
|
||||
updateCount: number,
|
||||
childIDs: Array<DebugID>,
|
||||
ownerID: DebugID,
|
||||
parentID: DebugID,
|
||||
},
|
||||
};
|
||||
|
||||
type HistoryItem = {
|
||||
duration: number,
|
||||
measurements: Array<Measurement>,
|
||||
operations: Array<Operation>,
|
||||
treeSnapshot: TreeSnapshot,
|
||||
};
|
||||
|
||||
export type FlushHistory = Array<HistoryItem>;
|
||||
|
||||
// Trust the developer to only use this with a __DEV__ check
|
||||
var ReactDebugTool = ((null: any): typeof ReactDebugTool);
|
||||
|
||||
if (__DEV__) {
|
||||
var hooks = [];
|
||||
var didHookThrowForEvent = {};
|
||||
|
||||
const callHook = function(event, fn, context, arg1, arg2, arg3, arg4, arg5) {
|
||||
try {
|
||||
fn.call(context, arg1, arg2, arg3, arg4, arg5);
|
||||
} catch (e) {
|
||||
warning(
|
||||
didHookThrowForEvent[event],
|
||||
'Exception thrown by hook while handling %s: %s',
|
||||
event,
|
||||
e + '\n' + e.stack,
|
||||
);
|
||||
didHookThrowForEvent[event] = true;
|
||||
}
|
||||
};
|
||||
|
||||
const emitEvent = function(event, arg1, arg2, arg3, arg4, arg5) {
|
||||
for (var i = 0; i < hooks.length; i++) {
|
||||
var hook = hooks[i];
|
||||
var fn = hook[event];
|
||||
if (fn) {
|
||||
callHook(event, fn, hook, arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var isProfiling = false;
|
||||
var flushHistory = [];
|
||||
var lifeCycleTimerStack = [];
|
||||
var currentFlushNesting = 0;
|
||||
var currentFlushMeasurements = [];
|
||||
var currentFlushStartTime = 0;
|
||||
var currentTimerDebugID = null;
|
||||
var currentTimerStartTime = 0;
|
||||
var currentTimerNestedFlushDuration = 0;
|
||||
var currentTimerType = null;
|
||||
|
||||
var lifeCycleTimerHasWarned = false;
|
||||
|
||||
const clearHistory = function() {
|
||||
ReactComponentTreeHook.purgeUnmountedComponents();
|
||||
ReactHostOperationHistoryHook.clearHistory();
|
||||
};
|
||||
|
||||
const getTreeSnapshot = function(registeredIDs) {
|
||||
return registeredIDs.reduce((tree, id) => {
|
||||
var ownerID = ReactComponentTreeHook.getOwnerID(id);
|
||||
var parentID = ReactComponentTreeHook.getParentID(id);
|
||||
tree[id] = {
|
||||
displayName: ReactComponentTreeHook.getDisplayName(id),
|
||||
text: ReactComponentTreeHook.getText(id),
|
||||
updateCount: ReactComponentTreeHook.getUpdateCount(id),
|
||||
childIDs: ReactComponentTreeHook.getChildIDs(id),
|
||||
// Text nodes don't have owners but this is close enough.
|
||||
ownerID: ownerID ||
|
||||
(parentID && ReactComponentTreeHook.getOwnerID(parentID)) ||
|
||||
0,
|
||||
parentID,
|
||||
};
|
||||
return tree;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const resetMeasurements = function() {
|
||||
var previousStartTime = currentFlushStartTime;
|
||||
var previousMeasurements = currentFlushMeasurements;
|
||||
var previousOperations = ReactHostOperationHistoryHook.getHistory();
|
||||
|
||||
if (currentFlushNesting === 0) {
|
||||
currentFlushStartTime = 0;
|
||||
currentFlushMeasurements = [];
|
||||
clearHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousMeasurements.length || previousOperations.length) {
|
||||
var registeredIDs = ReactComponentTreeHook.getRegisteredIDs();
|
||||
flushHistory.push({
|
||||
duration: performanceNow() - previousStartTime,
|
||||
measurements: previousMeasurements || [],
|
||||
operations: previousOperations || [],
|
||||
treeSnapshot: getTreeSnapshot(registeredIDs),
|
||||
});
|
||||
}
|
||||
|
||||
clearHistory();
|
||||
currentFlushStartTime = performanceNow();
|
||||
currentFlushMeasurements = [];
|
||||
};
|
||||
|
||||
const checkDebugID = function(debugID, allowRoot = false) {
|
||||
if (allowRoot && debugID === 0) {
|
||||
return;
|
||||
}
|
||||
if (!debugID) {
|
||||
warning(false, 'ReactDebugTool: debugID may not be empty.');
|
||||
}
|
||||
};
|
||||
|
||||
const beginLifeCycleTimer = function(debugID, timerType) {
|
||||
if (currentFlushNesting === 0) {
|
||||
return;
|
||||
}
|
||||
if (currentTimerType && !lifeCycleTimerHasWarned) {
|
||||
warning(
|
||||
false,
|
||||
'There is an internal error in the React performance measurement code.' +
|
||||
'\n\nDid not expect %s timer to start while %s timer is still in ' +
|
||||
'progress for %s instance.',
|
||||
timerType,
|
||||
currentTimerType || 'no',
|
||||
debugID === currentTimerDebugID ? 'the same' : 'another',
|
||||
);
|
||||
lifeCycleTimerHasWarned = true;
|
||||
}
|
||||
currentTimerStartTime = performanceNow();
|
||||
currentTimerNestedFlushDuration = 0;
|
||||
currentTimerDebugID = debugID;
|
||||
currentTimerType = timerType;
|
||||
};
|
||||
|
||||
const endLifeCycleTimer = function(debugID, timerType) {
|
||||
if (currentFlushNesting === 0) {
|
||||
return;
|
||||
}
|
||||
if (currentTimerType !== timerType && !lifeCycleTimerHasWarned) {
|
||||
warning(
|
||||
false,
|
||||
'There is an internal error in the React performance measurement code. ' +
|
||||
'We did not expect %s timer to stop while %s timer is still in ' +
|
||||
'progress for %s instance. Please report this as a bug in React.',
|
||||
timerType,
|
||||
currentTimerType || 'no',
|
||||
debugID === currentTimerDebugID ? 'the same' : 'another',
|
||||
);
|
||||
lifeCycleTimerHasWarned = true;
|
||||
}
|
||||
if (isProfiling) {
|
||||
currentFlushMeasurements.push({
|
||||
timerType,
|
||||
instanceID: debugID,
|
||||
duration: performanceNow() -
|
||||
currentTimerStartTime -
|
||||
currentTimerNestedFlushDuration,
|
||||
});
|
||||
}
|
||||
currentTimerStartTime = 0;
|
||||
currentTimerNestedFlushDuration = 0;
|
||||
currentTimerDebugID = null;
|
||||
currentTimerType = null;
|
||||
};
|
||||
|
||||
const pauseCurrentLifeCycleTimer = function() {
|
||||
var currentTimer = {
|
||||
startTime: currentTimerStartTime,
|
||||
nestedFlushStartTime: performanceNow(),
|
||||
debugID: currentTimerDebugID,
|
||||
timerType: currentTimerType,
|
||||
};
|
||||
lifeCycleTimerStack.push(currentTimer);
|
||||
currentTimerStartTime = 0;
|
||||
currentTimerNestedFlushDuration = 0;
|
||||
currentTimerDebugID = null;
|
||||
currentTimerType = null;
|
||||
};
|
||||
|
||||
const resumeCurrentLifeCycleTimer = function() {
|
||||
var {
|
||||
startTime,
|
||||
nestedFlushStartTime,
|
||||
debugID,
|
||||
timerType,
|
||||
} = lifeCycleTimerStack.pop();
|
||||
var nestedFlushDuration = performanceNow() - nestedFlushStartTime;
|
||||
currentTimerStartTime = startTime;
|
||||
currentTimerNestedFlushDuration += nestedFlushDuration;
|
||||
currentTimerDebugID = debugID;
|
||||
currentTimerType = timerType;
|
||||
};
|
||||
|
||||
var lastMarkTimeStamp = 0;
|
||||
var canUsePerformanceMeasure: boolean =
|
||||
typeof performance !== 'undefined' &&
|
||||
typeof performance.mark === 'function' &&
|
||||
typeof performance.clearMarks === 'function' &&
|
||||
typeof performance.measure === 'function' &&
|
||||
typeof performance.clearMeasures === 'function';
|
||||
|
||||
const shouldMark = function(debugID) {
|
||||
if (!isProfiling || !canUsePerformanceMeasure) {
|
||||
return false;
|
||||
}
|
||||
var element = ReactComponentTreeHook.getElement(debugID);
|
||||
if (element == null || typeof element !== 'object') {
|
||||
return false;
|
||||
}
|
||||
var isHostElement = typeof element.type === 'string';
|
||||
if (isHostElement) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const markBegin = function(debugID, markType) {
|
||||
if (!shouldMark(debugID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var markName = `${debugID}::${markType}`;
|
||||
lastMarkTimeStamp = performanceNow();
|
||||
performance.mark(markName);
|
||||
};
|
||||
|
||||
const markEnd = function(debugID, markType) {
|
||||
if (!shouldMark(debugID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var markName = `${debugID}::${markType}`;
|
||||
var displayName =
|
||||
ReactComponentTreeHook.getDisplayName(debugID) || 'Unknown';
|
||||
|
||||
// Chrome has an issue of dropping markers recorded too fast:
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=640652
|
||||
// To work around this, we will not report very small measurements.
|
||||
// I determined the magic number by tweaking it back and forth.
|
||||
// 0.05ms was enough to prevent the issue, but I set it to 0.1ms to be safe.
|
||||
// When the bug is fixed, we can `measure()` unconditionally if we want to.
|
||||
var timeStamp = performanceNow();
|
||||
if (timeStamp - lastMarkTimeStamp > 0.1) {
|
||||
var measurementName = `${displayName} [${markType}]`;
|
||||
performance.measure(measurementName, markName);
|
||||
}
|
||||
|
||||
performance.clearMarks(markName);
|
||||
if (measurementName) {
|
||||
performance.clearMeasures(measurementName);
|
||||
}
|
||||
};
|
||||
|
||||
ReactDebugTool = {
|
||||
addHook(hook: Hook): void {
|
||||
hooks.push(hook);
|
||||
},
|
||||
removeHook(hook: Hook): void {
|
||||
for (var i = 0; i < hooks.length; i++) {
|
||||
if (hooks[i] === hook) {
|
||||
hooks.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
},
|
||||
isProfiling(): boolean {
|
||||
return isProfiling;
|
||||
},
|
||||
beginProfiling(): void {
|
||||
if (isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = true;
|
||||
flushHistory.length = 0;
|
||||
resetMeasurements();
|
||||
ReactDebugTool.addHook(ReactHostOperationHistoryHook);
|
||||
},
|
||||
endProfiling(): void {
|
||||
if (!isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = false;
|
||||
resetMeasurements();
|
||||
ReactDebugTool.removeHook(ReactHostOperationHistoryHook);
|
||||
},
|
||||
getFlushHistory(): FlushHistory {
|
||||
return flushHistory;
|
||||
},
|
||||
onBeginFlush(): void {
|
||||
currentFlushNesting++;
|
||||
resetMeasurements();
|
||||
pauseCurrentLifeCycleTimer();
|
||||
emitEvent('onBeginFlush');
|
||||
},
|
||||
onEndFlush(): void {
|
||||
resetMeasurements();
|
||||
currentFlushNesting--;
|
||||
resumeCurrentLifeCycleTimer();
|
||||
emitEvent('onEndFlush');
|
||||
},
|
||||
onBeginLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeginLifeCycleTimer', debugID, timerType);
|
||||
markBegin(debugID, timerType);
|
||||
beginLifeCycleTimer(debugID, timerType);
|
||||
},
|
||||
onEndLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
|
||||
checkDebugID(debugID);
|
||||
endLifeCycleTimer(debugID, timerType);
|
||||
markEnd(debugID, timerType);
|
||||
emitEvent('onEndLifeCycleTimer', debugID, timerType);
|
||||
},
|
||||
onBeginProcessingChildContext(): void {
|
||||
emitEvent('onBeginProcessingChildContext');
|
||||
},
|
||||
onEndProcessingChildContext(): void {
|
||||
emitEvent('onEndProcessingChildContext');
|
||||
},
|
||||
onHostOperation(operation: Operation) {
|
||||
checkDebugID(operation.instanceID);
|
||||
emitEvent('onHostOperation', operation);
|
||||
},
|
||||
onSetState(): void {
|
||||
emitEvent('onSetState');
|
||||
},
|
||||
onSetChildren(debugID: DebugID, childDebugIDs: Array<DebugID>) {
|
||||
checkDebugID(debugID);
|
||||
childDebugIDs.forEach(checkDebugID);
|
||||
emitEvent('onSetChildren', debugID, childDebugIDs);
|
||||
},
|
||||
onBeforeMountComponent(
|
||||
debugID: DebugID,
|
||||
element: ReactElement,
|
||||
parentDebugID: DebugID,
|
||||
): void {
|
||||
checkDebugID(debugID);
|
||||
checkDebugID(parentDebugID, true);
|
||||
emitEvent('onBeforeMountComponent', debugID, element, parentDebugID);
|
||||
markBegin(debugID, 'mount');
|
||||
},
|
||||
onMountComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
markEnd(debugID, 'mount');
|
||||
emitEvent('onMountComponent', debugID);
|
||||
},
|
||||
onBeforeUpdateComponent(debugID: DebugID, element: ReactElement): void {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeforeUpdateComponent', debugID, element);
|
||||
markBegin(debugID, 'update');
|
||||
},
|
||||
onUpdateComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
markEnd(debugID, 'update');
|
||||
emitEvent('onUpdateComponent', debugID);
|
||||
},
|
||||
onBeforeUnmountComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeforeUnmountComponent', debugID);
|
||||
markBegin(debugID, 'unmount');
|
||||
},
|
||||
onUnmountComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
markEnd(debugID, 'unmount');
|
||||
emitEvent('onUnmountComponent', debugID);
|
||||
},
|
||||
onTestEvent(): void {
|
||||
emitEvent('onTestEvent');
|
||||
},
|
||||
};
|
||||
|
||||
ReactDebugTool.addHook(ReactInvalidSetStateWarningHook);
|
||||
ReactDebugTool.addHook(ReactComponentTreeHook);
|
||||
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
|
||||
if (/[?&]react_perf\b/.test(url)) {
|
||||
ReactDebugTool.beginProfiling();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReactDebugTool;
|
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactGlobalSharedState
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactInternals = require('react')
|
||||
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
||||
|
||||
var ReactGlobalSharedState = {
|
||||
ReactCurrentOwner: ReactInternals.ReactCurrentOwner,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
Object.assign(ReactGlobalSharedState, {
|
||||
ReactComponentTreeHook: ReactInternals.ReactComponentTreeHook,
|
||||
ReactDebugCurrentFrame: ReactInternals.ReactDebugCurrentFrame,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = ReactGlobalSharedState;
|
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactInstrumentation
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Trust the developer to only use ReactInstrumentation with a __DEV__ check
|
||||
var debugTool = ((null: any): typeof ReactDebugTool);
|
||||
|
||||
if (__DEV__) {
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
debugTool = ReactDebugTool;
|
||||
}
|
||||
|
||||
module.exports = {debugTool};
|
@ -1,458 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactPerf
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
var alreadyWarned = false;
|
||||
|
||||
import type {FlushHistory} from 'ReactDebugTool';
|
||||
|
||||
function roundFloat(val, base = 2) {
|
||||
var n = Math.pow(10, base);
|
||||
return Math.floor(val * n) / n;
|
||||
}
|
||||
|
||||
// Flow type definition of console.table is too strict right now, see
|
||||
// https://github.com/facebook/flow/pull/2353 for updates
|
||||
function consoleTable(table: Array<{[key: string]: any}>): void {
|
||||
console.table((table: any));
|
||||
}
|
||||
|
||||
function warnInProduction() {
|
||||
if (alreadyWarned) {
|
||||
return;
|
||||
}
|
||||
alreadyWarned = true;
|
||||
if (typeof console !== 'undefined') {
|
||||
console.error(
|
||||
'ReactPerf is not supported in the production builds of React. ' +
|
||||
'To collect measurements, please use the development build of React instead.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getLastMeasurements() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
return ReactDebugTool.getFlushHistory();
|
||||
}
|
||||
|
||||
function getExclusive(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregatedStats = {};
|
||||
var affectedIDs = {};
|
||||
|
||||
function updateAggregatedStats(
|
||||
treeSnapshot,
|
||||
instanceID,
|
||||
timerType,
|
||||
applyUpdate,
|
||||
) {
|
||||
var {displayName} = treeSnapshot[instanceID];
|
||||
var key = displayName;
|
||||
var stats = aggregatedStats[key];
|
||||
if (!stats) {
|
||||
affectedIDs[key] = {};
|
||||
stats = aggregatedStats[key] = {
|
||||
key,
|
||||
instanceCount: 0,
|
||||
counts: {},
|
||||
durations: {},
|
||||
totalDuration: 0,
|
||||
};
|
||||
}
|
||||
if (!stats.durations[timerType]) {
|
||||
stats.durations[timerType] = 0;
|
||||
}
|
||||
if (!stats.counts[timerType]) {
|
||||
stats.counts[timerType] = 0;
|
||||
}
|
||||
affectedIDs[key][instanceID] = true;
|
||||
applyUpdate(stats);
|
||||
}
|
||||
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements, treeSnapshot} = flush;
|
||||
measurements.forEach(measurement => {
|
||||
var {duration, instanceID, timerType} = measurement;
|
||||
updateAggregatedStats(treeSnapshot, instanceID, timerType, stats => {
|
||||
stats.totalDuration += duration;
|
||||
stats.durations[timerType] += duration;
|
||||
stats.counts[timerType]++;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(aggregatedStats)
|
||||
.map(key => ({
|
||||
...aggregatedStats[key],
|
||||
instanceCount: Object.keys(affectedIDs[key]).length,
|
||||
}))
|
||||
.sort((a, b) => b.totalDuration - a.totalDuration);
|
||||
}
|
||||
|
||||
function getInclusive(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregatedStats = {};
|
||||
var affectedIDs = {};
|
||||
|
||||
function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
|
||||
var {displayName, ownerID} = treeSnapshot[instanceID];
|
||||
var owner = treeSnapshot[ownerID];
|
||||
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
|
||||
var stats = aggregatedStats[key];
|
||||
if (!stats) {
|
||||
affectedIDs[key] = {};
|
||||
stats = aggregatedStats[key] = {
|
||||
key,
|
||||
instanceCount: 0,
|
||||
inclusiveRenderDuration: 0,
|
||||
renderCount: 0,
|
||||
};
|
||||
}
|
||||
affectedIDs[key][instanceID] = true;
|
||||
applyUpdate(stats);
|
||||
}
|
||||
|
||||
var isCompositeByID = {};
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements} = flush;
|
||||
measurements.forEach(measurement => {
|
||||
var {instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
isCompositeByID[instanceID] = true;
|
||||
});
|
||||
});
|
||||
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements, treeSnapshot} = flush;
|
||||
measurements.forEach(measurement => {
|
||||
var {duration, instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
updateAggregatedStats(treeSnapshot, instanceID, stats => {
|
||||
stats.renderCount++;
|
||||
});
|
||||
var nextParentID = instanceID;
|
||||
while (nextParentID) {
|
||||
// As we traverse parents, only count inclusive time towards composites.
|
||||
// We know something is a composite if its render() was called.
|
||||
if (isCompositeByID[nextParentID]) {
|
||||
updateAggregatedStats(treeSnapshot, nextParentID, stats => {
|
||||
stats.inclusiveRenderDuration += duration;
|
||||
});
|
||||
}
|
||||
nextParentID = treeSnapshot[nextParentID].parentID;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(aggregatedStats)
|
||||
.map(key => ({
|
||||
...aggregatedStats[key],
|
||||
instanceCount: Object.keys(affectedIDs[key]).length,
|
||||
}))
|
||||
.sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration);
|
||||
}
|
||||
|
||||
function getWasted(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregatedStats = {};
|
||||
var affectedIDs = {};
|
||||
|
||||
function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
|
||||
var {displayName, ownerID} = treeSnapshot[instanceID];
|
||||
var owner = treeSnapshot[ownerID];
|
||||
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
|
||||
var stats = aggregatedStats[key];
|
||||
if (!stats) {
|
||||
affectedIDs[key] = {};
|
||||
stats = aggregatedStats[key] = {
|
||||
key,
|
||||
instanceCount: 0,
|
||||
inclusiveRenderDuration: 0,
|
||||
renderCount: 0,
|
||||
};
|
||||
}
|
||||
affectedIDs[key][instanceID] = true;
|
||||
applyUpdate(stats);
|
||||
}
|
||||
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements, treeSnapshot, operations} = flush;
|
||||
var isDefinitelyNotWastedByID = {};
|
||||
|
||||
// Find host components associated with an operation in this batch.
|
||||
// Mark all components in their parent tree as definitely not wasted.
|
||||
operations.forEach(operation => {
|
||||
var {instanceID} = operation;
|
||||
var nextParentID = instanceID;
|
||||
while (nextParentID) {
|
||||
isDefinitelyNotWastedByID[nextParentID] = true;
|
||||
nextParentID = treeSnapshot[nextParentID].parentID;
|
||||
}
|
||||
});
|
||||
|
||||
// Find composite components that rendered in this batch.
|
||||
// These are potential candidates for being wasted renders.
|
||||
var renderedCompositeIDs = {};
|
||||
measurements.forEach(measurement => {
|
||||
var {instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
renderedCompositeIDs[instanceID] = true;
|
||||
});
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
var {duration, instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there was a DOM update below this component, or it has just been
|
||||
// mounted, its render() is not considered wasted.
|
||||
var {updateCount} = treeSnapshot[instanceID];
|
||||
if (isDefinitelyNotWastedByID[instanceID] || updateCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We consider this render() wasted.
|
||||
updateAggregatedStats(treeSnapshot, instanceID, stats => {
|
||||
stats.renderCount++;
|
||||
});
|
||||
|
||||
var nextParentID = instanceID;
|
||||
while (nextParentID) {
|
||||
// Any parents rendered during this batch are considered wasted
|
||||
// unless we previously marked them as dirty.
|
||||
var isWasted =
|
||||
renderedCompositeIDs[nextParentID] &&
|
||||
!isDefinitelyNotWastedByID[nextParentID];
|
||||
if (isWasted) {
|
||||
updateAggregatedStats(treeSnapshot, nextParentID, stats => {
|
||||
stats.inclusiveRenderDuration += duration;
|
||||
});
|
||||
}
|
||||
nextParentID = treeSnapshot[nextParentID].parentID;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(aggregatedStats)
|
||||
.map(key => ({
|
||||
...aggregatedStats[key],
|
||||
instanceCount: Object.keys(affectedIDs[key]).length,
|
||||
}))
|
||||
.sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration);
|
||||
}
|
||||
|
||||
function getOperations(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var stats = [];
|
||||
flushHistory.forEach((flush, flushIndex) => {
|
||||
var {operations, treeSnapshot} = flush;
|
||||
operations.forEach(operation => {
|
||||
var {instanceID, type, payload} = operation;
|
||||
var {displayName, ownerID} = treeSnapshot[instanceID];
|
||||
var owner = treeSnapshot[ownerID];
|
||||
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
|
||||
|
||||
stats.push({
|
||||
flushIndex,
|
||||
instanceID,
|
||||
key,
|
||||
type,
|
||||
ownerID,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
});
|
||||
return stats;
|
||||
}
|
||||
|
||||
function printExclusive(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getExclusive(flushHistory);
|
||||
var table = stats.map(item => {
|
||||
var {key, instanceCount, totalDuration} = item;
|
||||
var renderCount = item.counts.render || 0;
|
||||
var renderDuration = item.durations.render || 0;
|
||||
return {
|
||||
Component: key,
|
||||
'Total time (ms)': roundFloat(totalDuration),
|
||||
'Instance count': instanceCount,
|
||||
'Total render time (ms)': roundFloat(renderDuration),
|
||||
'Average render time (ms)': renderCount
|
||||
? roundFloat(renderDuration / renderCount)
|
||||
: undefined,
|
||||
'Render count': renderCount,
|
||||
'Total lifecycle time (ms)': roundFloat(totalDuration - renderDuration),
|
||||
};
|
||||
});
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
function printInclusive(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getInclusive(flushHistory);
|
||||
var table = stats.map(item => {
|
||||
var {key, instanceCount, inclusiveRenderDuration, renderCount} = item;
|
||||
return {
|
||||
'Owner > Component': key,
|
||||
'Inclusive render time (ms)': roundFloat(inclusiveRenderDuration),
|
||||
'Instance count': instanceCount,
|
||||
'Render count': renderCount,
|
||||
};
|
||||
});
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
function printWasted(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getWasted(flushHistory);
|
||||
var table = stats.map(item => {
|
||||
var {key, instanceCount, inclusiveRenderDuration, renderCount} = item;
|
||||
return {
|
||||
'Owner > Component': key,
|
||||
'Inclusive wasted time (ms)': roundFloat(inclusiveRenderDuration),
|
||||
'Instance count': instanceCount,
|
||||
'Render count': renderCount,
|
||||
};
|
||||
});
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
function printOperations(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getOperations(flushHistory);
|
||||
var table = stats.map(stat => ({
|
||||
'Owner > Node': stat.key,
|
||||
Operation: stat.type,
|
||||
Payload: typeof stat.payload === 'object'
|
||||
? JSON.stringify(stat.payload)
|
||||
: stat.payload,
|
||||
'Flush index': stat.flushIndex,
|
||||
'Owner Component ID': stat.ownerID,
|
||||
'DOM Component ID': stat.instanceID,
|
||||
}));
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
var warnedAboutPrintDOM = false;
|
||||
function printDOM(measurements: FlushHistory) {
|
||||
warning(
|
||||
warnedAboutPrintDOM,
|
||||
'`ReactPerf.printDOM(...)` is deprecated. Use ' +
|
||||
'`ReactPerf.printOperations(...)` instead.',
|
||||
);
|
||||
warnedAboutPrintDOM = true;
|
||||
return printOperations(measurements);
|
||||
}
|
||||
|
||||
var warnedAboutGetMeasurementsSummaryMap = false;
|
||||
function getMeasurementsSummaryMap(measurements: FlushHistory) {
|
||||
warning(
|
||||
warnedAboutGetMeasurementsSummaryMap,
|
||||
'`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' +
|
||||
'`ReactPerf.getWasted(...)` instead.',
|
||||
);
|
||||
warnedAboutGetMeasurementsSummaryMap = true;
|
||||
return getWasted(measurements);
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDebugTool.beginProfiling();
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDebugTool.endProfiling();
|
||||
}
|
||||
|
||||
function isRunning() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReactDebugTool.isProfiling();
|
||||
}
|
||||
|
||||
var ReactPerfAnalysis = {
|
||||
getLastMeasurements,
|
||||
getExclusive,
|
||||
getInclusive,
|
||||
getWasted,
|
||||
getOperations,
|
||||
printExclusive,
|
||||
printInclusive,
|
||||
printWasted,
|
||||
printOperations,
|
||||
start,
|
||||
stop,
|
||||
isRunning,
|
||||
// Deprecated:
|
||||
printDOM,
|
||||
getMeasurementsSummaryMap,
|
||||
};
|
||||
|
||||
module.exports = ReactPerfAnalysis;
|
@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
|
||||
const describeFiber = ReactDOMFeatureFlags.useFiber ? describe : xdescribe;
|
||||
|
||||
describeFiber('ReactDOMFrameScheduling', () => {
|
||||
it('throws when requestAnimationFrame is not polyfilled in the browser', () => {
|
||||
const previousRAF = global.requestAnimationFrame;
|
||||
try {
|
||||
global.requestAnimationFrame = undefined;
|
||||
jest.resetModules();
|
||||
expect(() => {
|
||||
require('ReactDOM');
|
||||
}).toThrow(
|
||||
'React depends on requestAnimationFrame. Make sure that you load a ' +
|
||||
'polyfill in older browsers.',
|
||||
);
|
||||
} finally {
|
||||
global.requestAnimationFrame = previousRAF;
|
||||
}
|
||||
});
|
||||
|
||||
// We're just testing importing, not using it.
|
||||
// It is important because even isomorphic components may import it.
|
||||
it('can import findDOMNode in Node environment', () => {
|
||||
const previousRAF = global.requestAnimationFrame;
|
||||
const previousRIC = global.requestIdleCallback;
|
||||
const prevWindow = global.window;
|
||||
try {
|
||||
global.requestAnimationFrame = undefined;
|
||||
global.requestIdleCallback = undefined;
|
||||
// Simulate the Node environment:
|
||||
delete global.window;
|
||||
jest.resetModules();
|
||||
expect(() => {
|
||||
require('ReactDOM');
|
||||
}).not.toThrow();
|
||||
} finally {
|
||||
global.requestAnimationFrame = previousRAF;
|
||||
global.requestIdleCallback = previousRIC;
|
||||
global.window = prevWindow;
|
||||
}
|
||||
});
|
||||
});
|
@ -1,84 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('ReactDebugTool', () => {
|
||||
var ReactDebugTool;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactDebugTool = require('ReactDebugTool');
|
||||
});
|
||||
|
||||
it('should add and remove hooks', () => {
|
||||
var handler1 = jasmine.createSpy('spy');
|
||||
var handler2 = jasmine.createSpy('spy');
|
||||
var hook1 = {onTestEvent: handler1};
|
||||
var hook2 = {onTestEvent: handler2};
|
||||
|
||||
ReactDebugTool.addHook(hook1);
|
||||
ReactDebugTool.onTestEvent();
|
||||
expect(handler1.calls.count()).toBe(1);
|
||||
expect(handler2.calls.count()).toBe(0);
|
||||
|
||||
ReactDebugTool.onTestEvent();
|
||||
expect(handler1.calls.count()).toBe(2);
|
||||
expect(handler2.calls.count()).toBe(0);
|
||||
|
||||
ReactDebugTool.addHook(hook2);
|
||||
ReactDebugTool.onTestEvent();
|
||||
expect(handler1.calls.count()).toBe(3);
|
||||
expect(handler2.calls.count()).toBe(1);
|
||||
|
||||
ReactDebugTool.onTestEvent();
|
||||
expect(handler1.calls.count()).toBe(4);
|
||||
expect(handler2.calls.count()).toBe(2);
|
||||
|
||||
ReactDebugTool.removeHook(hook1);
|
||||
ReactDebugTool.onTestEvent();
|
||||
expect(handler1.calls.count()).toBe(4);
|
||||
expect(handler2.calls.count()).toBe(3);
|
||||
|
||||
ReactDebugTool.removeHook(hook2);
|
||||
ReactDebugTool.onTestEvent();
|
||||
expect(handler1.calls.count()).toBe(4);
|
||||
expect(handler2.calls.count()).toBe(3);
|
||||
});
|
||||
|
||||
it('warns once when an error is thrown in hook', () => {
|
||||
spyOn(console, 'error');
|
||||
ReactDebugTool.addHook({
|
||||
onTestEvent() {
|
||||
throw new Error('Hi.');
|
||||
},
|
||||
});
|
||||
|
||||
ReactDebugTool.onTestEvent();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Exception thrown by hook while handling ' + 'onTestEvent: Error: Hi.',
|
||||
);
|
||||
|
||||
ReactDebugTool.onTestEvent();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
});
|
||||
|
||||
it('returns isProfiling state', () => {
|
||||
expect(ReactDebugTool.isProfiling()).toBe(false);
|
||||
|
||||
ReactDebugTool.beginProfiling();
|
||||
expect(ReactDebugTool.isProfiling()).toBe(true);
|
||||
|
||||
ReactDebugTool.endProfiling();
|
||||
expect(ReactDebugTool.isProfiling()).toBe(false);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactDebugCurrentFiber
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
|
||||
type LifeCyclePhase = 'render' | 'getChildContext';
|
||||
|
||||
if (__DEV__) {
|
||||
var getComponentName = require('getComponentName');
|
||||
var {
|
||||
getStackAddendumByWorkInProgressFiber,
|
||||
} = require('ReactFiberComponentTreeHook');
|
||||
}
|
||||
|
||||
function getCurrentFiberOwnerName(): string | null {
|
||||
if (__DEV__) {
|
||||
const fiber = ReactDebugCurrentFiber.current;
|
||||
if (fiber === null) {
|
||||
return null;
|
||||
}
|
||||
if (fiber._debugOwner != null) {
|
||||
return getComponentName(fiber._debugOwner);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getCurrentFiberStackAddendum(): string | null {
|
||||
if (__DEV__) {
|
||||
const fiber = ReactDebugCurrentFiber.current;
|
||||
if (fiber === null) {
|
||||
return null;
|
||||
}
|
||||
// Safe because if current fiber exists, we are reconciling,
|
||||
// and it is guaranteed to be the work-in-progress version.
|
||||
return getStackAddendumByWorkInProgressFiber(fiber);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var ReactDebugCurrentFiber = {
|
||||
current: (null: Fiber | null),
|
||||
phase: (null: LifeCyclePhase | null),
|
||||
|
||||
getCurrentFiberOwnerName,
|
||||
getCurrentFiberStackAddendum,
|
||||
};
|
||||
|
||||
module.exports = ReactDebugCurrentFiber;
|
@ -1,401 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactDebugFiberPerf
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
|
||||
type MeasurementPhase =
|
||||
| 'componentWillMount'
|
||||
| 'componentWillUnmount'
|
||||
| 'componentWillReceiveProps'
|
||||
| 'shouldComponentUpdate'
|
||||
| 'componentWillUpdate'
|
||||
| 'componentDidUpdate'
|
||||
| 'componentDidMount'
|
||||
| 'getChildContext';
|
||||
|
||||
// Trust the developer to only use this with a __DEV__ check
|
||||
let ReactDebugFiberPerf = ((null: any): typeof ReactDebugFiberPerf);
|
||||
|
||||
if (__DEV__) {
|
||||
const {
|
||||
HostRoot,
|
||||
HostComponent,
|
||||
HostText,
|
||||
HostPortal,
|
||||
YieldComponent,
|
||||
Fragment,
|
||||
} = require('ReactTypeOfWork');
|
||||
|
||||
const getComponentName = require('getComponentName');
|
||||
|
||||
// Prefix measurements so that it's possible to filter them.
|
||||
// Longer prefixes are hard to read in DevTools.
|
||||
const reactEmoji = '\u269B';
|
||||
const warningEmoji = '\u26D4';
|
||||
const supportsUserTiming =
|
||||
typeof performance !== 'undefined' &&
|
||||
typeof performance.mark === 'function' &&
|
||||
typeof performance.clearMarks === 'function' &&
|
||||
typeof performance.measure === 'function' &&
|
||||
typeof performance.clearMeasures === 'function';
|
||||
|
||||
// Keep track of current fiber so that we know the path to unwind on pause.
|
||||
// TODO: this looks the same as nextUnitOfWork in scheduler. Can we unify them?
|
||||
let currentFiber: Fiber | null = null;
|
||||
// If we're in the middle of user code, which fiber and method is it?
|
||||
// Reusing `currentFiber` would be confusing for this because user code fiber
|
||||
// can change during commit phase too, but we don't need to unwind it (since
|
||||
// lifecycles in the commit phase don't resemble a tree).
|
||||
let currentPhase: MeasurementPhase | null = null;
|
||||
let currentPhaseFiber: Fiber | null = null;
|
||||
// Did lifecycle hook schedule an update? This is often a performance problem,
|
||||
// so we will keep track of it, and include it in the report.
|
||||
// Track commits caused by cascading updates.
|
||||
let isCommitting: boolean = false;
|
||||
let hasScheduledUpdateInCurrentCommit: boolean = false;
|
||||
let hasScheduledUpdateInCurrentPhase: boolean = false;
|
||||
let commitCountInCurrentWorkLoop: number = 0;
|
||||
let effectCountInCurrentCommit: number = 0;
|
||||
// During commits, we only show a measurement once per method name
|
||||
// to avoid stretch the commit phase with measurement overhead.
|
||||
const labelsInCurrentCommit: Set<string> = new Set();
|
||||
|
||||
const formatMarkName = (markName: string) => {
|
||||
return `${reactEmoji} ${markName}`;
|
||||
};
|
||||
|
||||
const formatLabel = (label: string, warning: string | null) => {
|
||||
const prefix = warning ? `${warningEmoji} ` : `${reactEmoji} `;
|
||||
const suffix = warning ? ` Warning: ${warning}` : '';
|
||||
return `${prefix}${label}${suffix}`;
|
||||
};
|
||||
|
||||
const beginMark = (markName: string) => {
|
||||
performance.mark(formatMarkName(markName));
|
||||
};
|
||||
|
||||
const clearMark = (markName: string) => {
|
||||
performance.clearMarks(formatMarkName(markName));
|
||||
};
|
||||
|
||||
const endMark = (label: string, markName: string, warning: string | null) => {
|
||||
const formattedMarkName = formatMarkName(markName);
|
||||
const formattedLabel = formatLabel(label, warning);
|
||||
try {
|
||||
performance.measure(formattedLabel, formattedMarkName);
|
||||
} catch (err) {
|
||||
// If previous mark was missing for some reason, this will throw.
|
||||
// This could only happen if React crashed in an unexpected place earlier.
|
||||
// Don't pile on with more errors.
|
||||
}
|
||||
// Clear marks immediately to avoid growing buffer.
|
||||
performance.clearMarks(formattedMarkName);
|
||||
performance.clearMeasures(formattedLabel);
|
||||
};
|
||||
|
||||
const getFiberMarkName = (label: string, debugID: number) => {
|
||||
return `${label} (#${debugID})`;
|
||||
};
|
||||
|
||||
const getFiberLabel = (
|
||||
componentName: string,
|
||||
isMounted: boolean,
|
||||
phase: MeasurementPhase | null,
|
||||
) => {
|
||||
if (phase === null) {
|
||||
// These are composite component total time measurements.
|
||||
return `${componentName} [${isMounted ? 'update' : 'mount'}]`;
|
||||
} else {
|
||||
// Composite component methods.
|
||||
return `${componentName}.${phase}`;
|
||||
}
|
||||
};
|
||||
|
||||
const beginFiberMark = (
|
||||
fiber: Fiber,
|
||||
phase: MeasurementPhase | null,
|
||||
): boolean => {
|
||||
const componentName = getComponentName(fiber) || 'Unknown';
|
||||
const debugID = ((fiber._debugID: any): number);
|
||||
const isMounted = fiber.alternate !== null;
|
||||
const label = getFiberLabel(componentName, isMounted, phase);
|
||||
|
||||
if (isCommitting && labelsInCurrentCommit.has(label)) {
|
||||
// During the commit phase, we don't show duplicate labels because
|
||||
// there is a fixed overhead for every measurement, and we don't
|
||||
// want to stretch the commit phase beyond necessary.
|
||||
return false;
|
||||
}
|
||||
labelsInCurrentCommit.add(label);
|
||||
|
||||
const markName = getFiberMarkName(label, debugID);
|
||||
beginMark(markName);
|
||||
return true;
|
||||
};
|
||||
|
||||
const clearFiberMark = (fiber: Fiber, phase: MeasurementPhase | null) => {
|
||||
const componentName = getComponentName(fiber) || 'Unknown';
|
||||
const debugID = ((fiber._debugID: any): number);
|
||||
const isMounted = fiber.alternate !== null;
|
||||
const label = getFiberLabel(componentName, isMounted, phase);
|
||||
const markName = getFiberMarkName(label, debugID);
|
||||
clearMark(markName);
|
||||
};
|
||||
|
||||
const endFiberMark = (
|
||||
fiber: Fiber,
|
||||
phase: MeasurementPhase | null,
|
||||
warning: string | null,
|
||||
) => {
|
||||
const componentName = getComponentName(fiber) || 'Unknown';
|
||||
const debugID = ((fiber._debugID: any): number);
|
||||
const isMounted = fiber.alternate !== null;
|
||||
const label = getFiberLabel(componentName, isMounted, phase);
|
||||
const markName = getFiberMarkName(label, debugID);
|
||||
endMark(label, markName, warning);
|
||||
};
|
||||
|
||||
const shouldIgnoreFiber = (fiber: Fiber): boolean => {
|
||||
// Host components should be skipped in the timeline.
|
||||
// We could check typeof fiber.type, but does this work with RN?
|
||||
switch (fiber.tag) {
|
||||
case HostRoot:
|
||||
case HostComponent:
|
||||
case HostText:
|
||||
case HostPortal:
|
||||
case YieldComponent:
|
||||
case Fragment:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const clearPendingPhaseMeasurement = () => {
|
||||
if (currentPhase !== null && currentPhaseFiber !== null) {
|
||||
clearFiberMark(currentPhaseFiber, currentPhase);
|
||||
}
|
||||
currentPhaseFiber = null;
|
||||
currentPhase = null;
|
||||
hasScheduledUpdateInCurrentPhase = false;
|
||||
};
|
||||
|
||||
const pauseTimers = () => {
|
||||
// Stops all currently active measurements so that they can be resumed
|
||||
// if we continue in a later deferred loop from the same unit of work.
|
||||
let fiber = currentFiber;
|
||||
while (fiber) {
|
||||
if (fiber._debugIsCurrentlyTiming) {
|
||||
endFiberMark(fiber, null, null);
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
};
|
||||
|
||||
const resumeTimersRecursively = (fiber: Fiber) => {
|
||||
if (fiber.return !== null) {
|
||||
resumeTimersRecursively(fiber.return);
|
||||
}
|
||||
if (fiber._debugIsCurrentlyTiming) {
|
||||
beginFiberMark(fiber, null);
|
||||
}
|
||||
};
|
||||
|
||||
const resumeTimers = () => {
|
||||
// Resumes all measurements that were active during the last deferred loop.
|
||||
if (currentFiber !== null) {
|
||||
resumeTimersRecursively(currentFiber);
|
||||
}
|
||||
};
|
||||
|
||||
ReactDebugFiberPerf = {
|
||||
recordEffect(): void {
|
||||
effectCountInCurrentCommit++;
|
||||
},
|
||||
|
||||
recordScheduleUpdate(): void {
|
||||
if (isCommitting) {
|
||||
hasScheduledUpdateInCurrentCommit = true;
|
||||
}
|
||||
if (
|
||||
currentPhase !== null &&
|
||||
currentPhase !== 'componentWillMount' &&
|
||||
currentPhase !== 'componentWillReceiveProps'
|
||||
) {
|
||||
hasScheduledUpdateInCurrentPhase = true;
|
||||
}
|
||||
},
|
||||
|
||||
startWorkTimer(fiber: Fiber): void {
|
||||
if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
|
||||
return;
|
||||
}
|
||||
// If we pause, this is the fiber to unwind from.
|
||||
currentFiber = fiber;
|
||||
if (!beginFiberMark(fiber, null)) {
|
||||
return;
|
||||
}
|
||||
fiber._debugIsCurrentlyTiming = true;
|
||||
},
|
||||
|
||||
cancelWorkTimer(fiber: Fiber): void {
|
||||
if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
|
||||
return;
|
||||
}
|
||||
// Remember we shouldn't complete measurement for this fiber.
|
||||
// Otherwise flamechart will be deep even for small updates.
|
||||
fiber._debugIsCurrentlyTiming = false;
|
||||
clearFiberMark(fiber, null);
|
||||
},
|
||||
|
||||
stopWorkTimer(fiber: Fiber): void {
|
||||
if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
|
||||
return;
|
||||
}
|
||||
// If we pause, its parent is the fiber to unwind from.
|
||||
currentFiber = fiber.return;
|
||||
if (!fiber._debugIsCurrentlyTiming) {
|
||||
return;
|
||||
}
|
||||
fiber._debugIsCurrentlyTiming = false;
|
||||
endFiberMark(fiber, null, null);
|
||||
},
|
||||
|
||||
startPhaseTimer(fiber: Fiber, phase: MeasurementPhase): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
clearPendingPhaseMeasurement();
|
||||
if (!beginFiberMark(fiber, phase)) {
|
||||
return;
|
||||
}
|
||||
currentPhaseFiber = fiber;
|
||||
currentPhase = phase;
|
||||
},
|
||||
|
||||
stopPhaseTimer(): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
if (currentPhase !== null && currentPhaseFiber !== null) {
|
||||
const warning = hasScheduledUpdateInCurrentPhase
|
||||
? 'Scheduled a cascading update'
|
||||
: null;
|
||||
endFiberMark(currentPhaseFiber, currentPhase, warning);
|
||||
}
|
||||
currentPhase = null;
|
||||
currentPhaseFiber = null;
|
||||
},
|
||||
|
||||
startWorkLoopTimer(): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
commitCountInCurrentWorkLoop = 0;
|
||||
// This is top level call.
|
||||
// Any other measurements are performed within.
|
||||
beginMark('(React Tree Reconciliation)');
|
||||
// Resume any measurements that were in progress during the last loop.
|
||||
resumeTimers();
|
||||
},
|
||||
|
||||
stopWorkLoopTimer(): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
const warning = commitCountInCurrentWorkLoop > 1
|
||||
? 'There were cascading updates'
|
||||
: null;
|
||||
commitCountInCurrentWorkLoop = 0;
|
||||
// Pause any measurements until the next loop.
|
||||
pauseTimers();
|
||||
endMark(
|
||||
'(React Tree Reconciliation)',
|
||||
'(React Tree Reconciliation)',
|
||||
warning,
|
||||
);
|
||||
},
|
||||
|
||||
startCommitTimer(): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
isCommitting = true;
|
||||
hasScheduledUpdateInCurrentCommit = false;
|
||||
labelsInCurrentCommit.clear();
|
||||
beginMark('(Committing Changes)');
|
||||
},
|
||||
|
||||
stopCommitTimer(): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
|
||||
let warning = null;
|
||||
if (hasScheduledUpdateInCurrentCommit) {
|
||||
warning = 'Lifecycle hook scheduled a cascading update';
|
||||
} else if (commitCountInCurrentWorkLoop > 0) {
|
||||
warning = 'Caused by a cascading update in earlier commit';
|
||||
}
|
||||
hasScheduledUpdateInCurrentCommit = false;
|
||||
commitCountInCurrentWorkLoop++;
|
||||
isCommitting = false;
|
||||
labelsInCurrentCommit.clear();
|
||||
|
||||
endMark('(Committing Changes)', '(Committing Changes)', warning);
|
||||
},
|
||||
|
||||
startCommitHostEffectsTimer(): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
effectCountInCurrentCommit = 0;
|
||||
beginMark('(Committing Host Effects)');
|
||||
},
|
||||
|
||||
stopCommitHostEffectsTimer(): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
const count = effectCountInCurrentCommit;
|
||||
effectCountInCurrentCommit = 0;
|
||||
endMark(
|
||||
`(Committing Host Effects: ${count} Total)`,
|
||||
'(Committing Host Effects)',
|
||||
null,
|
||||
);
|
||||
},
|
||||
|
||||
startCommitLifeCyclesTimer(): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
effectCountInCurrentCommit = 0;
|
||||
beginMark('(Calling Lifecycle Methods)');
|
||||
},
|
||||
|
||||
stopCommitLifeCyclesTimer(): void {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
const count = effectCountInCurrentCommit;
|
||||
effectCountInCurrentCommit = 0;
|
||||
endMark(
|
||||
`(Calling Lifecycle Methods: ${count} Total)`,
|
||||
'(Calling Lifecycle Methods)',
|
||||
null,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = ReactDebugFiberPerf;
|
@ -1,474 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiber
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ReactElement, Source} from 'ReactElementType';
|
||||
import type {ReactInstance, DebugID} from 'ReactInstanceType';
|
||||
import type {ReactFragment} from 'ReactTypes';
|
||||
import type {ReactCoroutine, ReactYield} from 'ReactCoroutine';
|
||||
import type {ReactPortal} from 'ReactPortal';
|
||||
import type {TypeOfWork} from 'ReactTypeOfWork';
|
||||
import type {TypeOfInternalContext} from 'ReactTypeOfInternalContext';
|
||||
import type {TypeOfSideEffect} from 'ReactTypeOfSideEffect';
|
||||
import type {PriorityLevel} from 'ReactPriorityLevel';
|
||||
import type {UpdateQueue} from 'ReactFiberUpdateQueue';
|
||||
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
ClassComponent,
|
||||
HostRoot,
|
||||
HostComponent,
|
||||
HostText,
|
||||
HostPortal,
|
||||
CoroutineComponent,
|
||||
YieldComponent,
|
||||
Fragment,
|
||||
} = require('ReactTypeOfWork');
|
||||
|
||||
var {NoWork} = require('ReactPriorityLevel');
|
||||
|
||||
var {NoContext} = require('ReactTypeOfInternalContext');
|
||||
|
||||
var {NoEffect} = require('ReactTypeOfSideEffect');
|
||||
|
||||
var {cloneUpdateQueue} = require('ReactFiberUpdateQueue');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
if (__DEV__) {
|
||||
var getComponentName = require('getComponentName');
|
||||
|
||||
var hasBadMapPolyfill = false;
|
||||
try {
|
||||
const nonExtensibleObject = Object.preventExtensions({});
|
||||
/* eslint-disable no-new */
|
||||
new Map([[nonExtensibleObject, null]]);
|
||||
new Set([nonExtensibleObject]);
|
||||
/* eslint-enable no-new */
|
||||
} catch (e) {
|
||||
// TODO: Consider warning about bad polyfills
|
||||
hasBadMapPolyfill = true;
|
||||
}
|
||||
}
|
||||
|
||||
// A Fiber is work on a Component that needs to be done or was done. There can
|
||||
// be more than one per component.
|
||||
export type Fiber = {
|
||||
// __DEV__ only
|
||||
_debugID?: DebugID,
|
||||
_debugSource?: Source | null,
|
||||
_debugOwner?: Fiber | ReactInstance | null, // Stack compatible
|
||||
_debugIsCurrentlyTiming?: boolean,
|
||||
|
||||
// These first fields are conceptually members of an Instance. This used to
|
||||
// be split into a separate type and intersected with the other Fiber fields,
|
||||
// but until Flow fixes its intersection bugs, we've merged them into a
|
||||
// single type.
|
||||
|
||||
// An Instance is shared between all versions of a component. We can easily
|
||||
// break this out into a separate object to avoid copying so much to the
|
||||
// alternate versions of the tree. We put this on a single object for now to
|
||||
// minimize the number of objects created during the initial render.
|
||||
|
||||
// Tag identifying the type of fiber.
|
||||
tag: TypeOfWork,
|
||||
|
||||
// Unique identifier of this child.
|
||||
key: null | string,
|
||||
|
||||
// The function/class/module associated with this fiber.
|
||||
type: any,
|
||||
|
||||
// The local state associated with this fiber.
|
||||
stateNode: any,
|
||||
|
||||
// Conceptual aliases
|
||||
// parent : Instance -> return The parent happens to be the same as the
|
||||
// return fiber since we've merged the fiber and instance.
|
||||
|
||||
// Remaining fields belong to Fiber
|
||||
|
||||
// The Fiber to return to after finishing processing this one.
|
||||
// This is effectively the parent, but there can be multiple parents (two)
|
||||
// so this is only the parent of the thing we're currently processing.
|
||||
// It is conceptually the same as the return address of a stack frame.
|
||||
return: Fiber | null,
|
||||
|
||||
// Singly Linked List Tree Structure.
|
||||
child: Fiber | null,
|
||||
sibling: Fiber | null,
|
||||
index: number,
|
||||
|
||||
// The ref last used to attach this node.
|
||||
// I'll avoid adding an owner field for prod and model that as functions.
|
||||
ref: null | (((handle: mixed) => void) & {_stringRef: ?string}),
|
||||
|
||||
// Input is the data coming into process this fiber. Arguments. Props.
|
||||
pendingProps: any, // This type will be more specific once we overload the tag.
|
||||
// TODO: I think that there is a way to merge pendingProps and memoizedProps.
|
||||
memoizedProps: any, // The props used to create the output.
|
||||
|
||||
// A queue of state updates and callbacks.
|
||||
updateQueue: UpdateQueue | null,
|
||||
|
||||
// The state used to create the output
|
||||
memoizedState: any,
|
||||
|
||||
// Bitfield that describes properties about the fiber and its subtree. E.g.
|
||||
// the AsyncUpdates flag indicates whether the subtree should be async-by-
|
||||
// default. When a fiber is created, it inherits the internalContextTag of its
|
||||
// parent. Additional flags can be set at creation time, but after than the
|
||||
// value should remain unchanged throughout the fiber's lifetime, particularly
|
||||
// before its child fibers are created.
|
||||
internalContextTag: TypeOfInternalContext,
|
||||
|
||||
// Effect
|
||||
effectTag: TypeOfSideEffect,
|
||||
|
||||
// Singly linked list fast path to the next fiber with side-effects.
|
||||
nextEffect: Fiber | null,
|
||||
|
||||
// The first and last fiber with side-effect within this subtree. This allows
|
||||
// us to reuse a slice of the linked list when we reuse the work done within
|
||||
// this fiber.
|
||||
firstEffect: Fiber | null,
|
||||
lastEffect: Fiber | null,
|
||||
|
||||
// This will be used to quickly determine if a subtree has no pending changes.
|
||||
pendingWorkPriority: PriorityLevel,
|
||||
|
||||
// This value represents the priority level that was last used to process this
|
||||
// component. This indicates whether it is better to continue from the
|
||||
// progressed work or if it is better to continue from the current state.
|
||||
progressedPriority: PriorityLevel,
|
||||
|
||||
// If work bails out on a Fiber that already had some work started at a lower
|
||||
// priority, then we need to store the progressed work somewhere. This holds
|
||||
// the started child set until we need to get back to working on it. It may
|
||||
// or may not be the same as the "current" child.
|
||||
progressedChild: Fiber | null,
|
||||
|
||||
// When we reconcile children onto progressedChild it is possible that we have
|
||||
// to delete some child fibers. We need to keep track of this side-effects so
|
||||
// that if we continue later on, we have to include those effects. Deletions
|
||||
// are added in the reverse order from sibling pointers.
|
||||
progressedFirstDeletion: Fiber | null,
|
||||
progressedLastDeletion: Fiber | null,
|
||||
|
||||
// This is a pooled version of a Fiber. Every fiber that gets updated will
|
||||
// eventually have a pair. There are cases when we can clean up pairs to save
|
||||
// memory if we need to.
|
||||
alternate: Fiber | null,
|
||||
|
||||
// Conceptual aliases
|
||||
// workInProgress : Fiber -> alternate The alternate used for reuse happens
|
||||
// to be the same as work in progress.
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
var debugCounter = 1;
|
||||
}
|
||||
|
||||
// This is a constructor of a POJO instead of a constructor function for a few
|
||||
// reasons:
|
||||
// 1) Nobody should add any instance methods on this. Instance methods can be
|
||||
// more difficult to predict when they get optimized and they are almost
|
||||
// never inlined properly in static compilers.
|
||||
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
|
||||
// always know when it is a fiber.
|
||||
// 3) We can easily go from a createFiber call to calling a constructor if that
|
||||
// is faster. The opposite is not true.
|
||||
// 4) We might want to experiment with using numeric keys since they are easier
|
||||
// to optimize in a non-JIT environment.
|
||||
// 5) It should be easy to port this to a C struct and keep a C implementation
|
||||
// compatible.
|
||||
var createFiber = function(
|
||||
tag: TypeOfWork,
|
||||
key: null | string,
|
||||
internalContextTag: TypeOfInternalContext,
|
||||
): Fiber {
|
||||
var fiber: Fiber = {
|
||||
// Instance
|
||||
|
||||
tag: tag,
|
||||
|
||||
key: key,
|
||||
|
||||
type: null,
|
||||
|
||||
stateNode: null,
|
||||
|
||||
// Fiber
|
||||
|
||||
return: null,
|
||||
|
||||
child: null,
|
||||
sibling: null,
|
||||
index: 0,
|
||||
|
||||
ref: null,
|
||||
|
||||
pendingProps: null,
|
||||
memoizedProps: null,
|
||||
updateQueue: null,
|
||||
memoizedState: null,
|
||||
|
||||
internalContextTag,
|
||||
|
||||
effectTag: NoEffect,
|
||||
nextEffect: null,
|
||||
firstEffect: null,
|
||||
lastEffect: null,
|
||||
|
||||
pendingWorkPriority: NoWork,
|
||||
progressedPriority: NoWork,
|
||||
progressedChild: null,
|
||||
progressedFirstDeletion: null,
|
||||
progressedLastDeletion: null,
|
||||
|
||||
alternate: null,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
fiber._debugID = debugCounter++;
|
||||
fiber._debugSource = null;
|
||||
fiber._debugOwner = null;
|
||||
fiber._debugIsCurrentlyTiming = false;
|
||||
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
|
||||
Object.preventExtensions(fiber);
|
||||
}
|
||||
}
|
||||
|
||||
return fiber;
|
||||
};
|
||||
|
||||
function shouldConstruct(Component) {
|
||||
return !!(Component.prototype && Component.prototype.isReactComponent);
|
||||
}
|
||||
|
||||
// This is used to create an alternate fiber to do work on.
|
||||
// TODO: Rename to createWorkInProgressFiber or something like that.
|
||||
exports.cloneFiber = function(
|
||||
fiber: Fiber,
|
||||
priorityLevel: PriorityLevel,
|
||||
): Fiber {
|
||||
// We clone to get a work in progress. That means that this fiber is the
|
||||
// current. To make it safe to reuse that fiber later on as work in progress
|
||||
// we need to reset its work in progress flag now. We don't have an
|
||||
// opportunity to do this earlier since we don't traverse the tree when
|
||||
// the work in progress tree becomes the current tree.
|
||||
// fiber.progressedPriority = NoWork;
|
||||
// fiber.progressedChild = null;
|
||||
|
||||
// We use a double buffering pooling technique because we know that we'll only
|
||||
// ever need at most two versions of a tree. We pool the "other" unused node
|
||||
// that we're free to reuse. This is lazily created to avoid allocating extra
|
||||
// objects for things that are never updated. It also allow us to reclaim the
|
||||
// extra memory if needed.
|
||||
let alt = fiber.alternate;
|
||||
if (alt !== null) {
|
||||
// If we clone, then we do so from the "current" state. The current state
|
||||
// can't have any side-effects that are still valid so we reset just to be
|
||||
// sure.
|
||||
alt.effectTag = NoEffect;
|
||||
alt.nextEffect = null;
|
||||
alt.firstEffect = null;
|
||||
alt.lastEffect = null;
|
||||
} else {
|
||||
// This should not have an alternate already
|
||||
alt = createFiber(fiber.tag, fiber.key, fiber.internalContextTag);
|
||||
alt.type = fiber.type;
|
||||
|
||||
alt.progressedChild = fiber.progressedChild;
|
||||
alt.progressedPriority = fiber.progressedPriority;
|
||||
|
||||
alt.alternate = fiber;
|
||||
fiber.alternate = alt;
|
||||
}
|
||||
|
||||
alt.stateNode = fiber.stateNode;
|
||||
alt.child = fiber.child;
|
||||
alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
|
||||
alt.index = fiber.index; // This should always be overridden.
|
||||
alt.ref = fiber.ref;
|
||||
// pendingProps is here for symmetry but is unnecessary in practice for now.
|
||||
// TODO: Pass in the new pendingProps as an argument maybe?
|
||||
alt.pendingProps = fiber.pendingProps;
|
||||
cloneUpdateQueue(fiber, alt);
|
||||
alt.pendingWorkPriority = priorityLevel;
|
||||
|
||||
alt.memoizedProps = fiber.memoizedProps;
|
||||
alt.memoizedState = fiber.memoizedState;
|
||||
|
||||
if (__DEV__) {
|
||||
alt._debugID = fiber._debugID;
|
||||
alt._debugSource = fiber._debugSource;
|
||||
alt._debugOwner = fiber._debugOwner;
|
||||
}
|
||||
|
||||
return alt;
|
||||
};
|
||||
|
||||
exports.createHostRootFiber = function(): Fiber {
|
||||
const fiber = createFiber(HostRoot, null, NoContext);
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromElement = function(
|
||||
element: ReactElement,
|
||||
internalContextTag: TypeOfInternalContext,
|
||||
priorityLevel: PriorityLevel,
|
||||
): Fiber {
|
||||
let owner = null;
|
||||
if (__DEV__) {
|
||||
owner = element._owner;
|
||||
}
|
||||
|
||||
const fiber = createFiberFromElementType(
|
||||
element.type,
|
||||
element.key,
|
||||
internalContextTag,
|
||||
owner,
|
||||
);
|
||||
fiber.pendingProps = element.props;
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
|
||||
if (__DEV__) {
|
||||
fiber._debugSource = element._source;
|
||||
fiber._debugOwner = element._owner;
|
||||
}
|
||||
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromFragment = function(
|
||||
elements: ReactFragment,
|
||||
internalContextTag: TypeOfInternalContext,
|
||||
priorityLevel: PriorityLevel,
|
||||
): Fiber {
|
||||
// TODO: Consider supporting keyed fragments. Technically, we accidentally
|
||||
// support that in the existing React.
|
||||
const fiber = createFiber(Fragment, null, internalContextTag);
|
||||
fiber.pendingProps = elements;
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromText = function(
|
||||
content: string,
|
||||
internalContextTag: TypeOfInternalContext,
|
||||
priorityLevel: PriorityLevel,
|
||||
): Fiber {
|
||||
const fiber = createFiber(HostText, null, internalContextTag);
|
||||
fiber.pendingProps = content;
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
return fiber;
|
||||
};
|
||||
|
||||
function createFiberFromElementType(
|
||||
type: mixed,
|
||||
key: null | string,
|
||||
internalContextTag: TypeOfInternalContext,
|
||||
debugOwner: null | Fiber | ReactInstance,
|
||||
): Fiber {
|
||||
let fiber;
|
||||
if (typeof type === 'function') {
|
||||
fiber = shouldConstruct(type)
|
||||
? createFiber(ClassComponent, key, internalContextTag)
|
||||
: createFiber(IndeterminateComponent, key, internalContextTag);
|
||||
fiber.type = type;
|
||||
} else if (typeof type === 'string') {
|
||||
fiber = createFiber(HostComponent, key, internalContextTag);
|
||||
fiber.type = type;
|
||||
} else if (
|
||||
typeof type === 'object' &&
|
||||
type !== null &&
|
||||
typeof type.tag === 'number'
|
||||
) {
|
||||
// Currently assumed to be a continuation and therefore is a fiber already.
|
||||
// TODO: The yield system is currently broken for updates in some cases.
|
||||
// The reified yield stores a fiber, but we don't know which fiber that is;
|
||||
// the current or a workInProgress? When the continuation gets rendered here
|
||||
// we don't know if we can reuse that fiber or if we need to clone it.
|
||||
// There is probably a clever way to restructure this.
|
||||
fiber = ((type: any): Fiber);
|
||||
} else {
|
||||
let info = '';
|
||||
if (__DEV__) {
|
||||
if (
|
||||
type === undefined ||
|
||||
(typeof type === 'object' &&
|
||||
type !== null &&
|
||||
Object.keys(type).length === 0)
|
||||
) {
|
||||
info +=
|
||||
' You likely forgot to export your component from the file ' +
|
||||
"it's defined in.";
|
||||
}
|
||||
const ownerName = debugOwner ? getComponentName(debugOwner) : null;
|
||||
if (ownerName) {
|
||||
info += '\n\nCheck the render method of `' + ownerName + '`.';
|
||||
}
|
||||
}
|
||||
invariant(
|
||||
false,
|
||||
'Element type is invalid: expected a string (for built-in components) ' +
|
||||
'or a class/function (for composite components) but got: %s.%s',
|
||||
type == null ? type : typeof type,
|
||||
info,
|
||||
);
|
||||
}
|
||||
return fiber;
|
||||
}
|
||||
|
||||
exports.createFiberFromElementType = createFiberFromElementType;
|
||||
|
||||
exports.createFiberFromCoroutine = function(
|
||||
coroutine: ReactCoroutine,
|
||||
internalContextTag: TypeOfInternalContext,
|
||||
priorityLevel: PriorityLevel,
|
||||
): Fiber {
|
||||
const fiber = createFiber(
|
||||
CoroutineComponent,
|
||||
coroutine.key,
|
||||
internalContextTag,
|
||||
);
|
||||
fiber.type = coroutine.handler;
|
||||
fiber.pendingProps = coroutine;
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromYield = function(
|
||||
yieldNode: ReactYield,
|
||||
internalContextTag: TypeOfInternalContext,
|
||||
priorityLevel: PriorityLevel,
|
||||
): Fiber {
|
||||
const fiber = createFiber(YieldComponent, null, internalContextTag);
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromPortal = function(
|
||||
portal: ReactPortal,
|
||||
internalContextTag: TypeOfInternalContext,
|
||||
priorityLevel: PriorityLevel,
|
||||
): Fiber {
|
||||
const fiber = createFiber(HostPortal, portal.key, internalContextTag);
|
||||
fiber.pendingProps = portal.children || [];
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
fiber.stateNode = {
|
||||
containerInfo: portal.containerInfo,
|
||||
implementation: portal.implementation,
|
||||
};
|
||||
return fiber;
|
||||
};
|
@ -1,871 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberBeginWork
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ReactCoroutine} from 'ReactCoroutine';
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {HostContext} from 'ReactFiberHostContext';
|
||||
import type {FiberRoot} from 'ReactFiberRoot';
|
||||
import type {HostConfig} from 'ReactFiberReconciler';
|
||||
import type {PriorityLevel} from 'ReactPriorityLevel';
|
||||
|
||||
var {
|
||||
mountChildFibersInPlace,
|
||||
reconcileChildFibers,
|
||||
reconcileChildFibersInPlace,
|
||||
cloneChildFibers,
|
||||
} = require('ReactChildFiber');
|
||||
var {beginUpdateQueue} = require('ReactFiberUpdateQueue');
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
getMaskedContext,
|
||||
getUnmaskedContext,
|
||||
hasContextChanged,
|
||||
pushContextProvider,
|
||||
pushTopLevelContextObject,
|
||||
invalidateContextProvider,
|
||||
} = require('ReactFiberContext');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
FunctionalComponent,
|
||||
ClassComponent,
|
||||
HostRoot,
|
||||
HostComponent,
|
||||
HostText,
|
||||
HostPortal,
|
||||
CoroutineComponent,
|
||||
CoroutineHandlerPhase,
|
||||
YieldComponent,
|
||||
Fragment,
|
||||
} = ReactTypeOfWork;
|
||||
var {NoWork, OffscreenPriority} = require('ReactPriorityLevel');
|
||||
var {Placement, ContentReset, Err, Ref} = require('ReactTypeOfSideEffect');
|
||||
var ReactFiberClassComponent = require('ReactFiberClassComponent');
|
||||
var {ReactCurrentOwner} = require('ReactGlobalSharedState');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
if (__DEV__) {
|
||||
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
|
||||
var {cancelWorkTimer} = require('ReactDebugFiberPerf');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
var warnedAboutStatelessRefs = {};
|
||||
}
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
|
||||
hostContext: HostContext<C, CX>,
|
||||
scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
|
||||
getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
|
||||
) {
|
||||
const {
|
||||
shouldSetTextContent,
|
||||
useSyncScheduling,
|
||||
shouldDeprioritizeSubtree,
|
||||
} = config;
|
||||
|
||||
const {pushHostContext, pushHostContainer} = hostContext;
|
||||
|
||||
const {
|
||||
adoptClassInstance,
|
||||
constructClassInstance,
|
||||
mountClassInstance,
|
||||
resumeMountClassInstance,
|
||||
updateClassInstance,
|
||||
} = ReactFiberClassComponent(
|
||||
scheduleUpdate,
|
||||
getPriorityContext,
|
||||
memoizeProps,
|
||||
memoizeState,
|
||||
);
|
||||
|
||||
function markChildAsProgressed(current, workInProgress, priorityLevel) {
|
||||
// We now have clones. Let's store them as the currently progressed work.
|
||||
workInProgress.progressedChild = workInProgress.child;
|
||||
workInProgress.progressedPriority = priorityLevel;
|
||||
if (current !== null) {
|
||||
// We also store it on the current. When the alternate swaps in we can
|
||||
// continue from this point.
|
||||
current.progressedChild = workInProgress.progressedChild;
|
||||
current.progressedPriority = workInProgress.progressedPriority;
|
||||
}
|
||||
}
|
||||
|
||||
function clearDeletions(workInProgress) {
|
||||
workInProgress.progressedFirstDeletion = workInProgress.progressedLastDeletion = null;
|
||||
}
|
||||
|
||||
function transferDeletions(workInProgress) {
|
||||
// Any deletions get added first into the effect list.
|
||||
workInProgress.firstEffect = workInProgress.progressedFirstDeletion;
|
||||
workInProgress.lastEffect = workInProgress.progressedLastDeletion;
|
||||
}
|
||||
|
||||
function reconcileChildren(current, workInProgress, nextChildren) {
|
||||
const priorityLevel = workInProgress.pendingWorkPriority;
|
||||
reconcileChildrenAtPriority(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
priorityLevel,
|
||||
);
|
||||
}
|
||||
|
||||
function reconcileChildrenAtPriority(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
priorityLevel,
|
||||
) {
|
||||
// At this point any memoization is no longer valid since we'll have changed
|
||||
// the children.
|
||||
workInProgress.memoizedProps = null;
|
||||
if (current === null) {
|
||||
// If this is a fresh new component that hasn't been rendered yet, we
|
||||
// won't update its child set by applying minimal side-effects. Instead,
|
||||
// we will add them all to the child before it gets rendered. That means
|
||||
// we can optimize this reconciliation pass by not tracking side-effects.
|
||||
workInProgress.child = mountChildFibersInPlace(
|
||||
workInProgress,
|
||||
workInProgress.child,
|
||||
nextChildren,
|
||||
priorityLevel,
|
||||
);
|
||||
} else if (current.child === workInProgress.child) {
|
||||
// If the current child is the same as the work in progress, it means that
|
||||
// we haven't yet started any work on these children. Therefore, we use
|
||||
// the clone algorithm to create a copy of all the current children.
|
||||
|
||||
// If we had any progressed work already, that is invalid at this point so
|
||||
// let's throw it out.
|
||||
clearDeletions(workInProgress);
|
||||
|
||||
workInProgress.child = reconcileChildFibers(
|
||||
workInProgress,
|
||||
workInProgress.child,
|
||||
nextChildren,
|
||||
priorityLevel,
|
||||
);
|
||||
|
||||
transferDeletions(workInProgress);
|
||||
} else {
|
||||
// If, on the other hand, it is already using a clone, that means we've
|
||||
// already begun some work on this tree and we can continue where we left
|
||||
// off by reconciling against the existing children.
|
||||
workInProgress.child = reconcileChildFibersInPlace(
|
||||
workInProgress,
|
||||
workInProgress.child,
|
||||
nextChildren,
|
||||
priorityLevel,
|
||||
);
|
||||
|
||||
transferDeletions(workInProgress);
|
||||
}
|
||||
markChildAsProgressed(current, workInProgress, priorityLevel);
|
||||
}
|
||||
|
||||
function updateFragment(current, workInProgress) {
|
||||
var nextChildren = workInProgress.pendingProps;
|
||||
if (hasContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
if (nextChildren === null) {
|
||||
nextChildren = workInProgress.memoizedProps;
|
||||
}
|
||||
} else if (
|
||||
nextChildren === null ||
|
||||
workInProgress.memoizedProps === nextChildren
|
||||
) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
memoizeProps(workInProgress, nextChildren);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function markRef(current: Fiber | null, workInProgress: Fiber) {
|
||||
const ref = workInProgress.ref;
|
||||
if (ref !== null && (!current || current.ref !== ref)) {
|
||||
// Schedule a Ref effect
|
||||
workInProgress.effectTag |= Ref;
|
||||
}
|
||||
}
|
||||
|
||||
function updateFunctionalComponent(current, workInProgress) {
|
||||
var fn = workInProgress.type;
|
||||
var nextProps = workInProgress.pendingProps;
|
||||
|
||||
const memoizedProps = workInProgress.memoizedProps;
|
||||
if (hasContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
if (nextProps === null) {
|
||||
nextProps = memoizedProps;
|
||||
}
|
||||
} else {
|
||||
if (nextProps === null || memoizedProps === nextProps) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
// TODO: Disable this before release, since it is not part of the public API
|
||||
// I use this for testing to compare the relative overhead of classes.
|
||||
if (
|
||||
typeof fn.shouldComponentUpdate === 'function' &&
|
||||
!fn.shouldComponentUpdate(memoizedProps, nextProps)
|
||||
) {
|
||||
// Memoize props even if shouldComponentUpdate returns false
|
||||
memoizeProps(workInProgress, nextProps);
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
var unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
var context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
|
||||
var nextChildren;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
ReactDebugCurrentFiber.phase = 'render';
|
||||
nextChildren = fn(nextProps, context);
|
||||
ReactDebugCurrentFiber.phase = null;
|
||||
} else {
|
||||
nextChildren = fn(nextProps, context);
|
||||
}
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
memoizeProps(workInProgress, nextProps);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateClassComponent(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
priorityLevel: PriorityLevel,
|
||||
) {
|
||||
// Push context providers early to prevent context stack mismatches.
|
||||
// During mounting we don't know the child context yet as the instance doesn't exist.
|
||||
// We will invalidate the child context in finishClassComponent() right after rendering.
|
||||
const hasContext = pushContextProvider(workInProgress);
|
||||
|
||||
let shouldUpdate;
|
||||
if (current === null) {
|
||||
if (!workInProgress.stateNode) {
|
||||
// In the initial pass we might need to construct the instance.
|
||||
constructClassInstance(workInProgress, workInProgress.pendingProps);
|
||||
mountClassInstance(workInProgress, priorityLevel);
|
||||
shouldUpdate = true;
|
||||
} else {
|
||||
// In a resume, we'll already have an instance we can reuse.
|
||||
shouldUpdate = resumeMountClassInstance(workInProgress, priorityLevel);
|
||||
}
|
||||
} else {
|
||||
shouldUpdate = updateClassInstance(
|
||||
current,
|
||||
workInProgress,
|
||||
priorityLevel,
|
||||
);
|
||||
}
|
||||
return finishClassComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
shouldUpdate,
|
||||
hasContext,
|
||||
);
|
||||
}
|
||||
|
||||
function finishClassComponent(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
shouldUpdate: boolean,
|
||||
hasContext: boolean,
|
||||
) {
|
||||
// Refs should update even if shouldComponentUpdate returns false
|
||||
markRef(current, workInProgress);
|
||||
|
||||
if (!shouldUpdate) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
const instance = workInProgress.stateNode;
|
||||
|
||||
// Rerender
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
let nextChildren;
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFiber.phase = 'render';
|
||||
nextChildren = instance.render();
|
||||
ReactDebugCurrentFiber.phase = null;
|
||||
} else {
|
||||
nextChildren = instance.render();
|
||||
}
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
// Memoize props and state using the values we just used to render.
|
||||
// TODO: Restructure so we never read values from the instance.
|
||||
memoizeState(workInProgress, instance.state);
|
||||
memoizeProps(workInProgress, instance.props);
|
||||
|
||||
// The context might have changed so we need to recalculate it.
|
||||
if (hasContext) {
|
||||
invalidateContextProvider(workInProgress);
|
||||
}
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateHostRoot(current, workInProgress, priorityLevel) {
|
||||
const root = (workInProgress.stateNode: FiberRoot);
|
||||
if (root.pendingContext) {
|
||||
pushTopLevelContextObject(
|
||||
workInProgress,
|
||||
root.pendingContext,
|
||||
root.pendingContext !== root.context,
|
||||
);
|
||||
} else if (root.context) {
|
||||
// Should always be set
|
||||
pushTopLevelContextObject(workInProgress, root.context, false);
|
||||
}
|
||||
|
||||
pushHostContainer(workInProgress, root.containerInfo);
|
||||
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue !== null) {
|
||||
const prevState = workInProgress.memoizedState;
|
||||
const state = beginUpdateQueue(
|
||||
workInProgress,
|
||||
updateQueue,
|
||||
null,
|
||||
prevState,
|
||||
null,
|
||||
priorityLevel,
|
||||
);
|
||||
if (prevState === state) {
|
||||
// If the state is the same as before, that's a bailout because we had
|
||||
// no work matching this priority.
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
const element = state.element;
|
||||
reconcileChildren(current, workInProgress, element);
|
||||
memoizeState(workInProgress, state);
|
||||
return workInProgress.child;
|
||||
}
|
||||
// If there is no update queue, that's a bailout because the root has no props.
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
function updateHostComponent(current, workInProgress) {
|
||||
pushHostContext(workInProgress);
|
||||
|
||||
let nextProps = workInProgress.pendingProps;
|
||||
const prevProps = current !== null ? current.memoizedProps : null;
|
||||
const memoizedProps = workInProgress.memoizedProps;
|
||||
if (hasContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
if (nextProps === null) {
|
||||
nextProps = memoizedProps;
|
||||
invariant(
|
||||
nextProps !== null,
|
||||
'We should always have pending or current props. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
} else if (nextProps === null || memoizedProps === nextProps) {
|
||||
if (
|
||||
!useSyncScheduling &&
|
||||
shouldDeprioritizeSubtree(workInProgress.type, memoizedProps) &&
|
||||
workInProgress.pendingWorkPriority !== OffscreenPriority
|
||||
) {
|
||||
// This subtree still has work, but it should be deprioritized so we need
|
||||
// to bail out and not do any work yet.
|
||||
// TODO: It would be better if this tree got its correct priority set
|
||||
// during scheduleUpdate instead because otherwise we'll start a higher
|
||||
// priority reconciliation first before we can get down here. However,
|
||||
// that is a bit tricky since workInProgress and current can have
|
||||
// different "hidden" settings.
|
||||
let child = workInProgress.progressedChild;
|
||||
while (child !== null) {
|
||||
// To ensure that this subtree gets its priority reset, the children
|
||||
// need to be reset.
|
||||
child.pendingWorkPriority = OffscreenPriority;
|
||||
child = child.sibling;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
let nextChildren = nextProps.children;
|
||||
const isDirectTextChild = shouldSetTextContent(nextProps);
|
||||
|
||||
if (isDirectTextChild) {
|
||||
// We special case a direct text child of a host node. This is a common
|
||||
// case. We won't handle it as a reified child. We will instead handle
|
||||
// this in the host environment that also have access to this prop. That
|
||||
// avoids allocating another HostText fiber and traversing it.
|
||||
nextChildren = null;
|
||||
} else if (prevProps && shouldSetTextContent(prevProps)) {
|
||||
// If we're switching from a direct text child to a normal child, or to
|
||||
// empty, we need to schedule the text content to be reset.
|
||||
workInProgress.effectTag |= ContentReset;
|
||||
}
|
||||
|
||||
markRef(current, workInProgress);
|
||||
|
||||
if (
|
||||
!useSyncScheduling &&
|
||||
shouldDeprioritizeSubtree(workInProgress.type, nextProps) &&
|
||||
workInProgress.pendingWorkPriority !== OffscreenPriority
|
||||
) {
|
||||
// If this host component is hidden, we can bail out on the children.
|
||||
// We'll rerender the children later at the lower priority.
|
||||
|
||||
// It is unfortunate that we have to do the reconciliation of these
|
||||
// children already since that will add them to the tree even though
|
||||
// they are not actually done yet. If this is a large set it is also
|
||||
// confusing that this takes time to do right now instead of later.
|
||||
|
||||
if (workInProgress.progressedPriority === OffscreenPriority) {
|
||||
// If we already made some progress on the offscreen priority before,
|
||||
// then we should continue from where we left off.
|
||||
workInProgress.child = workInProgress.progressedChild;
|
||||
}
|
||||
|
||||
// Reconcile the children and stash them for later work.
|
||||
reconcileChildrenAtPriority(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
OffscreenPriority,
|
||||
);
|
||||
memoizeProps(workInProgress, nextProps);
|
||||
workInProgress.child = current !== null ? current.child : null;
|
||||
|
||||
if (current === null) {
|
||||
// If this doesn't have a current we won't track it for placement
|
||||
// effects. However, when we come back around to this we have already
|
||||
// inserted the parent which means that we'll infact need to make this a
|
||||
// placement.
|
||||
// TODO: There has to be a better solution to this problem.
|
||||
let child = workInProgress.progressedChild;
|
||||
while (child !== null) {
|
||||
child.effectTag = Placement;
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
// Abort and don't process children yet.
|
||||
return null;
|
||||
} else {
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
memoizeProps(workInProgress, nextProps);
|
||||
return workInProgress.child;
|
||||
}
|
||||
}
|
||||
|
||||
function updateHostText(current, workInProgress) {
|
||||
let nextProps = workInProgress.pendingProps;
|
||||
if (nextProps === null) {
|
||||
nextProps = workInProgress.memoizedProps;
|
||||
}
|
||||
memoizeProps(workInProgress, nextProps);
|
||||
// Nothing to do here. This is terminal. We'll do the completion step
|
||||
// immediately after.
|
||||
return null;
|
||||
}
|
||||
|
||||
function mountIndeterminateComponent(current, workInProgress, priorityLevel) {
|
||||
invariant(
|
||||
current === null,
|
||||
'An indeterminate component should never have mounted. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
var unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
var context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
|
||||
var value;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
value = fn(props, context);
|
||||
} else {
|
||||
value = fn(props, context);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof value.render === 'function'
|
||||
) {
|
||||
// Proceed under the assumption that this is a class instance
|
||||
workInProgress.tag = ClassComponent;
|
||||
|
||||
// Push context providers early to prevent context stack mismatches.
|
||||
// During mounting we don't know the child context yet as the instance doesn't exist.
|
||||
// We will invalidate the child context in finishClassComponent() right after rendering.
|
||||
const hasContext = pushContextProvider(workInProgress);
|
||||
adoptClassInstance(workInProgress, value);
|
||||
mountClassInstance(workInProgress, priorityLevel);
|
||||
return finishClassComponent(current, workInProgress, true, hasContext);
|
||||
} else {
|
||||
// Proceed under the assumption that this is a functional component
|
||||
workInProgress.tag = FunctionalComponent;
|
||||
if (__DEV__) {
|
||||
const Component = workInProgress.type;
|
||||
|
||||
if (Component) {
|
||||
warning(
|
||||
!Component.childContextTypes,
|
||||
'%s(...): childContextTypes cannot be defined on a functional component.',
|
||||
Component.displayName || Component.name || 'Component',
|
||||
);
|
||||
}
|
||||
if (workInProgress.ref !== null) {
|
||||
let info = '';
|
||||
const ownerName = ReactDebugCurrentFiber.getCurrentFiberOwnerName();
|
||||
if (ownerName) {
|
||||
info += '\n\nCheck the render method of `' + ownerName + '`.';
|
||||
}
|
||||
|
||||
let warningKey = ownerName || workInProgress._debugID || '';
|
||||
const debugSource = workInProgress._debugSource;
|
||||
if (debugSource) {
|
||||
warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
|
||||
}
|
||||
if (!warnedAboutStatelessRefs[warningKey]) {
|
||||
warnedAboutStatelessRefs[warningKey] = true;
|
||||
warning(
|
||||
false,
|
||||
'Stateless function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.%s%s',
|
||||
info,
|
||||
ReactDebugCurrentFiber.getCurrentFiberStackAddendum(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
reconcileChildren(current, workInProgress, value);
|
||||
memoizeProps(workInProgress, props);
|
||||
return workInProgress.child;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCoroutineComponent(current, workInProgress) {
|
||||
var nextCoroutine = (workInProgress.pendingProps: null | ReactCoroutine);
|
||||
if (hasContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
if (nextCoroutine === null) {
|
||||
nextCoroutine = current && current.memoizedProps;
|
||||
invariant(
|
||||
nextCoroutine !== null,
|
||||
'We should always have pending or current props. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
nextCoroutine === null ||
|
||||
workInProgress.memoizedProps === nextCoroutine
|
||||
) {
|
||||
nextCoroutine = workInProgress.memoizedProps;
|
||||
// TODO: When bailing out, we might need to return the stateNode instead
|
||||
// of the child. To check it for work.
|
||||
// return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
const nextChildren = nextCoroutine.children;
|
||||
const priorityLevel = workInProgress.pendingWorkPriority;
|
||||
|
||||
// The following is a fork of reconcileChildrenAtPriority but using
|
||||
// stateNode to store the child.
|
||||
|
||||
// At this point any memoization is no longer valid since we'll have changed
|
||||
// the children.
|
||||
workInProgress.memoizedProps = null;
|
||||
if (current === null) {
|
||||
workInProgress.stateNode = mountChildFibersInPlace(
|
||||
workInProgress,
|
||||
workInProgress.stateNode,
|
||||
nextChildren,
|
||||
priorityLevel,
|
||||
);
|
||||
} else if (current.child === workInProgress.child) {
|
||||
clearDeletions(workInProgress);
|
||||
|
||||
workInProgress.stateNode = reconcileChildFibers(
|
||||
workInProgress,
|
||||
workInProgress.stateNode,
|
||||
nextChildren,
|
||||
priorityLevel,
|
||||
);
|
||||
|
||||
transferDeletions(workInProgress);
|
||||
} else {
|
||||
workInProgress.stateNode = reconcileChildFibersInPlace(
|
||||
workInProgress,
|
||||
workInProgress.stateNode,
|
||||
nextChildren,
|
||||
priorityLevel,
|
||||
);
|
||||
|
||||
transferDeletions(workInProgress);
|
||||
}
|
||||
|
||||
memoizeProps(workInProgress, nextCoroutine);
|
||||
// This doesn't take arbitrary time so we could synchronously just begin
|
||||
// eagerly do the work of workInProgress.child as an optimization.
|
||||
return workInProgress.stateNode;
|
||||
}
|
||||
|
||||
function updatePortalComponent(current, workInProgress) {
|
||||
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
|
||||
const priorityLevel = workInProgress.pendingWorkPriority;
|
||||
let nextChildren = workInProgress.pendingProps;
|
||||
if (hasContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
if (nextChildren === null) {
|
||||
nextChildren = current && current.memoizedProps;
|
||||
invariant(
|
||||
nextChildren != null,
|
||||
'We should always have pending or current props. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
nextChildren === null ||
|
||||
workInProgress.memoizedProps === nextChildren
|
||||
) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
if (current === null) {
|
||||
// Portals are special because we don't append the children during mount
|
||||
// but at commit. Therefore we need to track insertions which the normal
|
||||
// flow doesn't do during mount. This doesn't happen at the root because
|
||||
// the root always starts with a "current" with a null child.
|
||||
// TODO: Consider unifying this with how the root works.
|
||||
workInProgress.child = reconcileChildFibersInPlace(
|
||||
workInProgress,
|
||||
workInProgress.child,
|
||||
nextChildren,
|
||||
priorityLevel,
|
||||
);
|
||||
memoizeProps(workInProgress, nextChildren);
|
||||
markChildAsProgressed(current, workInProgress, priorityLevel);
|
||||
} else {
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
memoizeProps(workInProgress, nextChildren);
|
||||
}
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
/*
|
||||
function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
|
||||
let child = firstChild;
|
||||
do {
|
||||
// Ensure that the first and last effect of the parent corresponds
|
||||
// to the children's first and last effect.
|
||||
if (!returnFiber.firstEffect) {
|
||||
returnFiber.firstEffect = child.firstEffect;
|
||||
}
|
||||
if (child.lastEffect) {
|
||||
if (returnFiber.lastEffect) {
|
||||
returnFiber.lastEffect.nextEffect = child.firstEffect;
|
||||
}
|
||||
returnFiber.lastEffect = child.lastEffect;
|
||||
}
|
||||
} while (child = child.sibling);
|
||||
}
|
||||
*/
|
||||
|
||||
function bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
workInProgress: Fiber,
|
||||
): Fiber | null {
|
||||
if (__DEV__) {
|
||||
cancelWorkTimer(workInProgress);
|
||||
}
|
||||
|
||||
const priorityLevel = workInProgress.pendingWorkPriority;
|
||||
// TODO: We should ideally be able to bail out early if the children have no
|
||||
// more work to do. However, since we don't have a separation of this
|
||||
// Fiber's priority and its children yet - we don't know without doing lots
|
||||
// of the same work we do anyway. Once we have that separation we can just
|
||||
// bail out here if the children has no more work at this priority level.
|
||||
// if (workInProgress.priorityOfChildren <= priorityLevel) {
|
||||
// // If there are side-effects in these children that have not yet been
|
||||
// // committed we need to ensure that they get properly transferred up.
|
||||
// if (current && current.child !== workInProgress.child) {
|
||||
// reuseChildrenEffects(workInProgress, child);
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
if (current && workInProgress.child === current.child) {
|
||||
// If we had any progressed work already, that is invalid at this point so
|
||||
// let's throw it out.
|
||||
clearDeletions(workInProgress);
|
||||
}
|
||||
|
||||
cloneChildFibers(current, workInProgress);
|
||||
markChildAsProgressed(current, workInProgress, priorityLevel);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function bailoutOnLowPriority(current, workInProgress) {
|
||||
if (__DEV__) {
|
||||
cancelWorkTimer(workInProgress);
|
||||
}
|
||||
|
||||
// TODO: Handle HostComponent tags here as well and call pushHostContext()?
|
||||
// See PR 8590 discussion for context
|
||||
switch (workInProgress.tag) {
|
||||
case ClassComponent:
|
||||
pushContextProvider(workInProgress);
|
||||
break;
|
||||
case HostPortal:
|
||||
pushHostContainer(
|
||||
workInProgress,
|
||||
workInProgress.stateNode.containerInfo,
|
||||
);
|
||||
break;
|
||||
}
|
||||
// TODO: What if this is currently in progress?
|
||||
// How can that happen? How is this not being cloned?
|
||||
return null;
|
||||
}
|
||||
|
||||
function memoizeProps(workInProgress: Fiber, nextProps: any) {
|
||||
workInProgress.memoizedProps = nextProps;
|
||||
// Reset the pending props
|
||||
workInProgress.pendingProps = null;
|
||||
}
|
||||
|
||||
function memoizeState(workInProgress: Fiber, nextState: any) {
|
||||
workInProgress.memoizedState = nextState;
|
||||
// Don't reset the updateQueue, in case there are pending updates. Resetting
|
||||
// is handled by beginUpdateQueue.
|
||||
}
|
||||
|
||||
function beginWork(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
priorityLevel: PriorityLevel,
|
||||
): Fiber | null {
|
||||
if (
|
||||
workInProgress.pendingWorkPriority === NoWork ||
|
||||
workInProgress.pendingWorkPriority > priorityLevel
|
||||
) {
|
||||
return bailoutOnLowPriority(current, workInProgress);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFiber.current = workInProgress;
|
||||
}
|
||||
|
||||
// If we don't bail out, we're going be recomputing our children so we need
|
||||
// to drop our effect list.
|
||||
workInProgress.firstEffect = null;
|
||||
workInProgress.lastEffect = null;
|
||||
|
||||
if (workInProgress.progressedPriority === priorityLevel) {
|
||||
// If we have progressed work on this priority level already, we can
|
||||
// proceed this that as the child.
|
||||
workInProgress.child = workInProgress.progressedChild;
|
||||
}
|
||||
|
||||
switch (workInProgress.tag) {
|
||||
case IndeterminateComponent:
|
||||
return mountIndeterminateComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
priorityLevel,
|
||||
);
|
||||
case FunctionalComponent:
|
||||
return updateFunctionalComponent(current, workInProgress);
|
||||
case ClassComponent:
|
||||
return updateClassComponent(current, workInProgress, priorityLevel);
|
||||
case HostRoot:
|
||||
return updateHostRoot(current, workInProgress, priorityLevel);
|
||||
case HostComponent:
|
||||
return updateHostComponent(current, workInProgress);
|
||||
case HostText:
|
||||
return updateHostText(current, workInProgress);
|
||||
case CoroutineHandlerPhase:
|
||||
// This is a restart. Reset the tag to the initial phase.
|
||||
workInProgress.tag = CoroutineComponent;
|
||||
// Intentionally fall through since this is now the same.
|
||||
case CoroutineComponent:
|
||||
return updateCoroutineComponent(current, workInProgress);
|
||||
case YieldComponent:
|
||||
// A yield component is just a placeholder, we can just run through the
|
||||
// next one immediately.
|
||||
return null;
|
||||
case HostPortal:
|
||||
return updatePortalComponent(current, workInProgress);
|
||||
case Fragment:
|
||||
return updateFragment(current, workInProgress);
|
||||
default:
|
||||
invariant(
|
||||
false,
|
||||
'Unknown unit of work tag. This error is likely caused by a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function beginFailedWork(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
priorityLevel: PriorityLevel,
|
||||
) {
|
||||
invariant(
|
||||
workInProgress.tag === ClassComponent || workInProgress.tag === HostRoot,
|
||||
'Invalid type of work. This error is likely caused by a bug in React. ' +
|
||||
'Please file an issue.',
|
||||
);
|
||||
|
||||
// Add an error effect so we can handle the error during the commit phase
|
||||
workInProgress.effectTag |= Err;
|
||||
|
||||
if (
|
||||
workInProgress.pendingWorkPriority === NoWork ||
|
||||
workInProgress.pendingWorkPriority > priorityLevel
|
||||
) {
|
||||
return bailoutOnLowPriority(current, workInProgress);
|
||||
}
|
||||
|
||||
// If we don't bail out, we're going be recomputing our children so we need
|
||||
// to drop our effect list.
|
||||
workInProgress.firstEffect = null;
|
||||
workInProgress.lastEffect = null;
|
||||
|
||||
// Unmount the current children as if the component rendered null
|
||||
const nextChildren = null;
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
|
||||
if (workInProgress.tag === ClassComponent) {
|
||||
const instance = workInProgress.stateNode;
|
||||
workInProgress.memoizedProps = instance.props;
|
||||
workInProgress.memoizedState = instance.state;
|
||||
workInProgress.pendingProps = null;
|
||||
}
|
||||
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
return {
|
||||
beginWork,
|
||||
beginFailedWork,
|
||||
};
|
||||
};
|
@ -1,597 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberClassComponent
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {PriorityLevel} from 'ReactPriorityLevel';
|
||||
|
||||
var {Update} = require('ReactTypeOfSideEffect');
|
||||
|
||||
var ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
var {AsyncUpdates} = require('ReactTypeOfInternalContext');
|
||||
|
||||
var {
|
||||
cacheContext,
|
||||
getMaskedContext,
|
||||
getUnmaskedContext,
|
||||
isContextConsumer,
|
||||
} = require('ReactFiberContext');
|
||||
var {
|
||||
addUpdate,
|
||||
addReplaceUpdate,
|
||||
addForceUpdate,
|
||||
beginUpdateQueue,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
var {hasContextChanged} = require('ReactFiberContext');
|
||||
var {isMounted} = require('ReactFiberTreeReflection');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
var emptyObject = require('fbjs/lib/emptyObject');
|
||||
var getComponentName = require('getComponentName');
|
||||
var shallowEqual = require('fbjs/lib/shallowEqual');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
const isArray = Array.isArray;
|
||||
|
||||
if (__DEV__) {
|
||||
var {startPhaseTimer, stopPhaseTimer} = require('ReactDebugFiberPerf');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
var warnOnInvalidCallback = function(callback: mixed, callerName: string) {
|
||||
warning(
|
||||
callback === null || typeof callback === 'function',
|
||||
'%s(...): Expected the last optional `callback` argument to be a ' +
|
||||
'function. Instead received: %s.',
|
||||
callerName,
|
||||
callback,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function(
|
||||
scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
|
||||
getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
|
||||
memoizeProps: (workInProgress: Fiber, props: any) => void,
|
||||
memoizeState: (workInProgress: Fiber, state: any) => void,
|
||||
) {
|
||||
// Class component state updater
|
||||
const updater = {
|
||||
isMounted,
|
||||
enqueueSetState(instance, partialState, callback) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const priorityLevel = getPriorityContext(fiber, false);
|
||||
callback = callback === undefined ? null : callback;
|
||||
if (__DEV__) {
|
||||
warnOnInvalidCallback(callback, 'setState');
|
||||
}
|
||||
addUpdate(fiber, partialState, callback, priorityLevel);
|
||||
scheduleUpdate(fiber, priorityLevel);
|
||||
},
|
||||
enqueueReplaceState(instance, state, callback) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const priorityLevel = getPriorityContext(fiber, false);
|
||||
callback = callback === undefined ? null : callback;
|
||||
if (__DEV__) {
|
||||
warnOnInvalidCallback(callback, 'replaceState');
|
||||
}
|
||||
addReplaceUpdate(fiber, state, callback, priorityLevel);
|
||||
scheduleUpdate(fiber, priorityLevel);
|
||||
},
|
||||
enqueueForceUpdate(instance, callback) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const priorityLevel = getPriorityContext(fiber, false);
|
||||
callback = callback === undefined ? null : callback;
|
||||
if (__DEV__) {
|
||||
warnOnInvalidCallback(callback, 'forceUpdate');
|
||||
}
|
||||
addForceUpdate(fiber, callback, priorityLevel);
|
||||
scheduleUpdate(fiber, priorityLevel);
|
||||
},
|
||||
};
|
||||
|
||||
function checkShouldComponentUpdate(
|
||||
workInProgress,
|
||||
oldProps,
|
||||
newProps,
|
||||
oldState,
|
||||
newState,
|
||||
newContext,
|
||||
) {
|
||||
if (
|
||||
oldProps === null ||
|
||||
(workInProgress.updateQueue !== null &&
|
||||
workInProgress.updateQueue.hasForceUpdate)
|
||||
) {
|
||||
// If the workInProgress already has an Update effect, return true
|
||||
return true;
|
||||
}
|
||||
|
||||
const instance = workInProgress.stateNode;
|
||||
const type = workInProgress.type;
|
||||
if (typeof instance.shouldComponentUpdate === 'function') {
|
||||
if (__DEV__) {
|
||||
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
|
||||
}
|
||||
const shouldUpdate = instance.shouldComponentUpdate(
|
||||
newProps,
|
||||
newState,
|
||||
newContext,
|
||||
);
|
||||
if (__DEV__) {
|
||||
stopPhaseTimer();
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
shouldUpdate !== undefined,
|
||||
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
|
||||
'boolean value. Make sure to return true or false.',
|
||||
getComponentName(workInProgress) || 'Unknown',
|
||||
);
|
||||
}
|
||||
|
||||
return shouldUpdate;
|
||||
}
|
||||
|
||||
if (type.prototype && type.prototype.isPureReactComponent) {
|
||||
return (
|
||||
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkClassInstance(workInProgress: Fiber) {
|
||||
const instance = workInProgress.stateNode;
|
||||
const type = workInProgress.type;
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(workInProgress);
|
||||
const renderPresent = instance.render;
|
||||
warning(
|
||||
renderPresent,
|
||||
'%s(...): No `render` method found on the returned component ' +
|
||||
'instance: you may have forgotten to define `render`.',
|
||||
name,
|
||||
);
|
||||
const noGetInitialStateOnES6 =
|
||||
!instance.getInitialState ||
|
||||
instance.getInitialState.isReactClassApproved ||
|
||||
instance.state;
|
||||
warning(
|
||||
noGetInitialStateOnES6,
|
||||
'getInitialState was defined on %s, a plain JavaScript class. ' +
|
||||
'This is only supported for classes created using React.createClass. ' +
|
||||
'Did you mean to define a state property instead?',
|
||||
name,
|
||||
);
|
||||
const noGetDefaultPropsOnES6 =
|
||||
!instance.getDefaultProps ||
|
||||
instance.getDefaultProps.isReactClassApproved;
|
||||
warning(
|
||||
noGetDefaultPropsOnES6,
|
||||
'getDefaultProps was defined on %s, a plain JavaScript class. ' +
|
||||
'This is only supported for classes created using React.createClass. ' +
|
||||
'Use a static property to define defaultProps instead.',
|
||||
name,
|
||||
);
|
||||
const noInstancePropTypes = !instance.propTypes;
|
||||
warning(
|
||||
noInstancePropTypes,
|
||||
'propTypes was defined as an instance property on %s. Use a static ' +
|
||||
'property to define propTypes instead.',
|
||||
name,
|
||||
);
|
||||
const noInstanceContextTypes = !instance.contextTypes;
|
||||
warning(
|
||||
noInstanceContextTypes,
|
||||
'contextTypes was defined as an instance property on %s. Use a static ' +
|
||||
'property to define contextTypes instead.',
|
||||
name,
|
||||
);
|
||||
const noComponentShouldUpdate =
|
||||
typeof instance.componentShouldUpdate !== 'function';
|
||||
warning(
|
||||
noComponentShouldUpdate,
|
||||
'%s has a method called ' +
|
||||
'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
|
||||
'The name is phrased as a question because the function is ' +
|
||||
'expected to return a value.',
|
||||
name,
|
||||
);
|
||||
if (
|
||||
type.prototype &&
|
||||
type.prototype.isPureReactComponent &&
|
||||
typeof instance.shouldComponentUpdate !== 'undefined'
|
||||
) {
|
||||
warning(
|
||||
false,
|
||||
'%s has a method called shouldComponentUpdate(). ' +
|
||||
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
|
||||
'Please extend React.Component if shouldComponentUpdate is used.',
|
||||
getComponentName(workInProgress) || 'A pure component',
|
||||
);
|
||||
}
|
||||
const noComponentDidUnmount =
|
||||
typeof instance.componentDidUnmount !== 'function';
|
||||
warning(
|
||||
noComponentDidUnmount,
|
||||
'%s has a method called ' +
|
||||
'componentDidUnmount(). But there is no such lifecycle method. ' +
|
||||
'Did you mean componentWillUnmount()?',
|
||||
name,
|
||||
);
|
||||
const noComponentWillRecieveProps =
|
||||
typeof instance.componentWillRecieveProps !== 'function';
|
||||
warning(
|
||||
noComponentWillRecieveProps,
|
||||
'%s has a method called ' +
|
||||
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
|
||||
name,
|
||||
);
|
||||
const hasMutatedProps = instance.props !== workInProgress.pendingProps;
|
||||
warning(
|
||||
instance.props === undefined || !hasMutatedProps,
|
||||
'%s(...): When calling super() in `%s`, make sure to pass ' +
|
||||
"up the same props that your component's constructor was passed.",
|
||||
name,
|
||||
name,
|
||||
);
|
||||
const noInstanceDefaultProps = !instance.defaultProps;
|
||||
warning(
|
||||
noInstanceDefaultProps,
|
||||
'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
|
||||
' Instead, define defaultProps as a static property on %s.',
|
||||
name,
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
const state = instance.state;
|
||||
if (state && (typeof state !== 'object' || isArray(state))) {
|
||||
invariant(
|
||||
false,
|
||||
'%s.state: must be set to an object or null',
|
||||
getComponentName(workInProgress),
|
||||
);
|
||||
}
|
||||
if (typeof instance.getChildContext === 'function') {
|
||||
invariant(
|
||||
typeof workInProgress.type.childContextTypes === 'object',
|
||||
'%s.getChildContext(): childContextTypes must be defined in order to ' +
|
||||
'use getChildContext().',
|
||||
getComponentName(workInProgress),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function resetInputPointers(workInProgress: Fiber, instance: any) {
|
||||
instance.props = workInProgress.memoizedProps;
|
||||
instance.state = workInProgress.memoizedState;
|
||||
}
|
||||
|
||||
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
|
||||
instance.updater = updater;
|
||||
workInProgress.stateNode = instance;
|
||||
// The instance needs access to the fiber so that it can schedule updates
|
||||
ReactInstanceMap.set(instance, workInProgress);
|
||||
}
|
||||
|
||||
function constructClassInstance(workInProgress: Fiber, props: any): any {
|
||||
const ctor = workInProgress.type;
|
||||
const unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const needsContext = isContextConsumer(workInProgress);
|
||||
const context = needsContext
|
||||
? getMaskedContext(workInProgress, unmaskedContext)
|
||||
: emptyObject;
|
||||
const instance = new ctor(props, context);
|
||||
adoptClassInstance(workInProgress, instance);
|
||||
|
||||
// Cache unmasked context so we can avoid recreating masked context unless necessary.
|
||||
// ReactFiberContext usually updates this cache but can't for newly-created instances.
|
||||
if (needsContext) {
|
||||
cacheContext(workInProgress, unmaskedContext, context);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Invokes the mount life-cycles on a previously never rendered instance.
|
||||
function mountClassInstance(
|
||||
workInProgress: Fiber,
|
||||
priorityLevel: PriorityLevel,
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
checkClassInstance(workInProgress);
|
||||
}
|
||||
|
||||
const instance = workInProgress.stateNode;
|
||||
const state = instance.state || null;
|
||||
|
||||
let props = workInProgress.pendingProps;
|
||||
invariant(
|
||||
props,
|
||||
'There must be pending props for an initial mount. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
|
||||
const unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
|
||||
instance.props = props;
|
||||
instance.state = state;
|
||||
instance.refs = emptyObject;
|
||||
instance.context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
|
||||
if (
|
||||
ReactFeatureFlags.enableAsyncSubtreeAPI &&
|
||||
workInProgress.type != null &&
|
||||
workInProgress.type.unstable_asyncUpdates === true
|
||||
) {
|
||||
workInProgress.internalContextTag |= AsyncUpdates;
|
||||
}
|
||||
|
||||
if (typeof instance.componentWillMount === 'function') {
|
||||
if (__DEV__) {
|
||||
startPhaseTimer(workInProgress, 'componentWillMount');
|
||||
}
|
||||
instance.componentWillMount();
|
||||
if (__DEV__) {
|
||||
stopPhaseTimer();
|
||||
}
|
||||
// If we had additional state updates during this life-cycle, let's
|
||||
// process them now.
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue !== null) {
|
||||
instance.state = beginUpdateQueue(
|
||||
workInProgress,
|
||||
updateQueue,
|
||||
instance,
|
||||
state,
|
||||
props,
|
||||
priorityLevel,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
}
|
||||
|
||||
// Called on a preexisting class instance. Returns false if a resumed render
|
||||
// could be reused.
|
||||
function resumeMountClassInstance(
|
||||
workInProgress: Fiber,
|
||||
priorityLevel: PriorityLevel,
|
||||
): boolean {
|
||||
const instance = workInProgress.stateNode;
|
||||
resetInputPointers(workInProgress, instance);
|
||||
|
||||
let newState = workInProgress.memoizedState;
|
||||
let newProps = workInProgress.pendingProps;
|
||||
if (!newProps) {
|
||||
// If there isn't any new props, then we'll reuse the memoized props.
|
||||
// This could be from already completed work.
|
||||
newProps = workInProgress.memoizedProps;
|
||||
invariant(
|
||||
newProps != null,
|
||||
'There should always be pending or memoized props. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
const newUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
|
||||
|
||||
// TODO: Should we deal with a setState that happened after the last
|
||||
// componentWillMount and before this componentWillMount? Probably
|
||||
// unsupported anyway.
|
||||
|
||||
if (
|
||||
!checkShouldComponentUpdate(
|
||||
workInProgress,
|
||||
workInProgress.memoizedProps,
|
||||
newProps,
|
||||
workInProgress.memoizedState,
|
||||
newState,
|
||||
newContext,
|
||||
)
|
||||
) {
|
||||
// Update the existing instance's state, props, and context pointers even
|
||||
// though we're bailing out.
|
||||
instance.props = newProps;
|
||||
instance.state = newState;
|
||||
instance.context = newContext;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we didn't bail out we need to construct a new instance. We don't
|
||||
// want to reuse one that failed to fully mount.
|
||||
const newInstance = constructClassInstance(workInProgress, newProps);
|
||||
newInstance.props = newProps;
|
||||
newInstance.state = newState = newInstance.state || null;
|
||||
newInstance.context = newContext;
|
||||
|
||||
if (typeof newInstance.componentWillMount === 'function') {
|
||||
if (__DEV__) {
|
||||
startPhaseTimer(workInProgress, 'componentWillMount');
|
||||
}
|
||||
newInstance.componentWillMount();
|
||||
if (__DEV__) {
|
||||
stopPhaseTimer();
|
||||
}
|
||||
}
|
||||
// If we had additional state updates, process them now.
|
||||
// They may be from componentWillMount() or from error boundary's setState()
|
||||
// during initial mounting.
|
||||
const newUpdateQueue = workInProgress.updateQueue;
|
||||
if (newUpdateQueue !== null) {
|
||||
newInstance.state = beginUpdateQueue(
|
||||
workInProgress,
|
||||
newUpdateQueue,
|
||||
newInstance,
|
||||
newState,
|
||||
newProps,
|
||||
priorityLevel,
|
||||
);
|
||||
}
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Invokes the update life-cycles and returns false if it shouldn't rerender.
|
||||
function updateClassInstance(
|
||||
current: Fiber,
|
||||
workInProgress: Fiber,
|
||||
priorityLevel: PriorityLevel,
|
||||
): boolean {
|
||||
const instance = workInProgress.stateNode;
|
||||
resetInputPointers(workInProgress, instance);
|
||||
|
||||
const oldProps = workInProgress.memoizedProps;
|
||||
let newProps = workInProgress.pendingProps;
|
||||
if (!newProps) {
|
||||
// If there aren't any new props, then we'll reuse the memoized props.
|
||||
// This could be from already completed work.
|
||||
newProps = oldProps;
|
||||
invariant(
|
||||
newProps != null,
|
||||
'There should always be pending or memoized props. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
const oldContext = instance.context;
|
||||
const newUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
|
||||
|
||||
// Note: During these life-cycles, instance.props/instance.state are what
|
||||
// ever the previously attempted to render - not the "current". However,
|
||||
// during componentDidUpdate we pass the "current" props.
|
||||
|
||||
if (oldProps !== newProps || oldContext !== newContext) {
|
||||
if (typeof instance.componentWillReceiveProps === 'function') {
|
||||
if (__DEV__) {
|
||||
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
|
||||
}
|
||||
instance.componentWillReceiveProps(newProps, newContext);
|
||||
if (__DEV__) {
|
||||
stopPhaseTimer();
|
||||
}
|
||||
|
||||
if (instance.state !== workInProgress.memoizedState) {
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
false,
|
||||
'%s.componentWillReceiveProps(): Assigning directly to ' +
|
||||
"this.state is deprecated (except inside a component's " +
|
||||
'constructor). Use setState instead.',
|
||||
getComponentName(workInProgress),
|
||||
);
|
||||
}
|
||||
updater.enqueueReplaceState(instance, instance.state, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the next state using the memoized state and the update queue.
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
const oldState = workInProgress.memoizedState;
|
||||
// TODO: Previous state can be null.
|
||||
let newState;
|
||||
if (updateQueue !== null) {
|
||||
newState = beginUpdateQueue(
|
||||
workInProgress,
|
||||
updateQueue,
|
||||
instance,
|
||||
oldState,
|
||||
newProps,
|
||||
priorityLevel,
|
||||
);
|
||||
} else {
|
||||
newState = oldState;
|
||||
}
|
||||
|
||||
if (
|
||||
oldProps === newProps &&
|
||||
oldState === newState &&
|
||||
!hasContextChanged() &&
|
||||
!(updateQueue !== null && updateQueue.hasForceUpdate)
|
||||
) {
|
||||
// If an update was already in progress, we should schedule an Update
|
||||
// effect even though we're bailing out, so that cWU/cDU are called.
|
||||
if (typeof instance.componentDidUpdate === 'function') {
|
||||
if (
|
||||
oldProps !== current.memoizedProps ||
|
||||
oldState !== current.memoizedState
|
||||
) {
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const shouldUpdate = checkShouldComponentUpdate(
|
||||
workInProgress,
|
||||
oldProps,
|
||||
newProps,
|
||||
oldState,
|
||||
newState,
|
||||
newContext,
|
||||
);
|
||||
|
||||
if (shouldUpdate) {
|
||||
if (typeof instance.componentWillUpdate === 'function') {
|
||||
if (__DEV__) {
|
||||
startPhaseTimer(workInProgress, 'componentWillUpdate');
|
||||
}
|
||||
instance.componentWillUpdate(newProps, newState, newContext);
|
||||
if (__DEV__) {
|
||||
stopPhaseTimer();
|
||||
}
|
||||
}
|
||||
if (typeof instance.componentDidUpdate === 'function') {
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
} else {
|
||||
// If an update was already in progress, we should schedule an Update
|
||||
// effect even though we're bailing out, so that cWU/cDU are called.
|
||||
if (typeof instance.componentDidUpdate === 'function') {
|
||||
if (
|
||||
oldProps !== current.memoizedProps ||
|
||||
oldState !== current.memoizedState
|
||||
) {
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
}
|
||||
|
||||
// If shouldComponentUpdate returned false, we should still update the
|
||||
// memoized props/state to indicate that this work can be reused.
|
||||
memoizeProps(workInProgress, newProps);
|
||||
memoizeState(workInProgress, newState);
|
||||
}
|
||||
|
||||
// Update the existing instance's state, props, and context pointers even
|
||||
// if shouldComponentUpdate returns false.
|
||||
instance.props = newProps;
|
||||
instance.state = newState;
|
||||
instance.context = newContext;
|
||||
|
||||
return shouldUpdate;
|
||||
}
|
||||
|
||||
return {
|
||||
adoptClassInstance,
|
||||
constructClassInstance,
|
||||
mountClassInstance,
|
||||
resumeMountClassInstance,
|
||||
updateClassInstance,
|
||||
};
|
||||
};
|
@ -1,541 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberCommitWork
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {HostConfig} from 'ReactFiberReconciler';
|
||||
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
ClassComponent,
|
||||
HostRoot,
|
||||
HostComponent,
|
||||
HostText,
|
||||
HostPortal,
|
||||
CoroutineComponent,
|
||||
} = ReactTypeOfWork;
|
||||
var {commitCallbacks} = require('ReactFiberUpdateQueue');
|
||||
var {onCommitUnmount} = require('ReactFiberDevToolsHook');
|
||||
var {invokeGuardedCallback} = require('ReactErrorUtils');
|
||||
|
||||
var {
|
||||
Placement,
|
||||
Update,
|
||||
Callback,
|
||||
ContentReset,
|
||||
} = require('ReactTypeOfSideEffect');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
if (__DEV__) {
|
||||
var {startPhaseTimer, stopPhaseTimer} = require('ReactDebugFiberPerf');
|
||||
}
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
|
||||
captureError: (failedFiber: Fiber, error: Error) => Fiber | null,
|
||||
) {
|
||||
const {
|
||||
commitMount,
|
||||
commitUpdate,
|
||||
resetTextContent,
|
||||
commitTextUpdate,
|
||||
appendChild,
|
||||
insertBefore,
|
||||
removeChild,
|
||||
getPublicInstance,
|
||||
} = config;
|
||||
|
||||
if (__DEV__) {
|
||||
var callComponentWillUnmountWithTimerInDev = function(current, instance) {
|
||||
startPhaseTimer(current, 'componentWillUnmount');
|
||||
instance.componentWillUnmount();
|
||||
stopPhaseTimer();
|
||||
};
|
||||
}
|
||||
|
||||
// Capture errors so they don't interrupt unmounting.
|
||||
function safelyCallComponentWillUnmount(current, instance) {
|
||||
if (__DEV__) {
|
||||
const unmountError = invokeGuardedCallback(
|
||||
null,
|
||||
callComponentWillUnmountWithTimerInDev,
|
||||
null,
|
||||
current,
|
||||
instance,
|
||||
);
|
||||
if (unmountError) {
|
||||
captureError(current, unmountError);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
instance.componentWillUnmount();
|
||||
} catch (unmountError) {
|
||||
captureError(current, unmountError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function safelyDetachRef(current: Fiber) {
|
||||
const ref = current.ref;
|
||||
if (ref !== null) {
|
||||
if (__DEV__) {
|
||||
const refError = invokeGuardedCallback(null, ref, null, null);
|
||||
if (refError !== null) {
|
||||
captureError(current, refError);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
ref(null);
|
||||
} catch (refError) {
|
||||
captureError(current, refError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getHostParent(fiber: Fiber): I | C {
|
||||
let parent = fiber.return;
|
||||
while (parent !== null) {
|
||||
switch (parent.tag) {
|
||||
case HostComponent:
|
||||
return parent.stateNode;
|
||||
case HostRoot:
|
||||
return parent.stateNode.containerInfo;
|
||||
case HostPortal:
|
||||
return parent.stateNode.containerInfo;
|
||||
}
|
||||
parent = parent.return;
|
||||
}
|
||||
invariant(
|
||||
false,
|
||||
'Expected to find a host parent. This error is likely caused by a bug ' +
|
||||
'in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
function getHostParentFiber(fiber: Fiber): Fiber {
|
||||
let parent = fiber.return;
|
||||
while (parent !== null) {
|
||||
if (isHostParent(parent)) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.return;
|
||||
}
|
||||
invariant(
|
||||
false,
|
||||
'Expected to find a host parent. This error is likely caused by a bug ' +
|
||||
'in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
function isHostParent(fiber: Fiber): boolean {
|
||||
return (
|
||||
fiber.tag === HostComponent ||
|
||||
fiber.tag === HostRoot ||
|
||||
fiber.tag === HostPortal
|
||||
);
|
||||
}
|
||||
|
||||
function getHostSibling(fiber: Fiber): ?I {
|
||||
// We're going to search forward into the tree until we find a sibling host
|
||||
// node. Unfortunately, if multiple insertions are done in a row we have to
|
||||
// search past them. This leads to exponential search for the next sibling.
|
||||
// TODO: Find a more efficient way to do this.
|
||||
let node: Fiber = fiber;
|
||||
siblings: while (true) {
|
||||
// If we didn't find anything, let's try the next sibling.
|
||||
while (node.sibling === null) {
|
||||
if (node.return === null || isHostParent(node.return)) {
|
||||
// If we pop out of the root or hit the parent the fiber we are the
|
||||
// last sibling.
|
||||
return null;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
while (node.tag !== HostComponent && node.tag !== HostText) {
|
||||
// If it is not host node and, we might have a host node inside it.
|
||||
// Try to search down until we find one.
|
||||
if (node.effectTag & Placement) {
|
||||
// If we don't have a child, try the siblings instead.
|
||||
continue siblings;
|
||||
}
|
||||
// If we don't have a child, try the siblings instead.
|
||||
// We also skip portals because they are not part of this host tree.
|
||||
if (node.child === null || node.tag === HostPortal) {
|
||||
continue siblings;
|
||||
} else {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
}
|
||||
}
|
||||
// Check if this host node is stable or about to be placed.
|
||||
if (!(node.effectTag & Placement)) {
|
||||
// Found it!
|
||||
return node.stateNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commitPlacement(finishedWork: Fiber): void {
|
||||
// Recursively insert all host nodes into the parent.
|
||||
const parentFiber = getHostParentFiber(finishedWork);
|
||||
let parent;
|
||||
switch (parentFiber.tag) {
|
||||
case HostComponent:
|
||||
parent = parentFiber.stateNode;
|
||||
break;
|
||||
case HostRoot:
|
||||
parent = parentFiber.stateNode.containerInfo;
|
||||
break;
|
||||
case HostPortal:
|
||||
parent = parentFiber.stateNode.containerInfo;
|
||||
break;
|
||||
default:
|
||||
invariant(
|
||||
false,
|
||||
'Invalid host parent fiber. This error is likely caused by a bug ' +
|
||||
'in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
if (parentFiber.effectTag & ContentReset) {
|
||||
// Reset the text content of the parent before doing any insertions
|
||||
resetTextContent(parent);
|
||||
// Clear ContentReset from the effect tag
|
||||
parentFiber.effectTag &= ~ContentReset;
|
||||
}
|
||||
|
||||
const before = getHostSibling(finishedWork);
|
||||
// We only have the top Fiber that was inserted but we need recurse down its
|
||||
// children to find all the terminal nodes.
|
||||
let node: Fiber = finishedWork;
|
||||
while (true) {
|
||||
if (node.tag === HostComponent || node.tag === HostText) {
|
||||
if (before) {
|
||||
insertBefore(parent, node.stateNode, before);
|
||||
} else {
|
||||
appendChild(parent, node.stateNode);
|
||||
}
|
||||
} else if (node.tag === HostPortal) {
|
||||
// If the insertion itself is a portal, 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 === finishedWork) {
|
||||
return;
|
||||
}
|
||||
while (node.sibling === null) {
|
||||
if (node.return === null || node.return === finishedWork) {
|
||||
return;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function commitNestedUnmounts(root: Fiber): void {
|
||||
// While we're inside a removed host node we don't want to call
|
||||
// removeChild on the inner nodes because they're removed by the top
|
||||
// call anyway. We also want to call componentWillUnmount on all
|
||||
// composites before this host node is removed from the tree. Therefore
|
||||
// we do an inner loop while we're still inside the host node.
|
||||
let node: Fiber = root;
|
||||
while (true) {
|
||||
commitUnmount(node);
|
||||
// Visit children because they may contain more composite or host nodes.
|
||||
// Skip portals because commitUnmount() currently visits them recursively.
|
||||
if (node.child !== null && node.tag !== HostPortal) {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
continue;
|
||||
}
|
||||
if (node === root) {
|
||||
return;
|
||||
}
|
||||
while (node.sibling === null) {
|
||||
if (node.return === null || node.return === root) {
|
||||
return;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function unmountHostComponents(parent, current): void {
|
||||
// We only have the top Fiber that was inserted but we need recurse down its
|
||||
// children to find all the terminal nodes.
|
||||
let node: Fiber = current;
|
||||
while (true) {
|
||||
if (node.tag === HostComponent || node.tag === HostText) {
|
||||
commitNestedUnmounts(node);
|
||||
// After all the children have unmounted, it is now safe to remove the
|
||||
// node from the tree.
|
||||
removeChild(parent, node.stateNode);
|
||||
// Don't visit children because we already visited them.
|
||||
} else if (node.tag === HostPortal) {
|
||||
// When we go into a portal, it becomes the parent to remove from.
|
||||
// We will reassign it back when we pop the portal on the way up.
|
||||
parent = node.stateNode.containerInfo;
|
||||
// Visit children because portals might contain host components.
|
||||
if (node.child !== null) {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
commitUnmount(node);
|
||||
// Visit children because we may find more host components below.
|
||||
if (node.child !== null) {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (node === current) {
|
||||
return;
|
||||
}
|
||||
while (node.sibling === null) {
|
||||
if (node.return === null || node.return === current) {
|
||||
return;
|
||||
}
|
||||
node = node.return;
|
||||
if (node.tag === HostPortal) {
|
||||
// When we go out of the portal, we need to restore the parent.
|
||||
// Since we don't keep a stack of them, we will search for it.
|
||||
parent = getHostParent(node);
|
||||
}
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function commitDeletion(current: Fiber): void {
|
||||
// Recursively delete all host nodes from the parent.
|
||||
const parent = getHostParent(current);
|
||||
// Detach refs and call componentWillUnmount() on the whole subtree.
|
||||
unmountHostComponents(parent, current);
|
||||
|
||||
// Cut off the return pointers to disconnect it from the tree. Ideally, we
|
||||
// should clear the child pointer of the parent alternate to let this
|
||||
// get GC:ed but we don't know which for sure which parent is the current
|
||||
// one so we'll settle for GC:ing the subtree of this child. This child
|
||||
// itself will be GC:ed when the parent updates the next time.
|
||||
current.return = null;
|
||||
current.child = null;
|
||||
if (current.alternate) {
|
||||
current.alternate.child = null;
|
||||
current.alternate.return = null;
|
||||
}
|
||||
}
|
||||
|
||||
// User-originating errors (lifecycles and refs) should not interrupt
|
||||
// deletion, so don't let them throw. Host-originating errors should
|
||||
// interrupt deletion, so it's okay
|
||||
function commitUnmount(current: Fiber): void {
|
||||
if (typeof onCommitUnmount === 'function') {
|
||||
onCommitUnmount(current);
|
||||
}
|
||||
|
||||
switch (current.tag) {
|
||||
case ClassComponent: {
|
||||
safelyDetachRef(current);
|
||||
const instance = current.stateNode;
|
||||
if (typeof instance.componentWillUnmount === 'function') {
|
||||
safelyCallComponentWillUnmount(current, instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HostComponent: {
|
||||
safelyDetachRef(current);
|
||||
return;
|
||||
}
|
||||
case CoroutineComponent: {
|
||||
commitNestedUnmounts(current.stateNode);
|
||||
return;
|
||||
}
|
||||
case HostPortal: {
|
||||
// TODO: this is recursive.
|
||||
// We are also not using this parent because
|
||||
// the portal will get pushed immediately.
|
||||
const parent = getHostParent(current);
|
||||
unmountHostComponents(parent, current);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
switch (finishedWork.tag) {
|
||||
case ClassComponent: {
|
||||
return;
|
||||
}
|
||||
case HostComponent: {
|
||||
const instance: I = finishedWork.stateNode;
|
||||
if (instance != null && current !== null) {
|
||||
// Commit the work prepared earlier.
|
||||
const newProps = finishedWork.memoizedProps;
|
||||
const oldProps = current.memoizedProps;
|
||||
const type = finishedWork.type;
|
||||
// TODO: Type the updateQueue to be specific to host components.
|
||||
const updatePayload: null | PL = (finishedWork.updateQueue: any);
|
||||
finishedWork.updateQueue = null;
|
||||
if (updatePayload !== null) {
|
||||
commitUpdate(
|
||||
instance,
|
||||
updatePayload,
|
||||
type,
|
||||
oldProps,
|
||||
newProps,
|
||||
finishedWork,
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HostText: {
|
||||
invariant(
|
||||
finishedWork.stateNode !== null && current !== null,
|
||||
'This should only be done during updates. This error is likely ' +
|
||||
'caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
const textInstance: TI = finishedWork.stateNode;
|
||||
const newText: string = finishedWork.memoizedProps;
|
||||
const oldText: string = current.memoizedProps;
|
||||
commitTextUpdate(textInstance, oldText, newText);
|
||||
return;
|
||||
}
|
||||
case HostRoot: {
|
||||
return;
|
||||
}
|
||||
case HostPortal: {
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
invariant(
|
||||
false,
|
||||
'This unit of work tag should not have side-effects. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commitLifeCycles(current: Fiber | null, finishedWork: Fiber): void {
|
||||
switch (finishedWork.tag) {
|
||||
case ClassComponent: {
|
||||
const instance = finishedWork.stateNode;
|
||||
if (finishedWork.effectTag & Update) {
|
||||
if (current === null) {
|
||||
if (__DEV__) {
|
||||
startPhaseTimer(finishedWork, 'componentDidMount');
|
||||
}
|
||||
instance.componentDidMount();
|
||||
if (__DEV__) {
|
||||
stopPhaseTimer();
|
||||
}
|
||||
} else {
|
||||
const prevProps = current.memoizedProps;
|
||||
const prevState = current.memoizedState;
|
||||
if (__DEV__) {
|
||||
startPhaseTimer(finishedWork, 'componentDidUpdate');
|
||||
}
|
||||
instance.componentDidUpdate(prevProps, prevState);
|
||||
if (__DEV__) {
|
||||
stopPhaseTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
finishedWork.effectTag & Callback &&
|
||||
finishedWork.updateQueue !== null
|
||||
) {
|
||||
commitCallbacks(finishedWork, finishedWork.updateQueue, instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HostRoot: {
|
||||
const updateQueue = finishedWork.updateQueue;
|
||||
if (updateQueue !== null) {
|
||||
const instance = finishedWork.child && finishedWork.child.stateNode;
|
||||
commitCallbacks(finishedWork, updateQueue, instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HostComponent: {
|
||||
const instance: I = finishedWork.stateNode;
|
||||
|
||||
// Renderers may schedule work to be done after host components are mounted
|
||||
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
|
||||
// These effects should only be committed when components are first mounted,
|
||||
// aka when there is no current/alternate.
|
||||
if (current === null && finishedWork.effectTag & Update) {
|
||||
const type = finishedWork.type;
|
||||
const props = finishedWork.memoizedProps;
|
||||
commitMount(instance, type, props, finishedWork);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case HostText: {
|
||||
// We have no life-cycles associated with text.
|
||||
return;
|
||||
}
|
||||
case HostPortal: {
|
||||
// We have no life-cycles associated with portals.
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
invariant(
|
||||
false,
|
||||
'This unit of work tag should not have side-effects. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commitAttachRef(finishedWork: Fiber) {
|
||||
const ref = finishedWork.ref;
|
||||
if (ref !== null) {
|
||||
const instance = getPublicInstance(finishedWork.stateNode);
|
||||
ref(instance);
|
||||
}
|
||||
}
|
||||
|
||||
function commitDetachRef(current: Fiber) {
|
||||
const currentRef = current.ref;
|
||||
if (currentRef !== null) {
|
||||
currentRef(null);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
commitPlacement,
|
||||
commitDeletion,
|
||||
commitWork,
|
||||
commitLifeCycles,
|
||||
commitAttachRef,
|
||||
commitDetachRef,
|
||||
};
|
||||
};
|
@ -1,361 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberCompleteWork
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ReactCoroutine} from 'ReactCoroutine';
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {HostContext} from 'ReactFiberHostContext';
|
||||
import type {FiberRoot} from 'ReactFiberRoot';
|
||||
import type {HostConfig} from 'ReactFiberReconciler';
|
||||
|
||||
var {reconcileChildFibers} = require('ReactChildFiber');
|
||||
var {popContextProvider} = require('ReactFiberContext');
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
FunctionalComponent,
|
||||
ClassComponent,
|
||||
HostRoot,
|
||||
HostComponent,
|
||||
HostText,
|
||||
HostPortal,
|
||||
CoroutineComponent,
|
||||
CoroutineHandlerPhase,
|
||||
YieldComponent,
|
||||
Fragment,
|
||||
} = ReactTypeOfWork;
|
||||
var {Ref, Update} = ReactTypeOfSideEffect;
|
||||
|
||||
if (__DEV__) {
|
||||
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
|
||||
}
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
|
||||
hostContext: HostContext<C, CX>,
|
||||
) {
|
||||
const {
|
||||
createInstance,
|
||||
createTextInstance,
|
||||
appendInitialChild,
|
||||
finalizeInitialChildren,
|
||||
prepareUpdate,
|
||||
} = config;
|
||||
|
||||
const {
|
||||
getRootHostContainer,
|
||||
popHostContext,
|
||||
getHostContext,
|
||||
popHostContainer,
|
||||
} = hostContext;
|
||||
|
||||
function markChildAsProgressed(current, workInProgress, priorityLevel) {
|
||||
// We now have clones. Let's store them as the currently progressed work.
|
||||
workInProgress.progressedChild = workInProgress.child;
|
||||
workInProgress.progressedPriority = priorityLevel;
|
||||
if (current !== null) {
|
||||
// We also store it on the current. When the alternate swaps in we can
|
||||
// continue from this point.
|
||||
current.progressedChild = workInProgress.progressedChild;
|
||||
current.progressedPriority = workInProgress.progressedPriority;
|
||||
}
|
||||
}
|
||||
|
||||
function markUpdate(workInProgress: Fiber) {
|
||||
// Tag the fiber with an update effect. This turns a Placement into
|
||||
// an UpdateAndPlacement.
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
|
||||
function markRef(workInProgress: Fiber) {
|
||||
workInProgress.effectTag |= Ref;
|
||||
}
|
||||
|
||||
function appendAllYields(yields: Array<mixed>, workInProgress: Fiber) {
|
||||
let node = workInProgress.stateNode;
|
||||
if (node) {
|
||||
node.return = workInProgress;
|
||||
}
|
||||
while (node !== null) {
|
||||
if (
|
||||
node.tag === HostComponent ||
|
||||
node.tag === HostText ||
|
||||
node.tag === HostPortal
|
||||
) {
|
||||
invariant(false, 'A coroutine cannot have host component children.');
|
||||
} else if (node.tag === YieldComponent) {
|
||||
yields.push(node.type);
|
||||
} else if (node.child !== null) {
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
continue;
|
||||
}
|
||||
while (node.sibling === null) {
|
||||
if (node.return === null || node.return === workInProgress) {
|
||||
return;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function moveCoroutineToHandlerPhase(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
) {
|
||||
var coroutine = (workInProgress.memoizedProps: ?ReactCoroutine);
|
||||
invariant(
|
||||
coroutine,
|
||||
'Should be resolved by now. This error is likely caused by a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
|
||||
// First step of the coroutine has completed. Now we need to do the second.
|
||||
// TODO: It would be nice to have a multi stage coroutine represented by a
|
||||
// single component, or at least tail call optimize nested ones. Currently
|
||||
// that requires additional fields that we don't want to add to the fiber.
|
||||
// So this requires nested handlers.
|
||||
// Note: This doesn't mutate the alternate node. I don't think it needs to
|
||||
// since this stage is reset for every pass.
|
||||
workInProgress.tag = CoroutineHandlerPhase;
|
||||
|
||||
// Build up the yields.
|
||||
// TODO: Compare this to a generator or opaque helpers like Children.
|
||||
var yields: Array<mixed> = [];
|
||||
appendAllYields(yields, workInProgress);
|
||||
var fn = coroutine.handler;
|
||||
var props = coroutine.props;
|
||||
var nextChildren = fn(props, yields);
|
||||
|
||||
var currentFirstChild = current !== null ? current.child : null;
|
||||
// Inherit the priority of the returnFiber.
|
||||
const priority = workInProgress.pendingWorkPriority;
|
||||
workInProgress.child = reconcileChildFibers(
|
||||
workInProgress,
|
||||
currentFirstChild,
|
||||
nextChildren,
|
||||
priority,
|
||||
);
|
||||
markChildAsProgressed(current, workInProgress, priority);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function appendAllChildren(parent: I, 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 = node.child;
|
||||
continue;
|
||||
}
|
||||
if (node === workInProgress) {
|
||||
return;
|
||||
}
|
||||
while (node.sibling === null) {
|
||||
if (node.return === null || node.return === workInProgress) {
|
||||
return;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node = node.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function completeWork(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
): Fiber | null {
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFiber.current = workInProgress;
|
||||
}
|
||||
|
||||
switch (workInProgress.tag) {
|
||||
case FunctionalComponent:
|
||||
return null;
|
||||
case ClassComponent: {
|
||||
// We are leaving this subtree, so pop context if any.
|
||||
popContextProvider(workInProgress);
|
||||
return null;
|
||||
}
|
||||
case HostRoot: {
|
||||
// TODO: Pop the host container after #8607 lands.
|
||||
const fiberRoot = (workInProgress.stateNode: FiberRoot);
|
||||
if (fiberRoot.pendingContext) {
|
||||
fiberRoot.context = fiberRoot.pendingContext;
|
||||
fiberRoot.pendingContext = null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case HostComponent: {
|
||||
popHostContext(workInProgress);
|
||||
const rootContainerInstance = getRootHostContainer();
|
||||
const type = workInProgress.type;
|
||||
const newProps = workInProgress.memoizedProps;
|
||||
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();
|
||||
const updatePayload = prepareUpdate(
|
||||
instance,
|
||||
type,
|
||||
oldProps,
|
||||
newProps,
|
||||
rootContainerInstance,
|
||||
currentHostContext,
|
||||
);
|
||||
|
||||
// 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.
|
||||
if (updatePayload) {
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
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.
|
||||
const 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,
|
||||
)
|
||||
) {
|
||||
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 = workInProgress.memoizedProps;
|
||||
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.
|
||||
if (oldText !== newText) {
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
} 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();
|
||||
const textInstance = createTextInstance(
|
||||
newText,
|
||||
rootContainerInstance,
|
||||
currentHostContext,
|
||||
workInProgress,
|
||||
);
|
||||
workInProgress.stateNode = textInstance;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case CoroutineComponent:
|
||||
return moveCoroutineToHandlerPhase(current, workInProgress);
|
||||
case CoroutineHandlerPhase:
|
||||
// Reset the tag to now be a first phase coroutine.
|
||||
workInProgress.tag = CoroutineComponent;
|
||||
return null;
|
||||
case YieldComponent:
|
||||
// Does nothing.
|
||||
return null;
|
||||
case Fragment:
|
||||
return null;
|
||||
case HostPortal:
|
||||
// TODO: Only mark this as an update if we have any pending callbacks.
|
||||
markUpdate(workInProgress);
|
||||
popHostContainer(workInProgress);
|
||||
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,
|
||||
};
|
||||
};
|
@ -1,289 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberContext
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {StackCursor} from 'ReactFiberStack';
|
||||
|
||||
var checkPropTypes = require('prop-types/checkPropTypes');
|
||||
var emptyObject = require('fbjs/lib/emptyObject');
|
||||
var getComponentName = require('getComponentName');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
var {isFiberMounted} = require('ReactFiberTreeReflection');
|
||||
var {ClassComponent, HostRoot} = require('ReactTypeOfWork');
|
||||
const {createCursor, pop, push} = require('ReactFiberStack');
|
||||
|
||||
if (__DEV__) {
|
||||
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
|
||||
var {ReactDebugCurrentFrame} = require('ReactGlobalSharedState');
|
||||
var {startPhaseTimer, stopPhaseTimer} = require('ReactDebugFiberPerf');
|
||||
var warnedAboutMissingGetChildContext = {};
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
exports.getUnmaskedContext = getUnmaskedContext;
|
||||
|
||||
function cacheContext(
|
||||
workInProgress: Fiber,
|
||||
unmaskedContext: Object,
|
||||
maskedContext: Object,
|
||||
) {
|
||||
const instance = workInProgress.stateNode;
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
|
||||
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
|
||||
}
|
||||
exports.cacheContext = cacheContext;
|
||||
|
||||
exports.getMaskedContext = function(
|
||||
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';
|
||||
ReactDebugCurrentFrame.current = workInProgress;
|
||||
checkPropTypes(
|
||||
contextTypes,
|
||||
context,
|
||||
'context',
|
||||
name,
|
||||
ReactDebugCurrentFrame.getStackAddendum,
|
||||
);
|
||||
ReactDebugCurrentFrame.current = null;
|
||||
}
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
exports.hasContextChanged = function(): boolean {
|
||||
return didPerformWorkStackCursor.current;
|
||||
};
|
||||
|
||||
function isContextConsumer(fiber: Fiber): boolean {
|
||||
return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
|
||||
}
|
||||
exports.isContextConsumer = isContextConsumer;
|
||||
|
||||
function isContextProvider(fiber: Fiber): boolean {
|
||||
return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
|
||||
}
|
||||
exports.isContextProvider = isContextProvider;
|
||||
|
||||
function popContextProvider(fiber: Fiber): void {
|
||||
if (!isContextProvider(fiber)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pop(didPerformWorkStackCursor, fiber);
|
||||
pop(contextStackCursor, fiber);
|
||||
}
|
||||
exports.popContextProvider = popContextProvider;
|
||||
|
||||
exports.pushTopLevelContextObject = function(
|
||||
fiber: Fiber,
|
||||
context: Object,
|
||||
didChange: boolean,
|
||||
): void {
|
||||
invariant(
|
||||
contextStackCursor.cursor == null,
|
||||
'Unexpected context found on stack',
|
||||
);
|
||||
|
||||
push(contextStackCursor, context, fiber);
|
||||
push(didPerformWorkStackCursor, didChange, fiber);
|
||||
};
|
||||
|
||||
function processChildContext(
|
||||
fiber: Fiber,
|
||||
parentContext: Object,
|
||||
isReconciling: boolean,
|
||||
): 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.phase = 'getChildContext';
|
||||
startPhaseTimer(fiber, 'getChildContext');
|
||||
childContext = instance.getChildContext();
|
||||
stopPhaseTimer();
|
||||
ReactDebugCurrentFiber.phase = null;
|
||||
} else {
|
||||
childContext = instance.getChildContext();
|
||||
}
|
||||
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';
|
||||
// We can only provide accurate element stacks if we pass work-in-progress tree
|
||||
// during the begin or complete phase. However currently this function is also
|
||||
// called from unstable_renderSubtree legacy implementation. In this case it unsafe to
|
||||
// assume anything about the given fiber. We won't pass it down if we aren't sure.
|
||||
// TODO: remove this hack when we delete unstable_renderSubtree in Fiber.
|
||||
const workInProgress = isReconciling ? fiber : null;
|
||||
ReactDebugCurrentFrame.current = workInProgress;
|
||||
checkPropTypes(
|
||||
childContextTypes,
|
||||
childContext,
|
||||
'child context',
|
||||
name,
|
||||
ReactDebugCurrentFrame.getStackAddendum,
|
||||
);
|
||||
ReactDebugCurrentFrame.current = null;
|
||||
}
|
||||
|
||||
return {...parentContext, ...childContext};
|
||||
}
|
||||
exports.processChildContext = processChildContext;
|
||||
|
||||
exports.pushContextProvider = function(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.
|
||||
previousContext = contextStackCursor.current;
|
||||
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
|
||||
push(didPerformWorkStackCursor, false, workInProgress);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.invalidateContextProvider = function(workInProgress: Fiber): void {
|
||||
const instance = workInProgress.stateNode;
|
||||
invariant(instance, 'Expected to have an instance by this point.');
|
||||
|
||||
// Merge parent and own context.
|
||||
const mergedContext = processChildContext(
|
||||
workInProgress,
|
||||
previousContext,
|
||||
true,
|
||||
);
|
||||
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, true, workInProgress);
|
||||
};
|
||||
|
||||
exports.resetContext = function(): void {
|
||||
previousContext = emptyObject;
|
||||
contextStackCursor.current = emptyObject;
|
||||
didPerformWorkStackCursor.current = false;
|
||||
};
|
||||
|
||||
exports.findCurrentUnmaskedContext = function(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',
|
||||
);
|
||||
|
||||
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');
|
||||
node = parent;
|
||||
}
|
||||
return node.stateNode.context;
|
||||
};
|
@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberDevToolsHook
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {FiberRoot} from 'ReactFiberRoot';
|
||||
|
||||
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
|
||||
|
||||
let rendererID = null;
|
||||
let injectInternals = null;
|
||||
let onCommitRoot = null;
|
||||
let onCommitUnmount = null;
|
||||
if (
|
||||
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__.supportsFiber
|
||||
) {
|
||||
let {
|
||||
inject,
|
||||
onCommitFiberRoot,
|
||||
onCommitFiberUnmount,
|
||||
} = __REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
|
||||
injectInternals = function(internals: Object) {
|
||||
warning(rendererID == null, 'Cannot inject into DevTools twice.');
|
||||
rendererID = inject(internals);
|
||||
};
|
||||
|
||||
onCommitRoot = function(root: FiberRoot) {
|
||||
if (rendererID == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
onCommitFiberRoot(rendererID, root);
|
||||
} catch (err) {
|
||||
// Catch all errors because it is unsafe to throw in the commit phase.
|
||||
if (__DEV__) {
|
||||
warning(false, 'React DevTools encountered an error: %s', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onCommitUnmount = function(fiber: Fiber) {
|
||||
if (rendererID == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
onCommitFiberUnmount(rendererID, fiber);
|
||||
} catch (err) {
|
||||
// Catch all errors because it is unsafe to throw in the commit phase.
|
||||
if (__DEV__) {
|
||||
warning(false, 'React DevTools encountered an error: %s', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exports.injectInternals = injectInternals;
|
||||
exports.onCommitRoot = onCommitRoot;
|
||||
exports.onCommitUnmount = onCommitUnmount;
|
@ -1,116 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberErrorLogger
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {CapturedError} from 'ReactFiberScheduler';
|
||||
|
||||
const defaultShowDialog = (capturedError: CapturedError) => true;
|
||||
|
||||
let showDialog = defaultShowDialog;
|
||||
|
||||
function logCapturedError(capturedError: CapturedError): void {
|
||||
const logError = showDialog(capturedError);
|
||||
|
||||
// Allow injected showDialog() to prevent default console.error logging.
|
||||
// This enables renderers like ReactNative to better manage redbox behavior.
|
||||
if (logError === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const {
|
||||
componentName,
|
||||
componentStack,
|
||||
error,
|
||||
errorBoundaryName,
|
||||
errorBoundaryFound,
|
||||
willRetry,
|
||||
} = capturedError;
|
||||
|
||||
const {message, name, stack} = error;
|
||||
|
||||
const errorSummary = message ? `${name}: ${message}` : name;
|
||||
|
||||
const componentNameMessage = componentName
|
||||
? `React caught an error thrown by ${componentName}.`
|
||||
: 'React caught an error thrown by one of your components.';
|
||||
|
||||
// Error stack varies by browser, eg:
|
||||
// Chrome prepends the Error name and type.
|
||||
// Firefox, Safari, and IE don't indent the stack lines.
|
||||
// Format it in a consistent way for error logging.
|
||||
let formattedCallStack = stack.slice(0, errorSummary.length) ===
|
||||
errorSummary
|
||||
? stack.slice(errorSummary.length)
|
||||
: stack;
|
||||
formattedCallStack = formattedCallStack
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => `\n ${line.trim()}`)
|
||||
.join();
|
||||
|
||||
let errorBoundaryMessage;
|
||||
// errorBoundaryFound check is sufficient; errorBoundaryName check is to satisfy Flow.
|
||||
if (errorBoundaryFound && errorBoundaryName) {
|
||||
if (willRetry) {
|
||||
errorBoundaryMessage =
|
||||
`React will try to recreate this component tree from scratch ` +
|
||||
`using the error boundary you provided, ${errorBoundaryName}.`;
|
||||
} else {
|
||||
errorBoundaryMessage =
|
||||
`This error was initially handled by the error boundary ${errorBoundaryName}. ` +
|
||||
`Recreating the tree from scratch failed so React will unmount the tree.`;
|
||||
}
|
||||
} else {
|
||||
// TODO Link to unstable_handleError() documentation once it exists.
|
||||
errorBoundaryMessage =
|
||||
'Consider adding an error boundary to your tree to customize error handling behavior.';
|
||||
}
|
||||
|
||||
console.error(
|
||||
`${componentNameMessage} You should fix this error in your code. ${errorBoundaryMessage}\n\n` +
|
||||
`${errorSummary}\n\n` +
|
||||
`The error is located at: ${componentStack}\n\n` +
|
||||
`The error was thrown at: ${formattedCallStack}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!__DEV__) {
|
||||
const {error} = capturedError;
|
||||
console.error(
|
||||
`React caught an error thrown by one of your components.\n\n${error.stack}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
exports.injection = {
|
||||
/**
|
||||
* Display custom dialog for lifecycle errors.
|
||||
* Return false to prevent default behavior of logging to console.error.
|
||||
*/
|
||||
injectDialog(fn: (e: CapturedError) => boolean) {
|
||||
invariant(
|
||||
showDialog === defaultShowDialog,
|
||||
'The custom dialog was already injected.',
|
||||
);
|
||||
invariant(
|
||||
typeof fn === 'function',
|
||||
'Injected showDialog() must be a function.',
|
||||
);
|
||||
showDialog = fn;
|
||||
},
|
||||
};
|
||||
|
||||
exports.logCapturedError = logCapturedError;
|
@ -1,130 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberHostContext
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {HostConfig} from 'ReactFiberReconciler';
|
||||
import type {StackCursor} from 'ReactFiberStack';
|
||||
|
||||
const {createCursor, pop, push} = require('ReactFiberStack');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
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,
|
||||
resetHostContainer(): void,
|
||||
};
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
|
||||
): HostContext<C, CX> {
|
||||
const {getChildHostContext, getRootHostContext} = config;
|
||||
|
||||
let contextStackCursor: StackCursor<CX | NoContextT> = createCursor(
|
||||
NO_CONTEXT,
|
||||
);
|
||||
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);
|
||||
|
||||
const nextRootContext = getRootHostContext(nextRootInstance);
|
||||
|
||||
// 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, 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 = requiredContext(rootInstanceStackCursor.current);
|
||||
const context = 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);
|
||||
}
|
||||
|
||||
function resetHostContainer() {
|
||||
contextStackCursor.current = NO_CONTEXT;
|
||||
rootInstanceStackCursor.current = NO_CONTEXT;
|
||||
}
|
||||
|
||||
return {
|
||||
getHostContext,
|
||||
getRootHostContainer,
|
||||
popHostContainer,
|
||||
popHostContext,
|
||||
pushHostContainer,
|
||||
pushHostContext,
|
||||
resetHostContainer,
|
||||
};
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberInstrumentation
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// This lets us hook into Fiber to debug what it's doing.
|
||||
// See https://github.com/facebook/react/pull/8033.
|
||||
// This is not part of the public API, not even for React DevTools.
|
||||
// You may only inject a debugTool if you work on React Fiber itself.
|
||||
var ReactFiberInstrumentation = {
|
||||
debugTool: null,
|
||||
};
|
||||
|
||||
module.exports = ReactFiberInstrumentation;
|
@ -1,271 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberReconciler
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {FiberRoot} from 'ReactFiberRoot';
|
||||
import type {PriorityLevel} from 'ReactPriorityLevel';
|
||||
import type {ReactNodeList} from 'ReactTypes';
|
||||
|
||||
var ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
|
||||
var {addTopLevelUpdate} = require('ReactFiberUpdateQueue');
|
||||
|
||||
var {
|
||||
findCurrentUnmaskedContext,
|
||||
isContextProvider,
|
||||
processChildContext,
|
||||
} = require('ReactFiberContext');
|
||||
var {createFiberRoot} = require('ReactFiberRoot');
|
||||
var ReactFiberScheduler = require('ReactFiberScheduler');
|
||||
|
||||
if (__DEV__) {
|
||||
var warning = require('fbjs/lib/warning');
|
||||
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
|
||||
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
|
||||
var getComponentName = require('getComponentName');
|
||||
}
|
||||
|
||||
var {findCurrentHostFiber} = require('ReactFiberTreeReflection');
|
||||
|
||||
var getContextForSubtree = require('getContextForSubtree');
|
||||
|
||||
export type Deadline = {
|
||||
timeRemaining: () => number,
|
||||
};
|
||||
|
||||
type OpaqueHandle = Fiber;
|
||||
type OpaqueRoot = FiberRoot;
|
||||
|
||||
export type HostConfig<T, P, I, TI, PI, C, 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,
|
||||
): boolean,
|
||||
|
||||
prepareUpdate(
|
||||
instance: I,
|
||||
type: T,
|
||||
oldProps: P,
|
||||
newProps: P,
|
||||
rootContainerInstance: C,
|
||||
hostContext: CX,
|
||||
): null | 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,
|
||||
|
||||
shouldSetTextContent(props: P): boolean,
|
||||
resetTextContent(instance: I): void,
|
||||
shouldDeprioritizeSubtree(type: T, props: P): boolean,
|
||||
|
||||
createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: C,
|
||||
hostContext: CX,
|
||||
internalInstanceHandle: OpaqueHandle,
|
||||
): TI,
|
||||
commitTextUpdate(textInstance: TI, oldText: string, newText: string): void,
|
||||
|
||||
appendChild(parentInstance: I | C, child: I | TI): void,
|
||||
insertBefore(parentInstance: I | C, child: I | TI, beforeChild: I | TI): void,
|
||||
removeChild(parentInstance: I | C, child: I | TI): void,
|
||||
|
||||
scheduleAnimationCallback(callback: () => void): number | void,
|
||||
scheduleDeferredCallback(
|
||||
callback: (deadline: Deadline) => void,
|
||||
): number | void,
|
||||
|
||||
prepareForCommit(): void,
|
||||
resetAfterCommit(): void,
|
||||
|
||||
useSyncScheduling?: boolean,
|
||||
};
|
||||
|
||||
export type Reconciler<C, I, TI> = {
|
||||
createContainer(containerInfo: C): OpaqueRoot,
|
||||
updateContainer(
|
||||
element: ReactNodeList,
|
||||
container: OpaqueRoot,
|
||||
parentComponent: ?ReactComponent<any, any, any>,
|
||||
callback: ?Function,
|
||||
): void,
|
||||
performWithPriority(priorityLevel: PriorityLevel, fn: Function): void,
|
||||
batchedUpdates<A>(fn: () => A): A,
|
||||
unbatchedUpdates<A>(fn: () => A): A,
|
||||
syncUpdates<A>(fn: () => A): A,
|
||||
deferredUpdates<A>(fn: () => A): A,
|
||||
|
||||
// Used to extract the return value from the initial render. Legacy API.
|
||||
getPublicRootInstance(
|
||||
container: OpaqueRoot,
|
||||
): ReactComponent<any, any, any> | TI | I | null,
|
||||
|
||||
// Use for findDOMNode/findHostNode. Legacy API.
|
||||
findHostInstance(component: Fiber): I | TI | null,
|
||||
};
|
||||
|
||||
getContextForSubtree._injectFiber(function(fiber: Fiber) {
|
||||
const parentContext = findCurrentUnmaskedContext(fiber);
|
||||
return isContextProvider(fiber)
|
||||
? processChildContext(fiber, parentContext, false)
|
||||
: parentContext;
|
||||
});
|
||||
|
||||
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
|
||||
): Reconciler<C, I, TI> {
|
||||
var {
|
||||
scheduleUpdate,
|
||||
getPriorityContext,
|
||||
performWithPriority,
|
||||
batchedUpdates,
|
||||
unbatchedUpdates,
|
||||
syncUpdates,
|
||||
deferredUpdates,
|
||||
} = ReactFiberScheduler(config);
|
||||
|
||||
function scheduleTopLevelUpdate(
|
||||
current: Fiber,
|
||||
element: ReactNodeList,
|
||||
callback: ?Function,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
if (
|
||||
ReactDebugCurrentFiber.phase === 'render' &&
|
||||
ReactDebugCurrentFiber.current !== null
|
||||
) {
|
||||
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',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the top-level element is an async wrapper component. If so, treat
|
||||
// updates to the root as async. This is a bit weird but lets us avoid a separate
|
||||
// `renderAsync` API.
|
||||
const forceAsync =
|
||||
(ReactFeatureFlags : any).enableAsyncSubtreeAPI &&
|
||||
element != null &&
|
||||
element.type != null &&
|
||||
(element.type: any).unstable_asyncUpdates === true;
|
||||
const priorityLevel = getPriorityContext(current, forceAsync);
|
||||
const nextState = {element};
|
||||
callback = callback === undefined ? null : callback;
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
callback === null || typeof callback === 'function',
|
||||
'render(...): Expected the last optional `callback` argument to be a ' +
|
||||
'function. Instead received: %s.',
|
||||
callback,
|
||||
);
|
||||
}
|
||||
addTopLevelUpdate(current, nextState, callback, priorityLevel);
|
||||
scheduleUpdate(current, priorityLevel);
|
||||
}
|
||||
|
||||
return {
|
||||
createContainer(containerInfo: C): OpaqueRoot {
|
||||
return createFiberRoot(containerInfo);
|
||||
},
|
||||
|
||||
updateContainer(
|
||||
element: ReactNodeList,
|
||||
container: OpaqueRoot,
|
||||
parentComponent: ?ReactComponent<any, any, any>,
|
||||
callback: ?Function,
|
||||
): void {
|
||||
// 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;
|
||||
}
|
||||
|
||||
scheduleTopLevelUpdate(current, element, callback);
|
||||
},
|
||||
|
||||
performWithPriority,
|
||||
|
||||
batchedUpdates,
|
||||
|
||||
unbatchedUpdates,
|
||||
|
||||
syncUpdates,
|
||||
|
||||
deferredUpdates,
|
||||
|
||||
getPublicRootInstance(
|
||||
container: OpaqueRoot,
|
||||
): ReactComponent<any, any, any> | I | TI | null {
|
||||
const containerFiber = container.current;
|
||||
if (!containerFiber.child) {
|
||||
return null;
|
||||
}
|
||||
return containerFiber.child.stateNode;
|
||||
},
|
||||
|
||||
findHostInstance(fiber: Fiber): I | TI | null {
|
||||
const hostFiber = findCurrentHostFiber(fiber);
|
||||
if (hostFiber === null) {
|
||||
return null;
|
||||
}
|
||||
return hostFiber.stateNode;
|
||||
},
|
||||
};
|
||||
};
|
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberRoot
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
|
||||
const {createHostRootFiber} = require('ReactFiber');
|
||||
|
||||
export type FiberRoot = {
|
||||
// Any additional information from the host associated with this root.
|
||||
containerInfo: any,
|
||||
// The currently active root fiber. This is the mutable root of the tree.
|
||||
current: Fiber,
|
||||
// Determines if this root has already been added to the schedule for work.
|
||||
isScheduled: boolean,
|
||||
// The work schedule is a linked list.
|
||||
nextScheduledRoot: FiberRoot | null,
|
||||
// Top context object, used by renderSubtreeIntoContainer
|
||||
context: Object | null,
|
||||
pendingContext: Object | null,
|
||||
};
|
||||
|
||||
exports.createFiberRoot = function(containerInfo: any): FiberRoot {
|
||||
// Cyclic construction. This cheats the type system right now because
|
||||
// stateNode is any.
|
||||
const uninitializedFiber = createHostRootFiber();
|
||||
const root = {
|
||||
current: uninitializedFiber,
|
||||
containerInfo: containerInfo,
|
||||
isScheduled: false,
|
||||
nextScheduledRoot: null,
|
||||
context: null,
|
||||
pendingContext: null,
|
||||
};
|
||||
uninitializedFiber.stateNode = root;
|
||||
return root;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,92 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberStack
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
|
||||
export type StackCursor<T> = {
|
||||
current: T,
|
||||
};
|
||||
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
const valueStack: Array<any> = [];
|
||||
|
||||
if (__DEV__) {
|
||||
var fiberStack: Array<Fiber | null> = [];
|
||||
}
|
||||
|
||||
let index = -1;
|
||||
|
||||
exports.createCursor = function<T>(defaultValue: T): StackCursor<T> {
|
||||
return {
|
||||
current: defaultValue,
|
||||
};
|
||||
};
|
||||
|
||||
exports.isEmpty = function(): boolean {
|
||||
return index === -1;
|
||||
};
|
||||
|
||||
exports.pop = function<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--;
|
||||
};
|
||||
|
||||
exports.push = function<T>(
|
||||
cursor: StackCursor<T>,
|
||||
value: T,
|
||||
fiber: Fiber,
|
||||
): void {
|
||||
index++;
|
||||
|
||||
valueStack[index] = cursor.current;
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack[index] = fiber;
|
||||
}
|
||||
|
||||
cursor.current = value;
|
||||
};
|
||||
|
||||
exports.reset = function(): void {
|
||||
while (index > -1) {
|
||||
valueStack[index] = null;
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack[index] = null;
|
||||
}
|
||||
|
||||
index--;
|
||||
}
|
||||
};
|
@ -1,265 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberTreeReflection
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
var {ReactCurrentOwner} = require('ReactGlobalSharedState');
|
||||
|
||||
var getComponentName = require('getComponentName');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
if (__DEV__) {
|
||||
var warning = require('fbjs/lib/warning');
|
||||
}
|
||||
|
||||
var {
|
||||
HostRoot,
|
||||
HostComponent,
|
||||
HostText,
|
||||
ClassComponent,
|
||||
} = require('ReactTypeOfWork');
|
||||
|
||||
var {NoEffect, Placement} = require('ReactTypeOfSideEffect');
|
||||
|
||||
var MOUNTING = 1;
|
||||
var MOUNTED = 2;
|
||||
var UNMOUNTED = 3;
|
||||
|
||||
function isFiberMountedImpl(fiber: Fiber): number {
|
||||
let node = fiber;
|
||||
if (!fiber.alternate) {
|
||||
// If there is no alternate, this might be a new tree that isn't inserted
|
||||
// yet. If it is, then it will have a pending insertion effect on it.
|
||||
if ((node.effectTag & Placement) !== NoEffect) {
|
||||
return MOUNTING;
|
||||
}
|
||||
while (node.return) {
|
||||
node = node.return;
|
||||
if ((node.effectTag & Placement) !== NoEffect) {
|
||||
return MOUNTING;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (node.return) {
|
||||
node = node.return;
|
||||
}
|
||||
}
|
||||
if (node.tag === HostRoot) {
|
||||
// TODO: Check if this was a nested HostRoot when used with
|
||||
// renderContainerIntoSubtree.
|
||||
return MOUNTED;
|
||||
}
|
||||
// If we didn't hit the root, that means that we're in an disconnected tree
|
||||
// that has been unmounted.
|
||||
return UNMOUNTED;
|
||||
}
|
||||
exports.isFiberMounted = function(fiber: Fiber): boolean {
|
||||
return isFiberMountedImpl(fiber) === MOUNTED;
|
||||
};
|
||||
|
||||
exports.isMounted = function(
|
||||
component: ReactComponent<any, any, any>,
|
||||
): boolean {
|
||||
if (__DEV__) {
|
||||
const owner = (ReactCurrentOwner.current: any);
|
||||
if (owner !== null && owner.tag === ClassComponent) {
|
||||
const ownerFiber: Fiber = owner;
|
||||
const instance = ownerFiber.stateNode;
|
||||
warning(
|
||||
instance._warnedAboutRefsInRender,
|
||||
'%s is accessing isMounted inside its render() function. ' +
|
||||
'render() should be a pure function of props and state. It should ' +
|
||||
'never access something that requires stale data from the previous ' +
|
||||
'render, such as refs. Move this logic to componentDidMount and ' +
|
||||
'componentDidUpdate instead.',
|
||||
getComponentName(ownerFiber) || 'A component',
|
||||
);
|
||||
instance._warnedAboutRefsInRender = true;
|
||||
}
|
||||
}
|
||||
|
||||
var fiber: ?Fiber = ReactInstanceMap.get(component);
|
||||
if (!fiber) {
|
||||
return false;
|
||||
}
|
||||
return isFiberMountedImpl(fiber) === MOUNTED;
|
||||
};
|
||||
|
||||
function assertIsMounted(fiber) {
|
||||
invariant(
|
||||
isFiberMountedImpl(fiber) === MOUNTED,
|
||||
'Unable to find node on an unmounted component.',
|
||||
);
|
||||
}
|
||||
|
||||
function findCurrentFiberUsingSlowPath(fiber: Fiber): Fiber | null {
|
||||
let alternate = fiber.alternate;
|
||||
if (!alternate) {
|
||||
// If there is no alternate, then we only need to check if it is mounted.
|
||||
const state = isFiberMountedImpl(fiber);
|
||||
invariant(
|
||||
state !== UNMOUNTED,
|
||||
'Unable to find node on an unmounted component.',
|
||||
);
|
||||
if (state === MOUNTING) {
|
||||
return null;
|
||||
}
|
||||
return fiber;
|
||||
}
|
||||
// If we have two possible branches, we'll walk backwards up to the root
|
||||
// to see what path the root points to. On the way we may hit one of the
|
||||
// special cases and we'll deal with them.
|
||||
let a = fiber;
|
||||
let b = alternate;
|
||||
while (true) {
|
||||
let parentA = a.return;
|
||||
let parentB = parentA ? parentA.alternate : null;
|
||||
if (!parentA || !parentB) {
|
||||
// We're at the root.
|
||||
break;
|
||||
}
|
||||
|
||||
// If both copies of the parent fiber point to the same child, we can
|
||||
// assume that the child is current. This happens when we bailout on low
|
||||
// priority: the bailed out fiber's child reuses the current child.
|
||||
if (parentA.child === parentB.child) {
|
||||
let child = parentA.child;
|
||||
while (child) {
|
||||
if (child === a) {
|
||||
// We've determined that A is the current branch.
|
||||
assertIsMounted(parentA);
|
||||
return fiber;
|
||||
}
|
||||
if (child === b) {
|
||||
// We've determined that B is the current branch.
|
||||
assertIsMounted(parentA);
|
||||
return alternate;
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
// We should never have an alternate for any mounting node. So the only
|
||||
// way this could possibly happen is if this was unmounted, if at all.
|
||||
invariant(false, 'Unable to find node on an unmounted component.');
|
||||
}
|
||||
|
||||
if (a.return !== b.return) {
|
||||
// The return pointer of A and the return pointer of B point to different
|
||||
// fibers. We assume that return pointers never criss-cross, so A must
|
||||
// belong to the child set of A.return, and B must belong to the child
|
||||
// set of B.return.
|
||||
a = parentA;
|
||||
b = parentB;
|
||||
} else {
|
||||
// The return pointers pointer to the same fiber. We'll have to use the
|
||||
// default, slow path: scan the child sets of each parent alternate to see
|
||||
// which child belongs to which set.
|
||||
//
|
||||
// Search parent A's child set
|
||||
let didFindChild = false;
|
||||
let child = parentA.child;
|
||||
while (child) {
|
||||
if (child === a) {
|
||||
didFindChild = true;
|
||||
a = parentA;
|
||||
b = parentB;
|
||||
break;
|
||||
}
|
||||
if (child === b) {
|
||||
didFindChild = true;
|
||||
b = parentA;
|
||||
a = parentB;
|
||||
break;
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
if (!didFindChild) {
|
||||
// Search parent B's child set
|
||||
child = parentB.child;
|
||||
while (child) {
|
||||
if (child === a) {
|
||||
didFindChild = true;
|
||||
a = parentB;
|
||||
b = parentA;
|
||||
break;
|
||||
}
|
||||
if (child === b) {
|
||||
didFindChild = true;
|
||||
b = parentB;
|
||||
a = parentA;
|
||||
break;
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
invariant(
|
||||
didFindChild,
|
||||
'Child was not found in either parent set. This indicates a bug ' +
|
||||
'related to the return pointer.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
invariant(
|
||||
a.alternate === b,
|
||||
"Return fibers should always be each others' alternates.",
|
||||
);
|
||||
}
|
||||
// If the root is not a host container, we're in a disconnected tree. I.e.
|
||||
// unmounted.
|
||||
invariant(
|
||||
a.tag === HostRoot,
|
||||
'Unable to find node on an unmounted component.',
|
||||
);
|
||||
if (a.stateNode.current === a) {
|
||||
// We've determined that A is the current branch.
|
||||
return fiber;
|
||||
}
|
||||
// Otherwise B has to be current branch.
|
||||
return alternate;
|
||||
}
|
||||
exports.findCurrentFiberUsingSlowPath = findCurrentFiberUsingSlowPath;
|
||||
|
||||
exports.findCurrentHostFiber = function(parent: Fiber): Fiber | null {
|
||||
const currentParent = findCurrentFiberUsingSlowPath(parent);
|
||||
if (!currentParent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Next we'll drill down this component to find the first HostComponent/Text.
|
||||
let node: Fiber = currentParent;
|
||||
while (true) {
|
||||
if (node.tag === HostComponent || node.tag === HostText) {
|
||||
return node;
|
||||
} else if (node.child) {
|
||||
// TODO: If we hit a Portal, we're supposed to skip it.
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
continue;
|
||||
}
|
||||
if (node === currentParent) {
|
||||
return null;
|
||||
}
|
||||
while (!node.sibling) {
|
||||
if (!node.return || node.return === currentParent) {
|
||||
return null;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
}
|
||||
// Flow needs the return null here, but ESLint complains about it.
|
||||
// eslint-disable-next-line no-unreachable
|
||||
return null;
|
||||
};
|
@ -1,509 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberUpdateQueue
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Fiber} from 'ReactFiber';
|
||||
import type {PriorityLevel} from 'ReactPriorityLevel';
|
||||
|
||||
const {Callback: CallbackEffect} = require('ReactTypeOfSideEffect');
|
||||
|
||||
const {
|
||||
NoWork,
|
||||
SynchronousPriority,
|
||||
TaskPriority,
|
||||
} = require('ReactPriorityLevel');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
if (__DEV__) {
|
||||
var warning = require('fbjs/lib/warning');
|
||||
}
|
||||
|
||||
type PartialState<State, Props> =
|
||||
| $Subtype<State>
|
||||
| ((prevState: State, props: Props) => $Subtype<State>);
|
||||
|
||||
// Callbacks are not validated until invocation
|
||||
type Callback = mixed;
|
||||
|
||||
type Update = {
|
||||
priorityLevel: PriorityLevel,
|
||||
partialState: PartialState<any, any>,
|
||||
callback: Callback | null,
|
||||
isReplace: boolean,
|
||||
isForced: boolean,
|
||||
isTopLevelUnmount: boolean,
|
||||
next: Update | null,
|
||||
};
|
||||
|
||||
// Singly linked-list of updates. When an update is scheduled, it is added to
|
||||
// the queue of the current fiber and the work-in-progress fiber. The two queues
|
||||
// are separate but they share a persistent structure.
|
||||
//
|
||||
// During reconciliation, updates are removed from the work-in-progress fiber,
|
||||
// but they remain on the current fiber. That ensures that if a work-in-progress
|
||||
// is aborted, the aborted updates are recovered by cloning from current.
|
||||
//
|
||||
// The work-in-progress queue is always a subset of the current queue.
|
||||
//
|
||||
// When the tree is committed, the work-in-progress becomes the current.
|
||||
export type UpdateQueue = {
|
||||
first: Update | null,
|
||||
last: Update | null,
|
||||
hasForceUpdate: boolean,
|
||||
callbackList: null | Array<Callback>,
|
||||
|
||||
// Dev only
|
||||
isProcessing?: boolean,
|
||||
};
|
||||
|
||||
function comparePriority(a: PriorityLevel, b: PriorityLevel): number {
|
||||
// When comparing update priorities, treat sync and Task work as equal.
|
||||
// TODO: Could we avoid the need for this by always coercing sync priority
|
||||
// to Task when scheduling an update?
|
||||
if (
|
||||
(a === TaskPriority || a === SynchronousPriority) &&
|
||||
(b === TaskPriority || b === SynchronousPriority)
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
if (a === NoWork && b !== NoWork) {
|
||||
return -255;
|
||||
}
|
||||
if (a !== NoWork && b === NoWork) {
|
||||
return 255;
|
||||
}
|
||||
return a - b;
|
||||
}
|
||||
|
||||
// Ensures that a fiber has an update queue, creating a new one if needed.
|
||||
// Returns the new or existing queue.
|
||||
function ensureUpdateQueue(fiber: Fiber): UpdateQueue {
|
||||
if (fiber.updateQueue !== null) {
|
||||
// We already have an update queue.
|
||||
return fiber.updateQueue;
|
||||
}
|
||||
|
||||
let queue;
|
||||
if (__DEV__) {
|
||||
queue = {
|
||||
first: null,
|
||||
last: null,
|
||||
hasForceUpdate: false,
|
||||
callbackList: null,
|
||||
isProcessing: false,
|
||||
};
|
||||
} else {
|
||||
queue = {
|
||||
first: null,
|
||||
last: null,
|
||||
hasForceUpdate: false,
|
||||
callbackList: null,
|
||||
};
|
||||
}
|
||||
|
||||
fiber.updateQueue = queue;
|
||||
return queue;
|
||||
}
|
||||
|
||||
// Clones an update queue from a source fiber onto its alternate.
|
||||
function cloneUpdateQueue(
|
||||
current: Fiber,
|
||||
workInProgress: Fiber,
|
||||
): UpdateQueue | null {
|
||||
const currentQueue = current.updateQueue;
|
||||
if (currentQueue === null) {
|
||||
// The source fiber does not have an update queue.
|
||||
workInProgress.updateQueue = null;
|
||||
return null;
|
||||
}
|
||||
// If the alternate already has a queue, reuse the previous object.
|
||||
const altQueue = workInProgress.updateQueue !== null
|
||||
? workInProgress.updateQueue
|
||||
: {};
|
||||
altQueue.first = currentQueue.first;
|
||||
altQueue.last = currentQueue.last;
|
||||
|
||||
// These fields are invalid by the time we clone from current. Reset them.
|
||||
altQueue.hasForceUpdate = false;
|
||||
altQueue.callbackList = null;
|
||||
altQueue.isProcessing = false;
|
||||
|
||||
workInProgress.updateQueue = altQueue;
|
||||
|
||||
return altQueue;
|
||||
}
|
||||
exports.cloneUpdateQueue = cloneUpdateQueue;
|
||||
|
||||
function cloneUpdate(update: Update): Update {
|
||||
return {
|
||||
priorityLevel: update.priorityLevel,
|
||||
partialState: update.partialState,
|
||||
callback: update.callback,
|
||||
isReplace: update.isReplace,
|
||||
isForced: update.isForced,
|
||||
isTopLevelUnmount: update.isTopLevelUnmount,
|
||||
next: null,
|
||||
};
|
||||
}
|
||||
|
||||
function insertUpdateIntoQueue(
|
||||
queue: UpdateQueue,
|
||||
update: Update,
|
||||
insertAfter: Update | null,
|
||||
insertBefore: Update | null,
|
||||
) {
|
||||
if (insertAfter !== null) {
|
||||
insertAfter.next = update;
|
||||
} else {
|
||||
// This is the first item in the queue.
|
||||
update.next = queue.first;
|
||||
queue.first = update;
|
||||
}
|
||||
|
||||
if (insertBefore !== null) {
|
||||
update.next = insertBefore;
|
||||
} else {
|
||||
// This is the last item in the queue.
|
||||
queue.last = update;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the update after which the incoming update should be inserted into
|
||||
// the queue, or null if it should be inserted at beginning.
|
||||
function findInsertionPosition(queue, update): Update | null {
|
||||
const priorityLevel = update.priorityLevel;
|
||||
let insertAfter = null;
|
||||
let insertBefore = null;
|
||||
if (
|
||||
queue.last !== null &&
|
||||
comparePriority(queue.last.priorityLevel, priorityLevel) <= 0
|
||||
) {
|
||||
// Fast path for the common case where the update should be inserted at
|
||||
// the end of the queue.
|
||||
insertAfter = queue.last;
|
||||
} else {
|
||||
insertBefore = queue.first;
|
||||
while (
|
||||
insertBefore !== null &&
|
||||
comparePriority(insertBefore.priorityLevel, priorityLevel) <= 0
|
||||
) {
|
||||
insertAfter = insertBefore;
|
||||
insertBefore = insertBefore.next;
|
||||
}
|
||||
}
|
||||
return insertAfter;
|
||||
}
|
||||
|
||||
// The work-in-progress queue is a subset of the current queue (if it exists).
|
||||
// We need to insert the incoming update into both lists. However, it's possible
|
||||
// that the correct position in one list will be different from the position in
|
||||
// the other. Consider the following case:
|
||||
//
|
||||
// Current: 3-5-6
|
||||
// Work-in-progress: 6
|
||||
//
|
||||
// Then we receive an update with priority 4 and insert it into each list:
|
||||
//
|
||||
// Current: 3-4-5-6
|
||||
// Work-in-progress: 4-6
|
||||
//
|
||||
// In the current queue, the new update's `next` pointer points to the update
|
||||
// with priority 5. But in the work-in-progress queue, the pointer points to the
|
||||
// update with priority 6. Because these two queues share the same persistent
|
||||
// data structure, this won't do. (This can only happen when the incoming update
|
||||
// has higher priority than all the updates in the work-in-progress queue.)
|
||||
//
|
||||
// To solve this, in the case where the incoming update needs to be inserted
|
||||
// into two different positions, we'll make a clone of the update and insert
|
||||
// each copy into a separate queue. This forks the list while maintaining a
|
||||
// persistent structure, because the update that is added to the work-in-progress
|
||||
// is always added to the front of the list.
|
||||
//
|
||||
// However, if incoming update is inserted into the same position of both lists,
|
||||
// we shouldn't make a copy.
|
||||
//
|
||||
// If the update is cloned, it returns the cloned update.
|
||||
function insertUpdate(fiber: Fiber, update: Update): Update | null {
|
||||
const queue1 = ensureUpdateQueue(fiber);
|
||||
const queue2 = fiber.alternate !== null
|
||||
? ensureUpdateQueue(fiber.alternate)
|
||||
: null;
|
||||
|
||||
// Warn if an update is scheduled from inside an updater function.
|
||||
if (__DEV__) {
|
||||
if (queue1.isProcessing || (queue2 !== null && queue2.isProcessing)) {
|
||||
warning(
|
||||
false,
|
||||
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
|
||||
'from inside an update function. Update functions should be pure, ' +
|
||||
'with zero side-effects. Consider using componentDidUpdate or a ' +
|
||||
'callback.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the insertion position in the first queue.
|
||||
const insertAfter1 = findInsertionPosition(queue1, update);
|
||||
const insertBefore1 = insertAfter1 !== null
|
||||
? insertAfter1.next
|
||||
: queue1.first;
|
||||
|
||||
if (queue2 === null) {
|
||||
// If there's no alternate queue, there's nothing else to do but insert.
|
||||
insertUpdateIntoQueue(queue1, update, insertAfter1, insertBefore1);
|
||||
return null;
|
||||
}
|
||||
|
||||
// If there is an alternate queue, find the insertion position.
|
||||
const insertAfter2 = findInsertionPosition(queue2, update);
|
||||
const insertBefore2 = insertAfter2 !== null
|
||||
? insertAfter2.next
|
||||
: queue2.first;
|
||||
|
||||
// Now we can insert into the first queue. This must come after finding both
|
||||
// insertion positions because it mutates the list.
|
||||
insertUpdateIntoQueue(queue1, update, insertAfter1, insertBefore1);
|
||||
|
||||
if (insertBefore1 !== insertBefore2) {
|
||||
// The insertion positions are different, so we need to clone the update and
|
||||
// insert the clone into the alternate queue.
|
||||
const update2 = cloneUpdate(update);
|
||||
insertUpdateIntoQueue(queue2, update2, insertAfter2, insertBefore2);
|
||||
return update2;
|
||||
} else {
|
||||
// The insertion positions are the same, so when we inserted into the first
|
||||
// queue, it also inserted into the alternate. All we need to do is update
|
||||
// the alternate queue's `first` and `last` pointers, in case they
|
||||
// have changed.
|
||||
if (insertAfter2 === null) {
|
||||
queue2.first = update;
|
||||
}
|
||||
if (insertBefore2 === null) {
|
||||
queue2.last = null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function addUpdate(
|
||||
fiber: Fiber,
|
||||
partialState: PartialState<any, any> | null,
|
||||
callback: mixed,
|
||||
priorityLevel: PriorityLevel,
|
||||
): void {
|
||||
const update = {
|
||||
priorityLevel,
|
||||
partialState,
|
||||
callback,
|
||||
isReplace: false,
|
||||
isForced: false,
|
||||
isTopLevelUnmount: false,
|
||||
next: null,
|
||||
};
|
||||
insertUpdate(fiber, update);
|
||||
}
|
||||
exports.addUpdate = addUpdate;
|
||||
|
||||
function addReplaceUpdate(
|
||||
fiber: Fiber,
|
||||
state: any | null,
|
||||
callback: Callback | null,
|
||||
priorityLevel: PriorityLevel,
|
||||
): void {
|
||||
const update = {
|
||||
priorityLevel,
|
||||
partialState: state,
|
||||
callback,
|
||||
isReplace: true,
|
||||
isForced: false,
|
||||
isTopLevelUnmount: false,
|
||||
next: null,
|
||||
};
|
||||
insertUpdate(fiber, update);
|
||||
}
|
||||
exports.addReplaceUpdate = addReplaceUpdate;
|
||||
|
||||
function addForceUpdate(
|
||||
fiber: Fiber,
|
||||
callback: Callback | null,
|
||||
priorityLevel: PriorityLevel,
|
||||
): void {
|
||||
const update = {
|
||||
priorityLevel,
|
||||
partialState: null,
|
||||
callback,
|
||||
isReplace: false,
|
||||
isForced: true,
|
||||
isTopLevelUnmount: false,
|
||||
next: null,
|
||||
};
|
||||
insertUpdate(fiber, update);
|
||||
}
|
||||
exports.addForceUpdate = addForceUpdate;
|
||||
|
||||
function getPendingPriority(queue: UpdateQueue): PriorityLevel {
|
||||
return queue.first !== null ? queue.first.priorityLevel : NoWork;
|
||||
}
|
||||
exports.getPendingPriority = getPendingPriority;
|
||||
|
||||
function addTopLevelUpdate(
|
||||
fiber: Fiber,
|
||||
partialState: PartialState<any, any>,
|
||||
callback: Callback | null,
|
||||
priorityLevel: PriorityLevel,
|
||||
): void {
|
||||
const isTopLevelUnmount = partialState.element === null;
|
||||
|
||||
const update = {
|
||||
priorityLevel,
|
||||
partialState,
|
||||
callback,
|
||||
isReplace: false,
|
||||
isForced: false,
|
||||
isTopLevelUnmount,
|
||||
next: null,
|
||||
};
|
||||
const update2 = insertUpdate(fiber, update);
|
||||
|
||||
if (isTopLevelUnmount) {
|
||||
// Drop all updates that are lower-priority, so that the tree is not
|
||||
// remounted. We need to do this for both queues.
|
||||
const queue1 = fiber.updateQueue;
|
||||
const queue2 = fiber.alternate !== null
|
||||
? fiber.alternate.updateQueue
|
||||
: null;
|
||||
|
||||
if (queue1 !== null && update.next !== null) {
|
||||
update.next = null;
|
||||
queue1.last = update;
|
||||
}
|
||||
if (queue2 !== null && update2 !== null && update2.next !== null) {
|
||||
update2.next = null;
|
||||
queue2.last = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.addTopLevelUpdate = addTopLevelUpdate;
|
||||
|
||||
function getStateFromUpdate(update, instance, prevState, props) {
|
||||
const partialState = update.partialState;
|
||||
if (typeof partialState === 'function') {
|
||||
const updateFn = partialState;
|
||||
return updateFn.call(instance, prevState, props);
|
||||
} else {
|
||||
return partialState;
|
||||
}
|
||||
}
|
||||
|
||||
function beginUpdateQueue(
|
||||
workInProgress: Fiber,
|
||||
queue: UpdateQueue,
|
||||
instance: any,
|
||||
prevState: any,
|
||||
props: any,
|
||||
priorityLevel: PriorityLevel,
|
||||
): any {
|
||||
if (__DEV__) {
|
||||
// Set this flag so we can warn if setState is called inside the update
|
||||
// function of another setState.
|
||||
queue.isProcessing = true;
|
||||
}
|
||||
|
||||
queue.hasForceUpdate = false;
|
||||
|
||||
// Applies updates with matching priority to the previous state to create
|
||||
// a new state object.
|
||||
let state = prevState;
|
||||
let dontMutatePrevState = true;
|
||||
let callbackList = null;
|
||||
let update = queue.first;
|
||||
while (
|
||||
update !== null &&
|
||||
comparePriority(update.priorityLevel, priorityLevel) <= 0
|
||||
) {
|
||||
// Remove each update from the queue right before it is processed. That way
|
||||
// if setState is called from inside an updater function, the new update
|
||||
// will be inserted in the correct position.
|
||||
queue.first = update.next;
|
||||
if (queue.first === null) {
|
||||
queue.last = null;
|
||||
}
|
||||
|
||||
let partialState;
|
||||
if (update.isReplace) {
|
||||
state = getStateFromUpdate(update, instance, state, props);
|
||||
dontMutatePrevState = true;
|
||||
} else {
|
||||
partialState = getStateFromUpdate(update, instance, state, props);
|
||||
if (partialState) {
|
||||
if (dontMutatePrevState) {
|
||||
state = Object.assign({}, state, partialState);
|
||||
} else {
|
||||
state = Object.assign(state, partialState);
|
||||
}
|
||||
dontMutatePrevState = false;
|
||||
}
|
||||
}
|
||||
if (update.isForced) {
|
||||
queue.hasForceUpdate = true;
|
||||
}
|
||||
// Second condition ignores top-level unmount callbacks if they are not the
|
||||
// last update in the queue, since a subsequent update will cause a remount.
|
||||
if (
|
||||
update.callback !== null &&
|
||||
!(update.isTopLevelUnmount && update.next !== null)
|
||||
) {
|
||||
callbackList = callbackList || [];
|
||||
callbackList.push(update.callback);
|
||||
workInProgress.effectTag |= CallbackEffect;
|
||||
}
|
||||
update = update.next;
|
||||
}
|
||||
|
||||
queue.callbackList = callbackList;
|
||||
|
||||
if (queue.first === null && callbackList === null && !queue.hasForceUpdate) {
|
||||
// The queue is empty and there are no callbacks. We can reset it.
|
||||
workInProgress.updateQueue = null;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
queue.isProcessing = false;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
exports.beginUpdateQueue = beginUpdateQueue;
|
||||
|
||||
function commitCallbacks(
|
||||
finishedWork: Fiber,
|
||||
queue: UpdateQueue,
|
||||
context: mixed,
|
||||
) {
|
||||
const callbackList = queue.callbackList;
|
||||
if (callbackList === null) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < callbackList.length; i++) {
|
||||
const callback = callbackList[i];
|
||||
invariant(
|
||||
typeof callback === 'function',
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: %s',
|
||||
callback,
|
||||
);
|
||||
callback.call(context);
|
||||
}
|
||||
}
|
||||
exports.commitCallbacks = commitCallbacks;
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactPriorityLevel
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
|
||||
module.exports = {
|
||||
NoWork: 0, // No work is pending.
|
||||
SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
|
||||
TaskPriority: 2, // Completes at the end of the current tick.
|
||||
AnimationPriority: 3, // Needs to complete before the next frame.
|
||||
HighPriority: 4, // Interaction that needs to complete pretty soon to feel responsive.
|
||||
LowPriority: 5, // Data fetching, or result from updating stores.
|
||||
OffscreenPriority: 6, // Won't be visible but do the work in case it becomes visible.
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactTypeOfSideEffect
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type TypeOfSideEffect = number;
|
||||
|
||||
module.exports = {
|
||||
NoEffect: 0, // 0b0000000
|
||||
Placement: 1, // 0b0000001
|
||||
Update: 2, // 0b0000010
|
||||
PlacementAndUpdate: 3, // 0b0000011
|
||||
Deletion: 4, // 0b0000100
|
||||
ContentReset: 8, // 0b0001000
|
||||
Callback: 16, // 0b0010000
|
||||
Err: 32, // 0b0100000
|
||||
Ref: 64, // 0b1000000
|
||||
};
|
@ -1,247 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNoop;
|
||||
var ReactCoroutine;
|
||||
var ReactFeatureFlags;
|
||||
|
||||
describe('ReactCoroutine', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactNoop = require('ReactNoop');
|
||||
ReactCoroutine = require('ReactCoroutine');
|
||||
ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
ReactFeatureFlags.disableNewFiberFeatures = false;
|
||||
});
|
||||
|
||||
function div(...children) {
|
||||
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
|
||||
return {type: 'div', children, prop: undefined};
|
||||
}
|
||||
|
||||
function span(prop) {
|
||||
return {type: 'span', children: [], prop};
|
||||
}
|
||||
|
||||
it('should render a coroutine', () => {
|
||||
var ops = [];
|
||||
|
||||
function Continuation({isSame}) {
|
||||
ops.push(['Continuation', isSame]);
|
||||
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
|
||||
}
|
||||
|
||||
// An alternative API could mark Continuation as something that needs
|
||||
// yielding. E.g. Continuation.yieldType = 123;
|
||||
function Child({bar}) {
|
||||
ops.push(['Child', bar]);
|
||||
return ReactCoroutine.createYield({
|
||||
props: {
|
||||
bar: bar,
|
||||
},
|
||||
continuation: Continuation,
|
||||
});
|
||||
}
|
||||
|
||||
function Indirection() {
|
||||
ops.push('Indirection');
|
||||
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
|
||||
}
|
||||
|
||||
function HandleYields(props, yields) {
|
||||
ops.push('HandleYields');
|
||||
return yields.map((y, i) => (
|
||||
<y.continuation key={i} isSame={props.foo === y.props.bar} />
|
||||
));
|
||||
}
|
||||
|
||||
// An alternative API could mark Parent as something that needs
|
||||
// yielding. E.g. Parent.handler = HandleYields;
|
||||
function Parent(props) {
|
||||
ops.push('Parent');
|
||||
return ReactCoroutine.createCoroutine(
|
||||
props.children,
|
||||
HandleYields,
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return <div><Parent foo={true}><Indirection /></Parent></div>;
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'Parent',
|
||||
'Indirection',
|
||||
['Child', true],
|
||||
// Yield
|
||||
['Child', false],
|
||||
// Yield
|
||||
'HandleYields',
|
||||
// Continue yields
|
||||
['Continuation', true],
|
||||
['Continuation', false],
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(span('foo==bar'), span('foo!=bar')),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update a coroutine', () => {
|
||||
function Continuation({isSame}) {
|
||||
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
|
||||
}
|
||||
|
||||
function Child({bar}) {
|
||||
return ReactCoroutine.createYield({
|
||||
props: {
|
||||
bar: bar,
|
||||
},
|
||||
continuation: Continuation,
|
||||
});
|
||||
}
|
||||
|
||||
function Indirection() {
|
||||
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
|
||||
}
|
||||
|
||||
function HandleYields(props, yields) {
|
||||
return yields.map((y, i) => (
|
||||
<y.continuation key={i} isSame={props.foo === y.props.bar} />
|
||||
));
|
||||
}
|
||||
|
||||
function Parent(props) {
|
||||
return ReactCoroutine.createCoroutine(
|
||||
props.children,
|
||||
HandleYields,
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
return <div><Parent foo={props.foo}><Indirection /></Parent></div>;
|
||||
}
|
||||
|
||||
ReactNoop.render(<App foo={true} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(span('foo==bar'), span('foo!=bar')),
|
||||
]);
|
||||
|
||||
ReactNoop.render(<App foo={false} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(span('foo!=bar'), span('foo==bar')),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should unmount a composite in a coroutine', () => {
|
||||
var ops = [];
|
||||
|
||||
class Continuation extends React.Component {
|
||||
render() {
|
||||
ops.push('Continuation');
|
||||
return <div />;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('Unmount Continuation');
|
||||
}
|
||||
}
|
||||
|
||||
class Child extends React.Component {
|
||||
render() {
|
||||
ops.push('Child');
|
||||
return ReactCoroutine.createYield(Continuation);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('Unmount Child');
|
||||
}
|
||||
}
|
||||
|
||||
function HandleYields(props, yields) {
|
||||
ops.push('HandleYields');
|
||||
return yields.map((ContinuationComponent, i) => (
|
||||
<ContinuationComponent key={i} />
|
||||
));
|
||||
}
|
||||
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
ops.push('Parent');
|
||||
return ReactCoroutine.createCoroutine(
|
||||
this.props.children,
|
||||
HandleYields,
|
||||
this.props,
|
||||
);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('Unmount Parent');
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Parent><Child /></Parent>);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Parent', 'Child', 'HandleYields', 'Continuation']);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<div />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'Unmount Parent',
|
||||
'Unmount Child',
|
||||
'Unmount Continuation',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle deep updates in coroutine', () => {
|
||||
let instances = {};
|
||||
|
||||
class Counter extends React.Component {
|
||||
state = {value: 5};
|
||||
render() {
|
||||
instances[this.props.id] = this;
|
||||
return ReactCoroutine.createYield(this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
return ReactCoroutine.createCoroutine(
|
||||
[
|
||||
<Counter key="a" id="a" />,
|
||||
<Counter key="b" id="b" />,
|
||||
<Counter key="c" id="c" />,
|
||||
],
|
||||
(p, yields) => yields.map((y, i) => <span key={i} prop={y * 100} />),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(500), span(500), span(500)]);
|
||||
|
||||
instances.a.setState({value: 1});
|
||||
instances.b.setState({value: 2});
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(100), span(200), span(500)]);
|
||||
});
|
||||
});
|
@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactFiberReconciler;
|
||||
|
||||
describe('ReactFiberHostContext', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('React');
|
||||
ReactFiberReconciler = require('ReactFiberReconciler');
|
||||
});
|
||||
|
||||
it('works with null host context', () => {
|
||||
var creates = 0;
|
||||
var Renderer = ReactFiberReconciler({
|
||||
prepareForCommit: function() {},
|
||||
resetAfterCommit: function() {},
|
||||
getRootHostContext: function() {
|
||||
return null;
|
||||
},
|
||||
getChildHostContext: function() {
|
||||
return null;
|
||||
},
|
||||
shouldSetTextContent: function() {
|
||||
return false;
|
||||
},
|
||||
createInstance: function() {
|
||||
creates++;
|
||||
},
|
||||
finalizeInitialChildren: function() {
|
||||
return null;
|
||||
},
|
||||
appendInitialChild: function() {
|
||||
return null;
|
||||
},
|
||||
appendChild: function() {
|
||||
return null;
|
||||
},
|
||||
useSyncScheduling: true,
|
||||
});
|
||||
|
||||
const container = Renderer.createContainer(/* root: */ null);
|
||||
Renderer.updateContainer(
|
||||
<a><b /></a>,
|
||||
container,
|
||||
/* parentComponent: */ null,
|
||||
/* callback: */ null,
|
||||
);
|
||||
expect(creates).toBe(2);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,490 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('ReactDebugFiberPerf', () => {
|
||||
let React;
|
||||
let ReactCoroutine;
|
||||
let ReactFeatureFlags;
|
||||
let ReactNoop;
|
||||
let ReactPortal;
|
||||
let PropTypes;
|
||||
|
||||
let root;
|
||||
let activeMeasure;
|
||||
let knownMarks;
|
||||
let knownMeasures;
|
||||
|
||||
function resetFlamechart() {
|
||||
root = {
|
||||
children: [],
|
||||
indent: -1,
|
||||
markName: null,
|
||||
label: null,
|
||||
parent: null,
|
||||
toString() {
|
||||
return this.children.map(c => c.toString()).join('\n');
|
||||
},
|
||||
};
|
||||
activeMeasure = root;
|
||||
knownMarks = new Set();
|
||||
knownMeasures = new Set();
|
||||
}
|
||||
|
||||
function addComment(comment) {
|
||||
activeMeasure.children.push(
|
||||
`${' '.repeat(activeMeasure.indent + 1)}// ${comment}`,
|
||||
);
|
||||
}
|
||||
|
||||
function getFlameChart() {
|
||||
// Make sure we unwind the measurement stack every time.
|
||||
expect(activeMeasure.indent).toBe(-1);
|
||||
expect(activeMeasure).toBe(root);
|
||||
// We should always clean them up because browsers
|
||||
// buffer user timing measurements forever.
|
||||
expect(knownMarks.size).toBe(0);
|
||||
expect(knownMeasures.size).toBe(0);
|
||||
return root.toString();
|
||||
}
|
||||
|
||||
function createUserTimingPolyfill() {
|
||||
// This is not a true polyfill, but it gives us enough
|
||||
// to capture measurements in a readable tree-like output.
|
||||
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
|
||||
return {
|
||||
mark(markName) {
|
||||
const measure = {
|
||||
children: [],
|
||||
indent: activeMeasure.indent + 1,
|
||||
markName: markName,
|
||||
// Will be assigned on measure() call:
|
||||
label: null,
|
||||
parent: activeMeasure,
|
||||
toString() {
|
||||
return (
|
||||
[
|
||||
' '.repeat(this.indent) + this.label,
|
||||
...this.children.map(c => c.toString()),
|
||||
].join('\n') +
|
||||
// Extra newline after each root reconciliation
|
||||
(this.indent === 0 ? '\n' : '')
|
||||
);
|
||||
},
|
||||
};
|
||||
// Step one level deeper
|
||||
activeMeasure.children.push(measure);
|
||||
activeMeasure = measure;
|
||||
knownMarks.add(markName);
|
||||
},
|
||||
// We don't use the overload with three arguments.
|
||||
measure(label, markName) {
|
||||
if (markName !== activeMeasure.markName) {
|
||||
throw new Error('Unexpected measure() call.');
|
||||
}
|
||||
// Step one level up
|
||||
activeMeasure.label = label;
|
||||
activeMeasure = activeMeasure.parent;
|
||||
knownMeasures.add(label);
|
||||
},
|
||||
clearMarks(markName) {
|
||||
if (markName === activeMeasure.markName) {
|
||||
// Step one level up if we're in this measure
|
||||
activeMeasure = activeMeasure.parent;
|
||||
activeMeasure.children.length--;
|
||||
}
|
||||
knownMarks.delete(markName);
|
||||
},
|
||||
clearMeasures(label) {
|
||||
knownMeasures.delete(label);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
resetFlamechart();
|
||||
global.performance = createUserTimingPolyfill();
|
||||
|
||||
// Import after the polyfill is set up:
|
||||
React = require('React');
|
||||
ReactCoroutine = require('ReactCoroutine');
|
||||
ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
ReactNoop = require('ReactNoop');
|
||||
ReactPortal = require('ReactPortal');
|
||||
ReactFeatureFlags.disableNewFiberFeatures = false;
|
||||
PropTypes = require('prop-types');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete global.performance;
|
||||
});
|
||||
|
||||
function Parent(props) {
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
|
||||
function Child(props) {
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
|
||||
it('measures a simple reconciliation', () => {
|
||||
ReactNoop.render(<Parent><Child /></Parent>);
|
||||
addComment('Mount');
|
||||
ReactNoop.flush();
|
||||
|
||||
ReactNoop.render(<Parent><Child /></Parent>);
|
||||
addComment('Update');
|
||||
ReactNoop.flush();
|
||||
|
||||
ReactNoop.render(null);
|
||||
addComment('Unmount');
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('skips parents during setState', () => {
|
||||
class A extends React.Component {
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class B extends React.Component {
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let a;
|
||||
let b;
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
<Parent>
|
||||
<Parent>
|
||||
<A ref={inst => (a = inst)} />
|
||||
</Parent>
|
||||
</Parent>
|
||||
<Parent>
|
||||
<B ref={inst => (b = inst)} />
|
||||
</Parent>
|
||||
</Parent>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
resetFlamechart();
|
||||
|
||||
a.setState({});
|
||||
b.setState({});
|
||||
addComment('Should include just A and B, no Parents');
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('warns on cascading renders from setState', () => {
|
||||
class Cascading extends React.Component {
|
||||
componentDidMount() {
|
||||
this.setState({});
|
||||
}
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Parent><Cascading /></Parent>);
|
||||
addComment('Should print a warning');
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('warns on cascading renders from top-level render', () => {
|
||||
class Cascading extends React.Component {
|
||||
componentDidMount() {
|
||||
ReactNoop.renderToRootWithID(<Child />, 'b');
|
||||
addComment('Scheduling another root from componentDidMount');
|
||||
ReactNoop.flush();
|
||||
}
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.renderToRootWithID(<Cascading />, 'a');
|
||||
addComment('Rendering the first root');
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('does not treat setState from cWM or cWRP as cascading', () => {
|
||||
class NotCascading extends React.Component {
|
||||
componentWillMount() {
|
||||
this.setState({});
|
||||
}
|
||||
componentWillReceiveProps() {
|
||||
this.setState({});
|
||||
}
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Parent><NotCascading /></Parent>);
|
||||
addComment('Should not print a warning');
|
||||
ReactNoop.flush();
|
||||
ReactNoop.render(<Parent><NotCascading /></Parent>);
|
||||
addComment('Should not print a warning');
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('captures all lifecycles', () => {
|
||||
class AllLifecycles extends React.Component {
|
||||
static childContextTypes = {
|
||||
foo: PropTypes.any,
|
||||
};
|
||||
shouldComponentUpdate() {
|
||||
return true;
|
||||
}
|
||||
getChildContext() {
|
||||
return {foo: 42};
|
||||
}
|
||||
componentWillMount() {}
|
||||
componentDidMount() {}
|
||||
componentWillReceiveProps() {}
|
||||
componentWillUpdate() {}
|
||||
componentDidUpdate() {}
|
||||
componentWillUnmount() {}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
ReactNoop.render(<AllLifecycles />);
|
||||
addComment('Mount');
|
||||
ReactNoop.flush();
|
||||
ReactNoop.render(<AllLifecycles />);
|
||||
addComment('Update');
|
||||
ReactNoop.flush();
|
||||
ReactNoop.render(null);
|
||||
addComment('Unmount');
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('measures deprioritized work', () => {
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
<div hidden={true}>
|
||||
<Child />
|
||||
</div>
|
||||
</Parent>,
|
||||
);
|
||||
});
|
||||
addComment('Flush the parent');
|
||||
ReactNoop.flushAnimationPri();
|
||||
addComment('Flush the child');
|
||||
ReactNoop.flushDeferredPri();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('measures deferred work in chunks', () => {
|
||||
class A extends React.Component {
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class B extends React.Component {
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
<A>
|
||||
<Child />
|
||||
</A>
|
||||
<B>
|
||||
<Child />
|
||||
</B>
|
||||
</Parent>,
|
||||
);
|
||||
addComment('Start mounting Parent and A');
|
||||
ReactNoop.flushDeferredPri(40);
|
||||
addComment('Mount B just a little (but not enough to memoize)');
|
||||
ReactNoop.flushDeferredPri(10);
|
||||
addComment('Complete B and Parent');
|
||||
ReactNoop.flushDeferredPri();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('recovers from fatal errors', () => {
|
||||
function Baddie() {
|
||||
throw new Error('Game over');
|
||||
}
|
||||
|
||||
ReactNoop.render(<Parent><Baddie /></Parent>);
|
||||
try {
|
||||
addComment('Will fatal');
|
||||
ReactNoop.flush();
|
||||
} catch (err) {
|
||||
expect(err.message).toBe('Game over');
|
||||
}
|
||||
ReactNoop.render(<Parent><Child /></Parent>);
|
||||
addComment('Will reconcile from a clean state');
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('recovers from caught errors', () => {
|
||||
function Baddie() {
|
||||
throw new Error('Game over');
|
||||
}
|
||||
|
||||
function ErrorReport() {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
class Boundary extends React.Component {
|
||||
state = {error: null};
|
||||
unstable_handleError(error) {
|
||||
this.setState({error});
|
||||
}
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return <ErrorReport />;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
<Boundary>
|
||||
<Parent>
|
||||
<Baddie />
|
||||
</Parent>
|
||||
</Boundary>
|
||||
</Parent>,
|
||||
);
|
||||
addComment('Stop on Baddie and restart from Boundary');
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('deduplicates lifecycle names during commit to reduce overhead', () => {
|
||||
class A extends React.Component {
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
class B extends React.Component {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.cascade && !prevProps.cascade) {
|
||||
this.setState({});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
<A />
|
||||
<B />
|
||||
<A />
|
||||
<B />
|
||||
</Parent>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
resetFlamechart();
|
||||
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
<A />
|
||||
<B />
|
||||
<A />
|
||||
<B />
|
||||
</Parent>,
|
||||
);
|
||||
addComment('The commit phase should mention A and B just once');
|
||||
ReactNoop.flush();
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
<A />
|
||||
<B />
|
||||
<A />
|
||||
<B cascade={true} />
|
||||
</Parent>,
|
||||
);
|
||||
addComment("Because of deduplication, we don't know B was cascading,");
|
||||
addComment('but we should still see the warning for the commit phase.');
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('supports coroutines', () => {
|
||||
function Continuation({isSame}) {
|
||||
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
|
||||
}
|
||||
|
||||
function CoChild({bar}) {
|
||||
return ReactCoroutine.createYield({
|
||||
props: {
|
||||
bar: bar,
|
||||
},
|
||||
continuation: Continuation,
|
||||
});
|
||||
}
|
||||
|
||||
function Indirection() {
|
||||
return [<CoChild key="a" bar={true} />, <CoChild key="b" bar={false} />];
|
||||
}
|
||||
|
||||
function HandleYields(props, yields) {
|
||||
return yields.map((y, i) => (
|
||||
<y.continuation key={i} isSame={props.foo === y.props.bar} />
|
||||
));
|
||||
}
|
||||
|
||||
function CoParent(props) {
|
||||
return ReactCoroutine.createCoroutine(
|
||||
props.children,
|
||||
HandleYields,
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return <div><CoParent foo={true}><Indirection /></CoParent></div>;
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('supports portals', () => {
|
||||
const noopContainer = {children: []};
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
{ReactPortal.createPortal(<Child />, noopContainer, null)}
|
||||
</Parent>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(getFlameChart()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,288 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNoop;
|
||||
var ReactFeatureFlags;
|
||||
|
||||
describe('ReactIncrementalReflection', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactNoop = require('ReactNoop');
|
||||
ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
ReactFeatureFlags.disableNewFiberFeatures = false;
|
||||
});
|
||||
|
||||
it('handles isMounted even when the initial render is deferred', () => {
|
||||
let ops = [];
|
||||
|
||||
const instances = [];
|
||||
|
||||
class Component extends React.Component {
|
||||
_isMounted() {
|
||||
// No longer a public API, but we can test that it works internally by
|
||||
// reaching into the updater.
|
||||
return this.updater.isMounted(this);
|
||||
}
|
||||
componentWillMount() {
|
||||
instances.push(this);
|
||||
ops.push('componentWillMount', this._isMounted());
|
||||
}
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount', this._isMounted());
|
||||
}
|
||||
render() {
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
|
||||
function Foo() {
|
||||
return <Component />;
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
// Render part way through but don't yet commit the updates.
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
|
||||
expect(ops).toEqual(['componentWillMount', false]);
|
||||
|
||||
expect(instances[0]._isMounted()).toBe(false);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Render the rest and commit the updates.
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['componentDidMount', true]);
|
||||
|
||||
expect(instances[0]._isMounted()).toBe(true);
|
||||
});
|
||||
|
||||
it('handles isMounted when an unmount is deferred', () => {
|
||||
let ops = [];
|
||||
|
||||
const instances = [];
|
||||
|
||||
class Component extends React.Component {
|
||||
_isMounted() {
|
||||
return this.updater.isMounted(this);
|
||||
}
|
||||
componentWillMount() {
|
||||
instances.push(this);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('componentWillUnmount', this._isMounted());
|
||||
}
|
||||
render() {
|
||||
ops.push('Component');
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
|
||||
function Other() {
|
||||
ops.push('Other');
|
||||
return <span />;
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
return props.mount ? <Component /> : <Other />;
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo mount={true} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Component']);
|
||||
ops = [];
|
||||
|
||||
expect(instances[0]._isMounted()).toBe(true);
|
||||
|
||||
ReactNoop.render(<Foo mount={false} />);
|
||||
// Render part way through but don't yet commit the updates so it is not
|
||||
// fully unmounted yet.
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
|
||||
expect(ops).toEqual(['Other']);
|
||||
ops = [];
|
||||
|
||||
expect(instances[0]._isMounted()).toBe(true);
|
||||
|
||||
// Finish flushing the unmount.
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['componentWillUnmount', true]);
|
||||
|
||||
expect(instances[0]._isMounted()).toBe(false);
|
||||
});
|
||||
|
||||
it('finds no node before insertion and correct node before deletion', () => {
|
||||
let ops = [];
|
||||
|
||||
let classInstance = null;
|
||||
|
||||
class Component extends React.Component {
|
||||
componentWillMount() {
|
||||
classInstance = this;
|
||||
ops.push('componentWillMount', ReactNoop.findInstance(this));
|
||||
}
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount', ReactNoop.findInstance(this));
|
||||
}
|
||||
componentWillUpdate() {
|
||||
ops.push('componentWillUpdate', ReactNoop.findInstance(this));
|
||||
}
|
||||
componentDidUpdate() {
|
||||
ops.push('componentDidUpdate', ReactNoop.findInstance(this));
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('componentWillUnmount', ReactNoop.findInstance(this));
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
return this.props.step < 2
|
||||
? <span ref={ref => (this.span = ref)} />
|
||||
: this.props.step === 2
|
||||
? <div ref={ref => (this.div = ref)} />
|
||||
: this.props.step === 3
|
||||
? null
|
||||
: this.props.step === 4
|
||||
? <div ref={ref => (this.span = ref)} />
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
function Sibling() {
|
||||
// Sibling is used to assert that we've rendered past the first component.
|
||||
ops.push('render sibling');
|
||||
return <span />;
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
return [<Component key="a" step={props.step} />, <Sibling key="b" />];
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo step={0} />);
|
||||
// Flush past Component but don't complete rendering everything yet.
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillMount',
|
||||
null,
|
||||
'render',
|
||||
'render sibling',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
expect(classInstance).toBeDefined();
|
||||
// The instance has been complete but is still not committed so it should
|
||||
// not find any host nodes in it.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(null);
|
||||
|
||||
ReactNoop.flush();
|
||||
|
||||
const hostSpan = classInstance.span;
|
||||
expect(hostSpan).toBeDefined();
|
||||
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
|
||||
|
||||
expect(ops).toEqual(['componentDidMount', hostSpan]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Flush next step which will cause an update but not yet render a new host
|
||||
// node.
|
||||
ReactNoop.render(<Foo step={1} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillUpdate',
|
||||
hostSpan,
|
||||
'render',
|
||||
'render sibling',
|
||||
'componentDidUpdate',
|
||||
hostSpan,
|
||||
]);
|
||||
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
|
||||
|
||||
ops = [];
|
||||
|
||||
// The next step will render a new host node but won't get committed yet.
|
||||
// We expect this to mutate the original Fiber.
|
||||
ReactNoop.render(<Foo step={2} />);
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillUpdate',
|
||||
hostSpan,
|
||||
'render',
|
||||
'render sibling',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// This should still be the host span.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
|
||||
|
||||
// When we finally flush the tree it will get committed.
|
||||
ReactNoop.flush();
|
||||
|
||||
const hostDiv = classInstance.div;
|
||||
|
||||
expect(hostDiv).toBeDefined();
|
||||
expect(hostSpan).not.toBe(hostDiv);
|
||||
|
||||
expect(ops).toEqual(['componentDidUpdate', hostDiv]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// We should now find the new host node.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
|
||||
|
||||
// Render to null but don't commit it yet.
|
||||
ReactNoop.render(<Foo step={3} />);
|
||||
ReactNoop.flushDeferredPri(25);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillUpdate',
|
||||
hostDiv,
|
||||
'render',
|
||||
'render sibling',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// This should still be the host div since the deletion is not committed.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
|
||||
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['componentDidUpdate', null]);
|
||||
|
||||
// This should still be the host div since the deletion is not committed.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(null);
|
||||
|
||||
// Render a div again
|
||||
ReactNoop.render(<Foo step={4} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
ops = [];
|
||||
|
||||
// Unmount the component.
|
||||
ReactNoop.render([]);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual(['componentWillUnmount', hostDiv]);
|
||||
});
|
||||
});
|
@ -1,409 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNoop;
|
||||
var ReactFeatureFlags;
|
||||
|
||||
describe('ReactIncrementalScheduling', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactNoop = require('ReactNoop');
|
||||
ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
ReactFeatureFlags.disableNewFiberFeatures = false;
|
||||
});
|
||||
|
||||
function span(prop) {
|
||||
return {type: 'span', children: [], prop};
|
||||
}
|
||||
|
||||
it('schedules and flushes deferred work', () => {
|
||||
ReactNoop.render(<span prop="1" />);
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
ReactNoop.flushDeferredPri();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('1')]);
|
||||
});
|
||||
|
||||
it('schedules and flushes animation work', () => {
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
ReactNoop.render(<span prop="1" />);
|
||||
});
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
ReactNoop.flushAnimationPri();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('1')]);
|
||||
});
|
||||
|
||||
it('searches for work on other roots once the current root completes', () => {
|
||||
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
|
||||
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
|
||||
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
|
||||
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
|
||||
});
|
||||
|
||||
it('schedules an animation callback when there`\s leftover animation work', () => {
|
||||
class Foo extends React.Component {
|
||||
state = {step: 0};
|
||||
componentDidMount() {
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
this.setState({step: 2});
|
||||
});
|
||||
this.setState({step: 1});
|
||||
}
|
||||
render() {
|
||||
return <span prop={this.state.step} />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
// Flush just enough work to mount the component, but not enough to flush
|
||||
// the animation update.
|
||||
ReactNoop.flushDeferredPri(25);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1)]);
|
||||
|
||||
// There's more animation work. A callback should have been scheduled.
|
||||
ReactNoop.flushAnimationPri();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(2)]);
|
||||
});
|
||||
|
||||
it('schedules top-level updates in order of priority', () => {
|
||||
// Initial render.
|
||||
ReactNoop.render(<span prop={1} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1)]);
|
||||
|
||||
ReactNoop.render(<span prop={5} />);
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
ReactNoop.render(<span prop={2} />);
|
||||
ReactNoop.render(<span prop={3} />);
|
||||
ReactNoop.render(<span prop={4} />);
|
||||
});
|
||||
|
||||
// The low pri update should be flushed last, even though it was scheduled
|
||||
// before the animation updates.
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(5)]);
|
||||
});
|
||||
|
||||
it('schedules top-level updates with same priority in order of insertion', () => {
|
||||
// Initial render.
|
||||
ReactNoop.render(<span prop={1} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1)]);
|
||||
|
||||
ReactNoop.render(<span prop={2} />);
|
||||
ReactNoop.render(<span prop={3} />);
|
||||
ReactNoop.render(<span prop={4} />);
|
||||
ReactNoop.render(<span prop={5} />);
|
||||
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(5)]);
|
||||
});
|
||||
|
||||
it('works on deferred roots in the order they were scheduled', () => {
|
||||
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
|
||||
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
|
||||
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
|
||||
|
||||
// Schedule deferred work in the reverse order
|
||||
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
|
||||
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
|
||||
// Ensure it starts in the order it was scheduled
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
// Schedule last bit of work, it will get processed the last
|
||||
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
|
||||
// Keep performing work in the order it was scheduled
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
});
|
||||
|
||||
it('schedules sync updates when inside componentDidMount/Update', () => {
|
||||
var instance;
|
||||
var ops = [];
|
||||
|
||||
class Foo extends React.Component {
|
||||
state = {tick: 0};
|
||||
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount (before setState): ' + this.state.tick);
|
||||
this.setState({tick: 1});
|
||||
// We're in a batch. Update hasn't flushed yet.
|
||||
ops.push('componentDidMount (after setState): ' + this.state.tick);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
ops.push('componentDidUpdate: ' + this.state.tick);
|
||||
if (this.state.tick === 2) {
|
||||
ops.push('componentDidUpdate (before setState): ' + this.state.tick);
|
||||
this.setState({tick: 3});
|
||||
ops.push('componentDidUpdate (after setState): ' + this.state.tick);
|
||||
// We're in a batch. Update hasn't flushed yet.
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
ops.push('render: ' + this.state.tick);
|
||||
instance = this;
|
||||
return <span prop={this.state.tick} />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
ReactNoop.flushDeferredPri(20 + 5);
|
||||
expect(ops).toEqual([
|
||||
'render: 0',
|
||||
'componentDidMount (before setState): 0',
|
||||
'componentDidMount (after setState): 0',
|
||||
// If the setState inside componentDidMount were deferred, there would be
|
||||
// no more ops. Because it has Task priority, we get these ops, too:
|
||||
'render: 1',
|
||||
'componentDidUpdate: 1',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
instance.setState({tick: 2});
|
||||
ReactNoop.flushDeferredPri(20 + 5);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'render: 2',
|
||||
'componentDidUpdate: 2',
|
||||
'componentDidUpdate (before setState): 2',
|
||||
'componentDidUpdate (after setState): 2',
|
||||
// If the setState inside componentDidUpdate were deferred, there would be
|
||||
// no more ops. Because it has Task priority, we get these ops, too:
|
||||
'render: 3',
|
||||
'componentDidUpdate: 3',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can opt-in to deferred/animation scheduling inside componentDidMount/Update', () => {
|
||||
var instance;
|
||||
var ops = [];
|
||||
|
||||
class Foo extends React.Component {
|
||||
state = {tick: 0};
|
||||
|
||||
componentDidMount() {
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
ops.push('componentDidMount (before setState): ' + this.state.tick);
|
||||
this.setState({tick: 1});
|
||||
ops.push('componentDidMount (after setState): ' + this.state.tick);
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
ops.push('componentDidUpdate: ' + this.state.tick);
|
||||
if (this.state.tick === 2) {
|
||||
ops.push(
|
||||
'componentDidUpdate (before setState): ' + this.state.tick,
|
||||
);
|
||||
this.setState({tick: 3});
|
||||
ops.push('componentDidUpdate (after setState): ' + this.state.tick);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
ops.push('render: ' + this.state.tick);
|
||||
instance = this;
|
||||
return <span prop={this.state.tick} />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
ReactNoop.flushDeferredPri(20 + 5);
|
||||
expect(ops).toEqual([
|
||||
'render: 0',
|
||||
'componentDidMount (before setState): 0',
|
||||
'componentDidMount (after setState): 0',
|
||||
// Following items shouldn't appear because they are the result of an
|
||||
// update scheduled with animation priority
|
||||
// 'render: 1',
|
||||
// 'componentDidUpdate: 1',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.flushAnimationPri();
|
||||
expect(ops).toEqual(['render: 1', 'componentDidUpdate: 1']);
|
||||
|
||||
ops = [];
|
||||
instance.setState({tick: 2});
|
||||
ReactNoop.flushDeferredPri(20 + 5);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'render: 2',
|
||||
'componentDidUpdate: 2',
|
||||
'componentDidUpdate (before setState): 2',
|
||||
'componentDidUpdate (after setState): 2',
|
||||
// Following items shouldn't appear because they are the result of an
|
||||
// update scheduled with animation priority
|
||||
// 'render: 3',
|
||||
// 'componentDidUpdate: 3',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.flushAnimationPri();
|
||||
expect(ops).toEqual(['render: 3', 'componentDidUpdate: 3']);
|
||||
});
|
||||
|
||||
it('performs Task work even after time runs out', () => {
|
||||
class Foo extends React.Component {
|
||||
state = {step: 1};
|
||||
componentDidMount() {
|
||||
this.setState({step: 2}, () => {
|
||||
this.setState({step: 3}, () => {
|
||||
this.setState({step: 4}, () => {
|
||||
this.setState({step: 5});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return <span prop={this.state.step} />;
|
||||
}
|
||||
}
|
||||
ReactNoop.render(<Foo />);
|
||||
// This should be just enough to complete all the work, but not enough to
|
||||
// commit it.
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
// Do one more unit of work.
|
||||
ReactNoop.flushDeferredPri(10);
|
||||
// The updates should all be flushed with Task priority
|
||||
expect(ReactNoop.getChildren()).toEqual([span(5)]);
|
||||
});
|
||||
|
||||
it('does not perform animation work after time runs out', () => {
|
||||
class Foo extends React.Component {
|
||||
state = {step: 1};
|
||||
componentDidMount() {
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
this.setState({step: 2}, () => {
|
||||
this.setState({step: 3}, () => {
|
||||
this.setState({step: 4}, () => {
|
||||
this.setState({step: 5});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return <span prop={this.state.step} />;
|
||||
}
|
||||
}
|
||||
ReactNoop.render(<Foo />);
|
||||
// This should be just enough to complete all the work, but not enough to
|
||||
// commit it.
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
// Do one more unit of work.
|
||||
ReactNoop.flushDeferredPri(10);
|
||||
// None of the updates should be flushed because they only have
|
||||
// animation priority.
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1)]);
|
||||
});
|
||||
|
||||
it('can opt-out of batching using unbatchedUpdates', () => {
|
||||
// syncUpdates gives synchronous priority to updates
|
||||
ReactNoop.syncUpdates(() => {
|
||||
// batchedUpdates downgrades sync updates to task priority
|
||||
ReactNoop.batchedUpdates(() => {
|
||||
ReactNoop.render(<span prop={0} />);
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
// Should not have flushed yet because we're still batching
|
||||
|
||||
// unbatchedUpdates reverses the effect of batchedUpdates, so sync
|
||||
// updates are not batched
|
||||
ReactNoop.unbatchedUpdates(() => {
|
||||
ReactNoop.render(<span prop={1} />);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1)]);
|
||||
ReactNoop.render(<span prop={2} />);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(2)]);
|
||||
});
|
||||
|
||||
ReactNoop.render(<span prop={3} />);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(2)]);
|
||||
});
|
||||
// Remaining update is now flushed
|
||||
expect(ReactNoop.getChildren()).toEqual([span(3)]);
|
||||
});
|
||||
});
|
||||
|
||||
it('nested updates are always deferred, even inside unbatchedUpdates', () => {
|
||||
let instance;
|
||||
let ops = [];
|
||||
class Foo extends React.Component {
|
||||
state = {step: 0};
|
||||
componentDidUpdate() {
|
||||
ops.push('componentDidUpdate: ' + this.state.step);
|
||||
if (this.state.step === 1) {
|
||||
ReactNoop.unbatchedUpdates(() => {
|
||||
// This is a nested state update, so it should not be
|
||||
// flushed synchronously, even though we wrapped it
|
||||
// in unbatchedUpdates.
|
||||
this.setState({step: 2});
|
||||
});
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1)]);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
ops.push('render: ' + this.state.step);
|
||||
instance = this;
|
||||
return <span prop={this.state.step} />;
|
||||
}
|
||||
}
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(0)]);
|
||||
|
||||
ReactNoop.syncUpdates(() => {
|
||||
instance.setState({step: 1});
|
||||
expect(ReactNoop.getChildren()).toEqual([span(2)]);
|
||||
});
|
||||
|
||||
expect(ops).toEqual([
|
||||
'render: 0',
|
||||
'render: 1',
|
||||
'componentDidUpdate: 1',
|
||||
'render: 2',
|
||||
'componentDidUpdate: 2',
|
||||
]);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -1,376 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNoop;
|
||||
var ReactFeatureFlags;
|
||||
|
||||
describe('ReactIncrementalUpdates', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModuleRegistry();
|
||||
React = require('react');
|
||||
ReactNoop = require('ReactNoop');
|
||||
ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
ReactFeatureFlags.disableNewFiberFeatures = false;
|
||||
});
|
||||
|
||||
it('applies updates in order of priority', () => {
|
||||
let state;
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
componentDidMount() {
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
// Has Animation priority
|
||||
this.setState({b: 'b'});
|
||||
this.setState({c: 'c'});
|
||||
});
|
||||
// Has Task priority
|
||||
this.setState({a: 'a'});
|
||||
}
|
||||
render() {
|
||||
state = this.state;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flushDeferredPri(25);
|
||||
expect(state).toEqual({a: 'a'});
|
||||
ReactNoop.flush();
|
||||
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
|
||||
});
|
||||
|
||||
it('applies updates with equal priority in insertion order', () => {
|
||||
let state;
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
componentDidMount() {
|
||||
// All have Task priority
|
||||
this.setState({a: 'a'});
|
||||
this.setState({b: 'b'});
|
||||
this.setState({c: 'c'});
|
||||
}
|
||||
render() {
|
||||
state = this.state;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
|
||||
});
|
||||
|
||||
it('only drops updates with equal or lesser priority when replaceState is called', () => {
|
||||
let instance;
|
||||
let ops = [];
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount');
|
||||
}
|
||||
componentDidUpdate() {
|
||||
ops.push('componentDidUpdate');
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
instance = this;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
|
||||
instance.setState({x: 'x'});
|
||||
instance.setState({y: 'y'});
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
instance.setState({a: 'a'});
|
||||
instance.setState({b: 'b'});
|
||||
});
|
||||
instance.updater.enqueueReplaceState(instance, {c: 'c'});
|
||||
instance.setState({d: 'd'});
|
||||
|
||||
ReactNoop.flushAnimationPri();
|
||||
// Even though a replaceState has been already scheduled, it hasn't been
|
||||
// flushed yet because it has low priority.
|
||||
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
||||
expect(ops).toEqual([
|
||||
'render',
|
||||
'componentDidMount',
|
||||
'render',
|
||||
'componentDidUpdate',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.flush();
|
||||
// Now the rest of the updates are flushed.
|
||||
expect(instance.state).toEqual({c: 'c', d: 'd'});
|
||||
expect(ops).toEqual(['render', 'componentDidUpdate']);
|
||||
});
|
||||
|
||||
it('can abort an update, schedule additional updates, and resume', () => {
|
||||
let instance;
|
||||
let ops = [];
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
componentDidUpdate() {
|
||||
ops.push('componentDidUpdate');
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
instance = this;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
|
||||
ops = [];
|
||||
|
||||
let progressedUpdates = [];
|
||||
function createUpdate(letter) {
|
||||
return () => {
|
||||
progressedUpdates.push(letter);
|
||||
return {
|
||||
[letter]: letter,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
instance.setState(createUpdate('a'));
|
||||
instance.setState(createUpdate('b'));
|
||||
instance.setState(createUpdate('c'));
|
||||
|
||||
// Do just enough work to begin the update but not enough to flush it
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
// expect(ReactNoop.getChildren()).toEqual([span('')]);
|
||||
expect(ops).toEqual(['render']);
|
||||
expect(progressedUpdates).toEqual(['a', 'b', 'c']);
|
||||
expect(instance.state).toEqual({a: 'a', b: 'b', c: 'c'});
|
||||
|
||||
ops = [];
|
||||
progressedUpdates = [];
|
||||
|
||||
instance.setState(createUpdate('f'));
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
instance.setState(createUpdate('d'));
|
||||
instance.setState(createUpdate('e'));
|
||||
});
|
||||
instance.setState(createUpdate('g'));
|
||||
|
||||
ReactNoop.flushAnimationPri();
|
||||
expect(ops).toEqual([
|
||||
// Flushes animation work (d and e)
|
||||
'render',
|
||||
'componentDidUpdate',
|
||||
]);
|
||||
ops = [];
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
// Flushes deferred work (f and g)
|
||||
'render',
|
||||
'componentDidUpdate',
|
||||
]);
|
||||
expect(progressedUpdates).toEqual(['d', 'e', 'a', 'b', 'c', 'f', 'g']);
|
||||
expect(instance.state).toEqual({
|
||||
a: 'a',
|
||||
b: 'b',
|
||||
c: 'c',
|
||||
d: 'd',
|
||||
e: 'e',
|
||||
f: 'f',
|
||||
g: 'g',
|
||||
});
|
||||
});
|
||||
|
||||
it('can abort an update, schedule a replaceState, and resume', () => {
|
||||
let instance;
|
||||
let ops = [];
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
componentDidUpdate() {
|
||||
ops.push('componentDidUpdate');
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
instance = this;
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
ops = [];
|
||||
|
||||
let progressedUpdates = [];
|
||||
function createUpdate(letter) {
|
||||
return () => {
|
||||
progressedUpdates.push(letter);
|
||||
return {
|
||||
[letter]: letter,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
instance.setState(createUpdate('a'));
|
||||
instance.setState(createUpdate('b'));
|
||||
instance.setState(createUpdate('c'));
|
||||
|
||||
// Do just enough work to begin the update but not enough to flush it
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
expect(ops).toEqual(['render']);
|
||||
expect(progressedUpdates).toEqual(['a', 'b', 'c']);
|
||||
expect(instance.state).toEqual({a: 'a', b: 'b', c: 'c'});
|
||||
|
||||
ops = [];
|
||||
progressedUpdates = [];
|
||||
|
||||
instance.setState(createUpdate('f'));
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
instance.setState(createUpdate('d'));
|
||||
// No longer a public API, but we can test that it works internally by
|
||||
// reaching into the updater.
|
||||
instance.updater.enqueueReplaceState(instance, createUpdate('e'));
|
||||
});
|
||||
instance.setState(createUpdate('g'));
|
||||
|
||||
ReactNoop.flush();
|
||||
expect(progressedUpdates).toEqual(['d', 'e', 'f', 'g']);
|
||||
expect(instance.state).toEqual({e: 'e', f: 'f', g: 'g'});
|
||||
});
|
||||
|
||||
it('passes accumulation of previous updates to replaceState updater function', () => {
|
||||
let instance;
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
render() {
|
||||
instance = this;
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
|
||||
instance.setState({a: 'a'});
|
||||
instance.setState({b: 'b'});
|
||||
// No longer a public API, but we can test that it works internally by
|
||||
// reaching into the updater.
|
||||
instance.updater.enqueueReplaceState(instance, previousState => ({
|
||||
previousState,
|
||||
}));
|
||||
ReactNoop.flush();
|
||||
expect(instance.state).toEqual({previousState: {a: 'a', b: 'b'}});
|
||||
});
|
||||
|
||||
it('does not call callbacks that are scheduled by another callback until a later commit', () => {
|
||||
let ops = [];
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
componentDidMount() {
|
||||
ops.push('did mount');
|
||||
this.setState({a: 'a'}, () => {
|
||||
ops.push('callback a');
|
||||
this.setState({b: 'b'}, () => {
|
||||
ops.push('callback b');
|
||||
});
|
||||
});
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'render',
|
||||
'did mount',
|
||||
'render',
|
||||
'callback a',
|
||||
'render',
|
||||
'callback b',
|
||||
]);
|
||||
});
|
||||
|
||||
it('gives setState during reconciliation the same priority as whatever level is currently reconciling', () => {
|
||||
let instance;
|
||||
let ops = [];
|
||||
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
componentWillReceiveProps() {
|
||||
ops.push('componentWillReceiveProps');
|
||||
this.setState({b: 'b'});
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
instance = this;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
instance.setState({a: 'a'});
|
||||
ReactNoop.render(<Foo />); // Trigger componentWillReceiveProps
|
||||
});
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
||||
expect(ops).toEqual(['componentWillReceiveProps', 'render']);
|
||||
});
|
||||
|
||||
it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
|
||||
spyOn(console, 'error');
|
||||
let instance;
|
||||
let ops = [];
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
render() {
|
||||
ops.push('render');
|
||||
instance = this;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
|
||||
instance.setState(function a() {
|
||||
ops.push('setState updater');
|
||||
this.setState({b: 'b'});
|
||||
return {a: 'a'};
|
||||
});
|
||||
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
// Initial render
|
||||
'render',
|
||||
'setState updater',
|
||||
// Update b is enqueued with the same priority as update a, so it should
|
||||
// be flushed in the same commit.
|
||||
'render',
|
||||
]);
|
||||
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
console.error.calls.reset();
|
||||
});
|
||||
});
|
@ -1,163 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNoop;
|
||||
var ReactFeatureFlags;
|
||||
|
||||
// This is a new feature in Fiber so I put it in its own test file. It could
|
||||
// probably move to one of the other test files once it is official.
|
||||
describe('ReactTopLevelFragment', function() {
|
||||
beforeEach(function() {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactNoop = require('ReactNoop');
|
||||
ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
ReactFeatureFlags.disableNewFiberFeatures = false;
|
||||
});
|
||||
|
||||
it('should render a simple fragment at the top of a component', function() {
|
||||
function Fragment() {
|
||||
return [<div key="a">Hello</div>, <div key="b">World</div>];
|
||||
}
|
||||
ReactNoop.render(<Fragment />);
|
||||
ReactNoop.flush();
|
||||
});
|
||||
|
||||
it('should preserve state when switching from a single child', function() {
|
||||
var instance = null;
|
||||
|
||||
class Stateful extends React.Component {
|
||||
render() {
|
||||
instance = this;
|
||||
return <div>Hello</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Fragment({condition}) {
|
||||
return condition
|
||||
? <Stateful key="a" />
|
||||
: [<Stateful key="a" />, <div key="b">World</div>];
|
||||
}
|
||||
ReactNoop.render(<Fragment />);
|
||||
ReactNoop.flush();
|
||||
|
||||
var instanceA = instance;
|
||||
|
||||
expect(instanceA).not.toBe(null);
|
||||
|
||||
ReactNoop.render(<Fragment condition={true} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
var instanceB = instance;
|
||||
|
||||
expect(instanceB).toBe(instanceA);
|
||||
});
|
||||
|
||||
it('should not preserve state when switching to a nested array', function() {
|
||||
var instance = null;
|
||||
|
||||
class Stateful extends React.Component {
|
||||
render() {
|
||||
instance = this;
|
||||
return <div>Hello</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Fragment({condition}) {
|
||||
return condition
|
||||
? <Stateful key="a" />
|
||||
: [[<Stateful key="a" />, <div key="b">World</div>], <div key="c" />];
|
||||
}
|
||||
ReactNoop.render(<Fragment />);
|
||||
ReactNoop.flush();
|
||||
|
||||
var instanceA = instance;
|
||||
|
||||
expect(instanceA).not.toBe(null);
|
||||
|
||||
ReactNoop.render(<Fragment condition={true} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
var instanceB = instance;
|
||||
|
||||
expect(instanceB).not.toBe(instanceA);
|
||||
});
|
||||
|
||||
it('preserves state if an implicit key slot switches from/to null', function() {
|
||||
var instance = null;
|
||||
|
||||
class Stateful extends React.Component {
|
||||
render() {
|
||||
instance = this;
|
||||
return <div>World</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Fragment({condition}) {
|
||||
return condition
|
||||
? [null, <Stateful key="a" />]
|
||||
: [<div key="b">Hello</div>, <Stateful key="a" />];
|
||||
}
|
||||
ReactNoop.render(<Fragment />);
|
||||
ReactNoop.flush();
|
||||
|
||||
var instanceA = instance;
|
||||
|
||||
expect(instanceA).not.toBe(null);
|
||||
|
||||
ReactNoop.render(<Fragment condition={true} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
var instanceB = instance;
|
||||
|
||||
expect(instanceB).toBe(instanceA);
|
||||
|
||||
ReactNoop.render(<Fragment condition={false} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
var instanceC = instance;
|
||||
|
||||
expect(instanceC === instanceA).toBe(true);
|
||||
});
|
||||
|
||||
it('should preserve state in a reorder', function() {
|
||||
var instance = null;
|
||||
|
||||
class Stateful extends React.Component {
|
||||
render() {
|
||||
instance = this;
|
||||
return <div>Hello</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Fragment({condition}) {
|
||||
return condition
|
||||
? [[<div key="b">World</div>, <Stateful key="a" />]]
|
||||
: [[<Stateful key="a" />, <div key="b">World</div>], <div key="c" />];
|
||||
}
|
||||
ReactNoop.render(<Fragment />);
|
||||
ReactNoop.flush();
|
||||
|
||||
var instanceA = instance;
|
||||
|
||||
expect(instanceA).not.toBe(null);
|
||||
|
||||
ReactNoop.render(<Fragment condition={true} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
var instanceB = instance;
|
||||
|
||||
expect(instanceB).toBe(instanceA);
|
||||
});
|
||||
});
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNoop;
|
||||
var ReactFeatureFlags;
|
||||
|
||||
// This is a new feature in Fiber so I put it in its own test file. It could
|
||||
// probably move to one of the other test files once it is official.
|
||||
describe('ReactTopLevelText', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactNoop = require('ReactNoop');
|
||||
ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
ReactFeatureFlags.disableNewFiberFeatures = false;
|
||||
});
|
||||
|
||||
it('should render a component returning strings directly from render', () => {
|
||||
const Text = ({value}) => value;
|
||||
ReactNoop.render(<Text value="foo" />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([{text: 'foo'}]);
|
||||
});
|
||||
|
||||
it('should render a component returning numbers directly from render', () => {
|
||||
const Text = ({value}) => value;
|
||||
ReactNoop.render(<Text value={10} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([{text: '10'}]);
|
||||
});
|
||||
});
|
@ -1,260 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
|
||||
"// Mount
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ AllLifecycles [mount]
|
||||
⚛ AllLifecycles.componentWillMount
|
||||
⚛ AllLifecycles.getChildContext
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⚛ AllLifecycles.componentDidMount
|
||||
|
||||
// Update
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ AllLifecycles [update]
|
||||
⚛ AllLifecycles.componentWillReceiveProps
|
||||
⚛ AllLifecycles.shouldComponentUpdate
|
||||
⚛ AllLifecycles.componentWillUpdate
|
||||
⚛ AllLifecycles.getChildContext
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
⚛ AllLifecycles.componentDidUpdate
|
||||
|
||||
// Unmount
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ AllLifecycles.componentWillUnmount
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = `
|
||||
"// The commit phase should mention A and B just once
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 9 Total)
|
||||
⚛ (Calling Lifecycle Methods: 9 Total)
|
||||
⚛ A.componentDidUpdate
|
||||
⚛ B.componentDidUpdate
|
||||
|
||||
// Because of deduplication, we don't know B was cascading,
|
||||
// but we should still see the warning for the commit phase.
|
||||
⛔ (React Tree Reconciliation) Warning: There were cascading updates
|
||||
⚛ Parent [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Host Effects: 9 Total)
|
||||
⚛ (Calling Lifecycle Methods: 9 Total)
|
||||
⚛ A.componentDidUpdate
|
||||
⚛ B.componentDidUpdate
|
||||
⚛ B [update]
|
||||
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
|
||||
⚛ (Committing Host Effects: 3 Total)
|
||||
⚛ (Calling Lifecycle Methods: 3 Total)
|
||||
⚛ B.componentDidUpdate
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = `
|
||||
"// Should not print a warning
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [mount]
|
||||
⚛ NotCascading [mount]
|
||||
⚛ NotCascading.componentWillMount
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
// Should not print a warning
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [update]
|
||||
⚛ NotCascading [update]
|
||||
⚛ NotCascading.componentWillReceiveProps
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
|
||||
"// Mount
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
// Update
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [update]
|
||||
⚛ Child [update]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
|
||||
// Unmount
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
|
||||
"// Start mounting Parent and A
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [mount]
|
||||
⚛ A [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
// Mount B just a little (but not enough to memoize)
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [mount]
|
||||
⚛ B [mount]
|
||||
|
||||
// Complete B and Parent
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [mount]
|
||||
⚛ B [mount]
|
||||
⚛ Child [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf measures deprioritized work 1`] = `
|
||||
"// Flush the parent
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
// Flush the child
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Child [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 3 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf recovers from caught errors 1`] = `
|
||||
"// Stop on Baddie and restart from Boundary
|
||||
⛔ (React Tree Reconciliation) Warning: There were cascading updates
|
||||
⚛ Parent [mount]
|
||||
⚛ Boundary [mount]
|
||||
⚛ Parent [mount]
|
||||
⚛ Baddie [mount]
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⚛ Boundary [update]
|
||||
⚛ ErrorReport [mount]
|
||||
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = `
|
||||
"// Will fatal
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [mount]
|
||||
⚛ Baddie [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
|
||||
// Will reconcile from a clean state
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
|
||||
"// Should include just A and B, no Parents
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 6 Total)
|
||||
⚛ (Calling Lifecycle Methods: 6 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf supports coroutines 1`] = `
|
||||
"⚛ (React Tree Reconciliation)
|
||||
⚛ App [mount]
|
||||
⚛ CoParent [mount]
|
||||
⚛ HandleYields [mount]
|
||||
⚛ Indirection [mount]
|
||||
⚛ CoChild [mount]
|
||||
⚛ CoChild [mount]
|
||||
⚛ Continuation [mount]
|
||||
⚛ Continuation [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 3 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf supports portals 1`] = `
|
||||
"⚛ (React Tree Reconciliation)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Host Effects: 3 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = `
|
||||
"// Should print a warning
|
||||
⛔ (React Tree Reconciliation) Warning: There were cascading updates
|
||||
⚛ Parent [mount]
|
||||
⚛ Cascading [mount]
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
|
||||
⚛ Cascading [update]
|
||||
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = `
|
||||
"// Rendering the first root
|
||||
⛔ (React Tree Reconciliation) Warning: There were cascading updates
|
||||
⚛ Cascading [mount]
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
|
||||
// Scheduling another root from componentDidMount
|
||||
⚛ Child [mount]
|
||||
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
@ -1,110 +0,0 @@
|
||||
/**
|
||||
* Copyright 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactCoroutine
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ReactNodeList} from 'ReactTypes';
|
||||
|
||||
// The Symbol used to tag the special React types. If there is no native Symbol
|
||||
// nor polyfill, then a plain number is used for performance.
|
||||
var REACT_COROUTINE_TYPE;
|
||||
var REACT_YIELD_TYPE;
|
||||
if (typeof Symbol === 'function' && Symbol.for) {
|
||||
REACT_COROUTINE_TYPE = Symbol.for('react.coroutine');
|
||||
REACT_YIELD_TYPE = Symbol.for('react.yield');
|
||||
} else {
|
||||
REACT_COROUTINE_TYPE = 0xeac8;
|
||||
REACT_YIELD_TYPE = 0xeac9;
|
||||
}
|
||||
|
||||
type CoroutineHandler<T> = (props: T, yields: Array<mixed>) => ReactNodeList;
|
||||
|
||||
export type ReactCoroutine = {
|
||||
$$typeof: Symbol | number,
|
||||
key: null | string,
|
||||
children: any,
|
||||
// This should be a more specific CoroutineHandler
|
||||
handler: (props: any, yields: Array<mixed>) => ReactNodeList,
|
||||
props: any,
|
||||
};
|
||||
export type ReactYield = {
|
||||
$$typeof: Symbol | number,
|
||||
value: mixed,
|
||||
};
|
||||
|
||||
exports.createCoroutine = function<T>(
|
||||
children: mixed,
|
||||
handler: CoroutineHandler<T>,
|
||||
props: T,
|
||||
key: ?string = null,
|
||||
): ReactCoroutine {
|
||||
var coroutine = {
|
||||
// This tag allow us to uniquely identify this as a React Coroutine
|
||||
$$typeof: REACT_COROUTINE_TYPE,
|
||||
key: key == null ? null : '' + key,
|
||||
children: children,
|
||||
handler: handler,
|
||||
props: props,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// TODO: Add _store property for marking this as validated.
|
||||
if (Object.freeze) {
|
||||
Object.freeze(coroutine.props);
|
||||
Object.freeze(coroutine);
|
||||
}
|
||||
}
|
||||
|
||||
return coroutine;
|
||||
};
|
||||
|
||||
exports.createYield = function(value: mixed): ReactYield {
|
||||
var yieldNode = {
|
||||
// This tag allow us to uniquely identify this as a React Yield
|
||||
$$typeof: REACT_YIELD_TYPE,
|
||||
value: value,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// TODO: Add _store property for marking this as validated.
|
||||
if (Object.freeze) {
|
||||
Object.freeze(yieldNode);
|
||||
}
|
||||
}
|
||||
|
||||
return yieldNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the object is a coroutine object.
|
||||
*/
|
||||
exports.isCoroutine = function(object: mixed): boolean {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.$$typeof === REACT_COROUTINE_TYPE
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the object is a yield object.
|
||||
*/
|
||||
exports.isYield = function(object: mixed): boolean {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.$$typeof === REACT_YIELD_TYPE
|
||||
);
|
||||
};
|
||||
|
||||
exports.REACT_YIELD_TYPE = REACT_YIELD_TYPE;
|
||||
exports.REACT_COROUTINE_TYPE = REACT_COROUTINE_TYPE;
|
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Copyright 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactPortal
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ReactNodeList} from 'ReactTypes';
|
||||
|
||||
// The Symbol used to tag the special React types. If there is no native Symbol
|
||||
// nor polyfill, then a plain number is used for performance.
|
||||
var REACT_PORTAL_TYPE =
|
||||
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.portal')) ||
|
||||
0xeaca;
|
||||
|
||||
export type ReactPortal = {
|
||||
$$typeof: Symbol | number,
|
||||
key: null | string,
|
||||
containerInfo: any,
|
||||
children: ReactNodeList,
|
||||
// TODO: figure out the API for cross-renderer implementation.
|
||||
implementation: any,
|
||||
};
|
||||
|
||||
exports.createPortal = function(
|
||||
children: ReactNodeList,
|
||||
containerInfo: any,
|
||||
// TODO: figure out the API for cross-renderer implementation.
|
||||
implementation: any,
|
||||
key: ?string = null,
|
||||
): ReactPortal {
|
||||
return {
|
||||
// This tag allow us to uniquely identify this as a React Portal
|
||||
$$typeof: REACT_PORTAL_TYPE,
|
||||
key: key == null ? null : '' + key,
|
||||
children,
|
||||
containerInfo,
|
||||
implementation,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the object is a portal object.
|
||||
*/
|
||||
exports.isPortal = function(object: mixed): boolean {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.$$typeof === REACT_PORTAL_TYPE
|
||||
);
|
||||
};
|
||||
|
||||
exports.REACT_PORTAL_TYPE = REACT_PORTAL_TYPE;
|
@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactHostOperationHistoryHook
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {DebugID} from 'ReactInstanceType';
|
||||
|
||||
export type Operation = {instanceID: DebugID} & (
|
||||
| {type: 'mount', payload: string}
|
||||
| {type: 'insert child', payload: {toIndex: number, content: string}}
|
||||
| {type: 'move child', payload: {fromIndex: number, toIndex: number}}
|
||||
| {type: 'replace children', payload: string}
|
||||
| {type: 'replace text', payload: string}
|
||||
| {type: 'replace with', payload: string}
|
||||
| {type: 'update styles', payload: mixed /* Style Object */}
|
||||
| {type: 'update attribute', payload: {[name: string]: string}}
|
||||
| {type: 'remove attribute', payload: string});
|
||||
|
||||
// Trust the developer to only use this with a __DEV__ check
|
||||
var ReactHostOperationHistoryHook = ((null: any): typeof ReactHostOperationHistoryHook);
|
||||
|
||||
if (__DEV__) {
|
||||
var history: Array<Operation> = [];
|
||||
|
||||
ReactHostOperationHistoryHook = {
|
||||
onHostOperation(operation: Operation) {
|
||||
history.push(operation);
|
||||
},
|
||||
|
||||
clearHistory(): void {
|
||||
if (ReactHostOperationHistoryHook._preventClearing) {
|
||||
// Should only be used for tests.
|
||||
return;
|
||||
}
|
||||
|
||||
history = [];
|
||||
},
|
||||
|
||||
getHistory(): Array<Operation> {
|
||||
return history;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = ReactHostOperationHistoryHook;
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactInvalidSetStateWarningHook
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
var ReactInvalidSetStateWarningHook = {};
|
||||
|
||||
if (__DEV__) {
|
||||
var processingChildContext = false;
|
||||
|
||||
var warnInvalidSetState = function() {
|
||||
warning(
|
||||
!processingChildContext,
|
||||
'setState(...): Cannot call setState() inside getChildContext()',
|
||||
);
|
||||
};
|
||||
|
||||
ReactInvalidSetStateWarningHook = {
|
||||
onBeginProcessingChildContext(): void {
|
||||
processingChildContext = true;
|
||||
},
|
||||
onEndProcessingChildContext(): void {
|
||||
processingChildContext = false;
|
||||
},
|
||||
onSetState(): void {
|
||||
warnInvalidSetState();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = ReactInvalidSetStateWarningHook;
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactInstanceMap
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* `ReactInstanceMap` maintains a mapping from a public facing stateful
|
||||
* instance (key) and the internal representation (value). This allows public
|
||||
* methods to accept the user facing instance as an argument and map them back
|
||||
* to internal methods.
|
||||
*/
|
||||
|
||||
// TODO: Replace this with ES6: var ReactInstanceMap = new Map();
|
||||
var ReactInstanceMap = {
|
||||
/**
|
||||
* This API should be called `delete` but we'd have to make sure to always
|
||||
* transform these to strings for IE support. When this transform is fully
|
||||
* supported we can rename it.
|
||||
*/
|
||||
remove: function(key) {
|
||||
key._reactInternalInstance = undefined;
|
||||
},
|
||||
|
||||
get: function(key) {
|
||||
return key._reactInternalInstance;
|
||||
},
|
||||
|
||||
has: function(key) {
|
||||
return key._reactInternalInstance !== undefined;
|
||||
},
|
||||
|
||||
set: function(key, value) {
|
||||
key._reactInternalInstance = value;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactInstanceMap;
|
@ -1,146 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactTreeTraversal
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var {HostComponent} = require('ReactTypeOfWork');
|
||||
|
||||
function getParent(inst) {
|
||||
if (inst._hostParent !== undefined) {
|
||||
return inst._hostParent;
|
||||
}
|
||||
if (typeof inst.tag === 'number') {
|
||||
do {
|
||||
inst = inst.return;
|
||||
// TODO: If this is a HostRoot we might want to bail out.
|
||||
// That is depending on if we want nested subtrees (layers) to bubble
|
||||
// events to their parent. We could also go through parentNode on the
|
||||
// host node but that wouldn't work for React Native and doesn't let us
|
||||
// do the portal feature.
|
||||
} while (inst && inst.tag !== HostComponent);
|
||||
if (inst) {
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lowest common ancestor of A and B, or null if they are in
|
||||
* different trees.
|
||||
*/
|
||||
function getLowestCommonAncestor(instA, instB) {
|
||||
var depthA = 0;
|
||||
for (var tempA = instA; tempA; tempA = getParent(tempA)) {
|
||||
depthA++;
|
||||
}
|
||||
var depthB = 0;
|
||||
for (var tempB = instB; tempB; tempB = getParent(tempB)) {
|
||||
depthB++;
|
||||
}
|
||||
|
||||
// If A is deeper, crawl up.
|
||||
while (depthA - depthB > 0) {
|
||||
instA = getParent(instA);
|
||||
depthA--;
|
||||
}
|
||||
|
||||
// If B is deeper, crawl up.
|
||||
while (depthB - depthA > 0) {
|
||||
instB = getParent(instB);
|
||||
depthB--;
|
||||
}
|
||||
|
||||
// Walk in lockstep until we find a match.
|
||||
var depth = depthA;
|
||||
while (depth--) {
|
||||
if (instA === instB || instA === instB.alternate) {
|
||||
return instA;
|
||||
}
|
||||
instA = getParent(instA);
|
||||
instB = getParent(instB);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if A is an ancestor of B.
|
||||
*/
|
||||
function isAncestor(instA, instB) {
|
||||
while (instB) {
|
||||
if (instA === instB || instA === instB.alternate) {
|
||||
return true;
|
||||
}
|
||||
instB = getParent(instB);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent instance of the passed-in instance.
|
||||
*/
|
||||
function getParentInstance(inst) {
|
||||
return getParent(inst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
|
||||
*/
|
||||
function traverseTwoPhase(inst, fn, arg) {
|
||||
var path = [];
|
||||
while (inst) {
|
||||
path.push(inst);
|
||||
inst = getParent(inst);
|
||||
}
|
||||
var i;
|
||||
for (i = path.length; i-- > 0; ) {
|
||||
fn(path[i], 'captured', arg);
|
||||
}
|
||||
for (i = 0; i < path.length; i++) {
|
||||
fn(path[i], 'bubbled', arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
|
||||
* should would receive a `mouseEnter` or `mouseLeave` event.
|
||||
*
|
||||
* Does not invoke the callback on the nearest common ancestor because nothing
|
||||
* "entered" or "left" that element.
|
||||
*/
|
||||
function traverseEnterLeave(from, to, fn, argFrom, argTo) {
|
||||
var common = from && to ? getLowestCommonAncestor(from, to) : null;
|
||||
var pathFrom = [];
|
||||
while (from && from !== common) {
|
||||
pathFrom.push(from);
|
||||
from = getParent(from);
|
||||
}
|
||||
var pathTo = [];
|
||||
while (to && to !== common) {
|
||||
pathTo.push(to);
|
||||
to = getParent(to);
|
||||
}
|
||||
var i;
|
||||
for (i = 0; i < pathFrom.length; i++) {
|
||||
fn(pathFrom[i], 'bubbled', argFrom);
|
||||
}
|
||||
for (i = pathTo.length; i-- > 0; ) {
|
||||
fn(pathTo[i], 'captured', argTo);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAncestor: isAncestor,
|
||||
getLowestCommonAncestor: getLowestCommonAncestor,
|
||||
getParentInstance: getParentInstance,
|
||||
traverseTwoPhase: traverseTwoPhase,
|
||||
traverseEnterLeave: traverseEnterLeave,
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user