mirror of
https://github.com/status-im/react-native.git
synced 2025-02-25 23:55:23 +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
|
; For RN Apps installed via npm, "Libraries" folder is inside
|
||||||
; "node_modules/react-native" but in the source repo it is in the root
|
; "node_modules/react-native" but in the source repo it is in the root
|
||||||
.*/Libraries/react-native/React.js
|
.*/Libraries/react-native/React.js
|
||||||
.*/Libraries/react-native/ReactNative.js
|
|
||||||
|
|
||||||
[include]
|
[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>,
|
CopyConstructor: Class<T>,
|
||||||
pooler: Pooler,
|
pooler: Pooler,
|
||||||
): Class<T> & {
|
): Class<T> & {
|
||||||
getPooled(...args: $ReadOnlyArray<mixed>): /* arguments of the constructor */ T,
|
getPooled(): /* arguments of the constructor */ T,
|
||||||
release(instance: mixed): void,
|
release(): void,
|
||||||
} {
|
} {
|
||||||
// Casting as any so that flow ignores the actual implementation and trusts
|
// Casting as any so that flow ignores the actual implementation and trusts
|
||||||
// it to match the type we declared
|
// it to match the type we declared
|
@ -6,12 +6,14 @@
|
|||||||
* LICENSE file in the root directory of this source tree. An additional grant
|
* 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.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*
|
*
|
||||||
* @flow
|
* @providesModule ReactDebugTool
|
||||||
* @providesModule ReactPropTypesSecret
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'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');
|
const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
|
||||||
|
|
||||||
module.exports = ReactNativeFeatureFlags.useFiber
|
import type {ReactNativeType} from 'ReactNativeTypes';
|
||||||
? require('ReactNativeFiber')
|
|
||||||
: require('ReactNativeStack');
|
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
|
* 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.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*
|
*
|
||||||
* @providesModule getNextDebugID
|
* @providesModule ReactPerf
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var nextDebugID = 1;
|
const {
|
||||||
|
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||||
|
} = require('ReactNative');
|
||||||
|
|
||||||
function getNextDebugID(): number {
|
module.exports = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactPerf;
|
||||||
return nextDebugID++;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = getNextDebugID;
|
|
@ -12,9 +12,6 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import type {ReactCoroutine, ReactYield} from 'ReactCoroutine';
|
|
||||||
import type {ReactPortal} from 'ReactPortal';
|
|
||||||
|
|
||||||
export type ReactNode =
|
export type ReactNode =
|
||||||
| ReactElement<any>
|
| ReactElement<any>
|
||||||
| ReactCoroutine
|
| ReactCoroutine
|
||||||
@ -30,3 +27,26 @@ export type ReactNodeList = ReactEmpty | ReactNode;
|
|||||||
export type ReactText = string | number;
|
export type ReactText = string | number;
|
||||||
|
|
||||||
export type ReactEmpty = null | void | boolean;
|
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
|
* 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.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*
|
*
|
||||||
* @providesModule ReactVersion
|
* @providesModule TouchHistoryMath
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'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
|
* 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.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*
|
*
|
||||||
* @providesModule ReactTypeOfInternalContext
|
* @providesModule takeSnapshot
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export type TypeOfInternalContext = number;
|
const {
|
||||||
|
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||||
|
} = require('ReactNative');
|
||||||
|
|
||||||
module.exports = {
|
module.exports =
|
||||||
NoContext: 0,
|
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.takeSnapshot;
|
||||||
AsyncUpdates: 1,
|
|
||||||
};
|
|
@ -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