Reviewed By: kentaromiura

Differential Revision: D4026114

fbshipit-source-id: 67808af91454d95941fea01eef58a4d9086f46e1
This commit is contained in:
David Aurelio 2016-11-04 05:40:26 -07:00 коммит произвёл Facebook Github Bot
Родитель b76ab8e3a1
Коммит 3683beb88a
147 изменённых файлов: 13563 добавлений и 56 удалений

Просмотреть файл

@ -17,6 +17,7 @@
# Ignore unexpected extra @providesModule
.*/node_modules/commoner/test/source/widget/share.js
.*/node_modules/.*/node_modules/fbjs/.*
# Ignore duplicate module providers
# For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root

Просмотреть файл

@ -47,7 +47,9 @@ class AppEventsTest extends React.Component {
throw new Error('Received wrong event: ' + JSON.stringify(event));
}
var elapsed = (Date.now() - event.ts) + 'ms';
this.setState({received: event, elapsed}, TestModule.markTestCompleted);
this.setState({received: event, elapsed}, () => {
TestModule.markTestCompleted();
});
};
render() {

Просмотреть файл

@ -174,7 +174,9 @@ class AsyncStorageTest extends React.Component {
};
componentDidMount() {
done = () => this.setState({done: true}, TestModule.markTestCompleted);
done = () => this.setState({done: true}, () => {
TestModule.markTestCompleted();
});
updateMessage = (msg) => {
this.setState({messages: this.state.messages.concat('\n' + msg)});
DEBUG && console.log(msg);

Просмотреть файл

@ -51,7 +51,9 @@ class IntegrationTestHarnessTest extends React.Component {
} else if (!TestModule.markTestCompleted) {
throw new Error('RCTTestModule.markTestCompleted not defined.');
}
this.setState({done: true}, TestModule.markTestCompleted);
this.setState({done: true}, () => {
TestModule.markTestCompleted();
});
};
render() {

Просмотреть файл

@ -120,7 +120,9 @@ var TimersTest = React.createClass({
},
done() {
this.setState({done: true}, TestModule.markTestCompleted);
this.setState({done: true}, () => {
TestModule.markTestCompleted();
});
},
render() {

Просмотреть файл

@ -17,7 +17,7 @@ var Transform = require('art/core/transform');
var React = require('React');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var createReactNativeComponentClass = require('react/lib/createReactNativeComponentClass');
var createReactNativeComponentClass = require('createReactNativeComponentClass');
var merge = require('merge');
// Diff Helpers

Просмотреть файл

@ -20,7 +20,7 @@ var Set = require('Set');
var SpringConfig = require('SpringConfig');
var ViewStylePropTypes = require('ViewStylePropTypes');
var findNodeHandle = require('react/lib/findNodeHandle');
var findNodeHandle = require('findNodeHandle');
var flattenStyle = require('flattenStyle');
var invariant = require('fbjs/lib/invariant');
var requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');

Просмотреть файл

@ -11,7 +11,7 @@
*/
'use strict';
const ReactNativeMount = require('react/lib/ReactNativeMount');
const ReactNativeMount = require('ReactNativeMount');
const getReactData = require('getReactData');
const INDENTATION_SIZE = 2;

Просмотреть файл

@ -12,7 +12,7 @@
'use strict';
const ColorPropType = require('ColorPropType');
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
const NativeMethodsMixin = require('NativeMethodsMixin');
const Platform = require('Platform');
const React = require('React');
const StyleSheet = require('StyleSheet');

Просмотреть файл

@ -13,7 +13,7 @@
*/
'use strict';
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
const NativeMethodsMixin = require('NativeMethodsMixin');
const React = require('React');
const StyleSheet = require('StyleSheet');
const View = require('View');

Просмотреть файл

@ -11,7 +11,7 @@
'use strict';
var ColorPropType = require('ColorPropType');
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var Platform = require('Platform');
var React = require('React');
var ReactNative = require('ReactNative');

Просмотреть файл

@ -14,7 +14,7 @@
const ColorPropType = require('ColorPropType');
const EdgeInsetsPropType = require('EdgeInsetsPropType');
const Image = require('Image');
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
const NativeMethodsMixin = require('NativeMethodsMixin');
const React = require('React');
const StyleSheet = require('StyleSheet');
const View = require('View');

Просмотреть файл

@ -12,7 +12,7 @@
*/
'use strict';
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var StyleSheet = require('StyleSheet');
var StyleSheetPropType = require('StyleSheetPropType');

Просмотреть файл

@ -10,7 +10,7 @@
*/
'use strict';
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var View = require('View');
var ColorPropType = require('ColorPropType');

Просмотреть файл

@ -12,7 +12,7 @@
'use strict';
var Image = require('Image');
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');

Просмотреть файл

@ -12,7 +12,7 @@
'use strict';
const ColorPropType = require('ColorPropType');
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
const NativeMethodsMixin = require('NativeMethodsMixin');
const Platform = require('Platform');
const React = require('React');
const View = require('View');

Просмотреть файл

@ -11,7 +11,7 @@
*/
'use strict';
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');

Просмотреть файл

@ -12,7 +12,7 @@
'use strict';
var Image = require('Image');
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var Platform = require('Platform');
var React = require('React');

Просмотреть файл

@ -12,7 +12,7 @@
'use strict';
var ColorPropType = require('ColorPropType');
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var Platform = require('Platform');
var React = require('React');
var StyleSheet = require('StyleSheet');

Просмотреть файл

@ -14,7 +14,7 @@
const ColorPropType = require('ColorPropType');
const DocumentSelectionState = require('DocumentSelectionState');
const EventEmitter = require('EventEmitter');
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
const NativeMethodsMixin = require('NativeMethodsMixin');
const Platform = require('Platform');
const React = require('React');
const ReactNative = require('ReactNative');

Просмотреть файл

@ -12,7 +12,7 @@
'use strict';
var Image = require('Image');
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var UIManager = require('UIManager');

Просмотреть файл

@ -13,7 +13,7 @@
var Animated = require('Animated');
var EdgeInsetsPropType = require('EdgeInsetsPropType');
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var Touchable = require('Touchable');

Просмотреть файл

@ -14,7 +14,7 @@
// Note (avik): add @flow when Flow supports spread properties in propTypes
var ColorPropType = require('ColorPropType');
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var StyleSheet = require('StyleSheet');

Просмотреть файл

@ -14,7 +14,7 @@
// Note (avik): add @flow when Flow supports spread properties in propTypes
var Animated = require('Animated');
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var TimerMixin = require('react-timer-mixin');
var Touchable = require('Touchable');

Просмотреть файл

@ -10,7 +10,7 @@
jest.disableAutomock();
const React = require('React');
const ReactTestRenderer = require('react/lib/ReactTestRenderer');
const ReactTestRenderer = require('react-test-renderer');
const Text = require('Text');
const TouchableHighlight = require('TouchableHighlight');

Просмотреть файл

@ -12,7 +12,7 @@
'use strict';
const EdgeInsetsPropType = require('EdgeInsetsPropType');
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
const NativeMethodsMixin = require('NativeMethodsMixin');
const NativeModules = require('NativeModules');
const React = require('React');
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');

Просмотреть файл

@ -72,7 +72,7 @@ function setupDevtools() {
return;
}
// This is breaking encapsulation of the React package. Move plz.
var ReactNativeComponentTree = require('react/lib/ReactNativeComponentTree');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
ComponentTree: {
getClosestInstanceFromNode: function (node) {
@ -90,8 +90,8 @@ function setupDevtools() {
}
}
},
Mount: require('react/lib/ReactNativeMount'),
Reconciler: require('react/lib/ReactReconciler')
Mount: require('ReactNativeMount'),
Reconciler: require('ReactReconciler')
});
ws.onmessage = handleMessage;
}

Просмотреть файл

@ -11,7 +11,7 @@
*/
'use strict';
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
var ImageResizeMode = require('ImageResizeMode');
var ImageStylePropTypes = require('ImageStylePropTypes');

Просмотреть файл

@ -15,7 +15,7 @@ const EdgeInsetsPropType = require('EdgeInsetsPropType');
const ImageResizeMode = require('ImageResizeMode');
const ImageSourcePropType = require('ImageSourcePropType');
const ImageStylePropTypes = require('ImageStylePropTypes');
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
const NativeMethodsMixin = require('NativeMethodsMixin');
const NativeModules = require('NativeModules');
const React = require('React');
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');

Просмотреть файл

@ -10,7 +10,7 @@
*/
'use strict';
var ReactNativeComponentTree = require('react/lib/ReactNativeComponentTree');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
function traverseOwnerTreeUp(hierarchy, instance) {
if (instance) {

Просмотреть файл

@ -12,7 +12,7 @@
'use strict';
const InteractionManager = require('./InteractionManager');
const TouchHistoryMath = require('react/lib/TouchHistoryMath');
const TouchHistoryMath = require('TouchHistoryMath');
const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;

Просмотреть файл

@ -11,8 +11,8 @@
*/
'use strict';
var ReactDebugTool = require('react/lib/ReactDebugTool');
var ReactPerf = require('react/lib/ReactPerf');
var ReactDebugTool = require('ReactDebugTool');
var ReactPerf = require('ReactPerf');
var invariant = require('fbjs/lib/invariant');
var performanceNow = require('fbjs/lib/performanceNow');

Просмотреть файл

@ -67,10 +67,10 @@ const Systrace = {
if (__DEV__) {
if (enabled) {
global.nativeTraceBeginLegacy && global.nativeTraceBeginLegacy(TRACE_TAG_JSC_CALLS);
require('react/lib/ReactDebugTool').addDevtool(ReactSystraceDevtool);
require('ReactDebugTool').addDevtool(ReactSystraceDevtool);
} else {
global.nativeTraceEndLegacy && global.nativeTraceEndLegacy(TRACE_TAG_JSC_CALLS);
require('react/lib/ReactDebugTool').removeDevtool(ReactSystraceDevtool);
require('ReactDebugTool').removeDevtool(ReactSystraceDevtool);
}
}
_enabled = enabled;

Просмотреть файл

@ -15,7 +15,7 @@ const NativeModules = require('NativeModules');
const Platform = require('Platform');
const defineLazyObjectProperty = require('defineLazyObjectProperty');
const findNodeHandle = require('react/lib/findNodeHandle');
const findNodeHandle = require('findNodeHandle');
const invariant = require('fbjs/lib/invariant');
import type React from 'react';

Просмотреть файл

@ -15,7 +15,7 @@ const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
const UIManager = require('UIManager');
const UnimplementedView = require('UnimplementedView');
const createReactNativeComponentClass = require('react/lib/createReactNativeComponentClass');
const createReactNativeComponentClass = require('createReactNativeComponentClass');
const insetsDiffer = require('insetsDiffer');
const matricesDiffer = require('matricesDiffer');
const pointsDiffer = require('pointsDiffer');

Просмотреть файл

@ -0,0 +1,5 @@
# 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).

Просмотреть файл

@ -1,13 +1,14 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* 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.
*
* @flow
* @providesModule ReactNative
* @providesModule ReactVersion
*/
'use strict';
module.exports = require('react/lib/ReactNative');
'use strict';
module.exports = '16.0.0-alpha';

Просмотреть файл

@ -0,0 +1,221 @@
/**
* 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
*/
'use strict';
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
var TextInputState = require('TextInputState');
var UIManager = require('UIManager');
var findNodeHandle = require('findNodeHandle');
var invariant = require('fbjs/lib/invariant');
type MeasureOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number
) => void
type MeasureInWindowOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
) => void
type MeasureLayoutOnSuccessCallback = (
left: number,
top: number,
width: number,
height: number
) => void
function warnForStyleProps(props, validAttributes) {
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 + ': ... } }`'
);
}
}
}
/**
* `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).
*/
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(
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(
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
* `React.findNodeHandle(component)`.
*/
measureLayout: function(
relativeToNativeNode: number,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */
) {
UIManager.measureLayout(
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) {
if (__DEV__) {
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
}
var updatePayload = ReactNativeAttributePayload.create(
nativeProps,
this.viewConfig.validAttributes
);
UIManager.updateView(
findNodeHandle(this),
this.viewConfig.uiViewClassName,
updatePayload
);
},
/**
* 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(findNodeHandle(this));
},
/**
* Removes focus from an input or view. This is the opposite of `focus()`.
*/
blur: function() {
TextInputState.blurTextInput(findNodeHandle(this));
},
};
function throwOnStylesProp(component, props) {
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);
}
}
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);
};
}
/**
* In the future, we should cleanup callbacks by cancelling them instead of
* using this.
*/
function mountSafeCallback(
context: ReactComponent<any, any, any>,
callback: ?Function
): any {
return function() {
if (!callback || (context.isMounted && !context.isMounted())) {
return undefined;
}
return callback.apply(context, arguments);
};
}
module.exports = NativeMethodsMixin;

Просмотреть файл

@ -0,0 +1,75 @@
/**
* 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 ReactNative
* @flow
*/
'use strict';
// Require ReactNativeDefaultInjection first for its side effects of setting up
// the JS environment
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection');
var ReactNativeMount = require('ReactNativeMount');
var ReactUpdates = require('ReactUpdates');
var findNodeHandle = require('findNodeHandle');
ReactNativeDefaultInjection.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,
findNodeHandle: findNodeHandle,
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;

Просмотреть файл

@ -0,0 +1,520 @@
/**
* 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 preceeding 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.
// $FlowFixMe
updatePayload = diffNestedProperty(
updatePayload,
prevProp,
nextProp,
attributeConfig
);
if (removedKeyCount > 0 && updatePayload) {
restoreDeletedValuesInNestedArray(
updatePayload,
nextProp,
attributeConfig
);
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
);
}
}
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;

Просмотреть файл

@ -0,0 +1,232 @@
/**
* 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 ReactNativeEventEmitter = require('ReactNativeEventEmitter');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var ReactMultiChild = require('ReactMultiChild');
var UIManager = require('UIManager');
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
var registrationNames = ReactNativeEventEmitter.registrationNames;
var putListener = ReactNativeEventEmitter.putListener;
var deleteListener = ReactNativeEventEmitter.deleteListener;
var deleteAllListeners = ReactNativeEventEmitter.deleteAllListeners;
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() {
ReactNativeComponentTree.uncacheNode(this);
deleteAllListeners(this);
this.unmountChildren();
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._reconcileListenersUponUpdate(
prevElement.props,
nextElement.props
);
this.updateChildren(nextElement.props.children, transaction, context);
},
/**
* @param {object} initialProps Native component props.
*/
_registerListenersUponCreation: function(initialProps) {
for (var key in initialProps) {
// NOTE: The check for `!props[key]`, is only possible because this method
// registers listeners the *first* time a component is created.
if (registrationNames[key] && initialProps[key]) {
var listener = initialProps[key];
putListener(this, key, listener);
}
}
},
/**
* Reconciles event listeners, adding or removing if necessary.
* @param {object} prevProps Native component props including events.
* @param {object} nextProps Next native component props including events.
*/
_reconcileListenersUponUpdate: function(prevProps, nextProps) {
for (var key in nextProps) {
if (registrationNames[key] && (nextProps[key] !== prevProps[key])) {
if (nextProps[key]) {
putListener(this, key, nextProps[key]);
} else {
deleteListener(this, key);
}
}
}
},
/**
* 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._registerListenersUponCreation(this._currentElement.props);
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;

Просмотреть файл

@ -0,0 +1,70 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,33 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,66 @@
/**
* 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 = {};
/**
* 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 uncacheNode(inst) {
var tag = inst._rootNodeID;
if (tag) {
delete instanceCache[tag];
}
}
function getInstanceFromTag(tag) {
return instanceCache[tag] || null;
}
function getTagFromInstance(inst) {
invariant(inst._rootNodeID, 'All native instances should have a tag.');
return inst._rootNodeID;
}
var ReactNativeComponentTree = {
getClosestInstanceFromNode: getInstanceFromTag,
getInstanceFromNode: getInstanceFromTag,
getNodeFromInstance: getTagFromInstance,
precacheNode: precacheNode,
uncacheNode: uncacheNode,
};
module.exports = ReactNativeComponentTree;

Просмотреть файл

@ -0,0 +1,21 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,85 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,111 @@
/**
* 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 ReactNativeDefaultInjection
* @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 React = require('React');
var ReactComponentEnvironment = require('ReactComponentEnvironment');
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
var ReactEmptyComponent = require('ReactEmptyComponent');
var ReactNativeBridgeEventPlugin = require('ReactNativeBridgeEventPlugin');
var ReactHostComponent = require('ReactHostComponent');
var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
var ReactNativeEventPluginOrder = require('ReactNativeEventPluginOrder');
var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler');
var ReactNativeTextComponent = require('ReactNativeTextComponent');
var ReactNativeTreeTraversal = require('ReactNativeTreeTraversal');
var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent');
var ReactUpdates = require('ReactUpdates');
var ResponderEventPlugin = require('ResponderEventPlugin');
var invariant = require('fbjs/lib/invariant');
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);
EventPluginUtils.injection.injectTreeTraversal(ReactNativeTreeTraversal);
ResponderEventPlugin.injection.injectGlobalResponderHandler(
ReactNativeGlobalResponderHandler
);
/**
* Some important event plugins included by default (without having to require
* them).
*/
EventPluginHub.injection.injectEventPluginsByName({
'ResponderEventPlugin': ResponderEventPlugin,
'ReactNativeBridgeEventPlugin': ReactNativeBridgeEventPlugin,
});
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
);
};
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,
};

Просмотреть файл

@ -0,0 +1,221 @@
/**
* 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 ReactUpdates = require('ReactUpdates');
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;
};
/**
* `ReactNativeEventEmitter` is used to attach top-level event listeners. For example:
*
* ReactNativeEventEmitter.putListener('myID', 'onClick', myFunction);
*
* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
*
* @internal
*/
var ReactNativeEventEmitter = {
...ReactEventEmitterMixin,
registrationNames: EventPluginRegistry.registrationNameModules,
putListener: EventPluginHub.putListener,
getListener: EventPluginHub.getListener,
deleteListener: EventPluginHub.deleteListener,
deleteAllListeners: EventPluginHub.deleteAllListeners,
/**
* 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);
if (!inst) {
// If the original instance is already gone, we don't have to dispatch
// any events.
return;
}
ReactUpdates.batchedUpdates(function() {
ReactNativeEventEmitter.handleTopLevel(
topLevelType,
inst,
nativeEvent,
nativeEvent.target
);
});
},
/**
* 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 occured on tag zero.'
);
}
} else {
rootNodeID = target;
}
}
ReactNativeEventEmitter._receiveRootNodeIDEvent(
rootNodeID,
eventTopLevelType,
nativeEvent
);
}
},
};
module.exports = ReactNativeEventEmitter;

Просмотреть файл

@ -0,0 +1,19 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,28 @@
/**
* 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) {
UIManager.setJSResponder(
to._rootNodeID,
blockNativeResponder
);
} else {
UIManager.clearJSResponder();
}
},
};
module.exports = ReactNativeGlobalResponderHandler;

Просмотреть файл

@ -0,0 +1,229 @@
/**
* 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;
// 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.getPublicInstance();
if (callback) {
callback.call(component);
}
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;

Просмотреть файл

@ -0,0 +1,44 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,132 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,57 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,81 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,126 @@
/**
* 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 ReactNativeTreeTraversal
*/
'use strict';
// Same as ReactDOMTreeTraversal without the invariants.
/**
* 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 = tempA._hostParent) {
depthA++;
}
var depthB = 0;
for (var tempB = instB; tempB; tempB = tempB._hostParent) {
depthB++;
}
// If A is deeper, crawl up.
while (depthA - depthB > 0) {
instA = instA._hostParent;
depthA--;
}
// If B is deeper, crawl up.
while (depthB - depthA > 0) {
instB = instB._hostParent;
depthB--;
}
// Walk in lockstep until we find a match.
var depth = depthA;
while (depth--) {
if (instA === instB) {
return instA;
}
instA = instA._hostParent;
instB = instB._hostParent;
}
return null;
}
/**
* Return if A is an ancestor of B.
*/
function isAncestor(instA, instB) {
while (instB) {
if (instB === instA) {
return true;
}
instB = instB._hostParent;
}
return false;
}
/**
* Return the parent instance of the passed-in instance.
*/
function getParentInstance(inst) {
return inst._hostParent;
}
/**
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
*/
function traverseTwoPhase(inst, fn, arg) {
var path = [];
while (inst) {
path.push(inst);
inst = inst._hostParent;
}
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 = from._hostParent;
}
var pathTo = [];
while (to && to !== common) {
pathTo.push(to);
to = to._hostParent;
}
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,
};

Просмотреть файл

@ -0,0 +1,14 @@
/**
* 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';
// Noop
// TODO #10932517: Move all initialization callers back into react-native

Просмотреть файл

@ -0,0 +1,16 @@
/**
* 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';
var RCTEventEmitter = {
register: jest.fn(),
};
module.exports = RCTEventEmitter;

Просмотреть файл

@ -0,0 +1,17 @@
/**
* 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';
// Mock of the Native Hooks
// TODO: Should this move into the components themselves? E.g. focusable
var TextInputState = {};
module.exports = TextInputState;

Просмотреть файл

@ -0,0 +1,23 @@
/**
* 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';
// Mock of the Native Hooks
var RCTUIManager = {
createView: jest.fn(),
setChildren: jest.fn(),
manageChildren: jest.fn(),
updateView: jest.fn(),
removeSubviewsFromContainerWithID: jest.fn(),
replaceExistingNonRootView: jest.fn(),
};
module.exports = RCTUIManager;

Просмотреть файл

@ -0,0 +1,19 @@
/**
* 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';
var createReactNativeComponentClass = require('createReactNativeComponentClass');
var View = createReactNativeComponentClass({
validAttributes: {},
uiViewClassName: 'View',
});
module.exports = View;

Просмотреть файл

@ -0,0 +1,63 @@
/**
* 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';
// TODO: Move deepDiffer into react
var deepDiffer = function(one: any, two: any): boolean {
if (one === two) {
// Short circuit on identical object references instead of traversing them.
return false;
}
if ((typeof one === 'function') && (typeof two === 'function')) {
// We consider all functions equal
return false;
}
if ((typeof one !== 'object') || (one === null)) {
// Primitives can be directly compared
return one !== two;
}
if ((typeof two !== 'object') || (two === null)) {
// We know they are different because the previous case would have triggered
// otherwise.
return true;
}
if (one.constructor !== two.constructor) {
return true;
}
if (Array.isArray(one)) {
// We know two is also an array because the constructors are equal
var len = one.length;
if (two.length !== len) {
return true;
}
for (var ii = 0; ii < len; ii++) {
if (deepDiffer(one[ii], two[ii])) {
return true;
}
}
} else {
for (var key in one) {
if (deepDiffer(one[key], two[key])) {
return true;
}
}
for (var twoKey in two) {
// The only case we haven't checked yet is keys that are in two but aren't
// in one, which means they are different.
if (one[twoKey] === undefined && two[twoKey] !== undefined) {
return true;
}
}
}
return false;
};
module.exports = deepDiffer;

Просмотреть файл

@ -0,0 +1,16 @@
/**
* 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';
// TODO: move into react or fbjs
var deepFreezeAndThrowOnMutationInDev = function() { };
module.exports = deepFreezeAndThrowOnMutationInDev;

Просмотреть файл

@ -0,0 +1,16 @@
/**
* 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';
// TODO: Move flattenStyle into react
var flattenStyle = function() { };
module.exports = flattenStyle;

Просмотреть файл

@ -0,0 +1,48 @@
/**
* 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';
var ReactNativeBaseComponent = require('ReactNativeBaseComponent');
// See also ReactNativeBaseComponent
type ReactNativeBaseComponentViewConfig = {
validAttributes: Object;
uiViewClassName: string;
propTypes?: Object,
}
/**
* @param {string} config iOS View configuration.
* @private
*/
var createReactNativeComponentClass = function(
viewConfig: ReactNativeBaseComponentViewConfig
): ReactClass<any> {
var 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 = createReactNativeComponentClass;

Просмотреть файл

@ -0,0 +1,110 @@
/**
* 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
*/
'use strict';
var ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
var ReactInstanceMap = require('ReactInstanceMap');
var invariant = require('fbjs/lib/invariant');
var warning = require('fbjs/lib/warning');
/**
* 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
*/
function findNodeHandle(componentOrHandle: any): ?number {
if (__DEV__) {
var owner = ReactCurrentOwner.current;
if (owner !== null) {
warning(
owner._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.',
owner.getName() || 'A component'
);
owner._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 internalInstance.getHostNode();
} else {
var rootNodeID = component._rootNodeID;
if (rootNodeID) {
return rootNodeID;
} else {
invariant(
(
// Native
typeof component === 'object' &&
'_rootNodeID' 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.'
);
}
}
}
module.exports = findNodeHandle;

Просмотреть файл

@ -0,0 +1,423 @@
/**
* 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
*/
'use strict';
var ReactInvalidSetStateWarningHook = require('ReactInvalidSetStateWarningHook');
var ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook');
var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook');
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
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>;
var hooks = [];
var didHookThrowForEvent = {};
function callHook(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;
}
}
function emitEvent(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;
function clearHistory() {
ReactComponentTreeHook.purgeUnmountedComponents();
ReactHostOperationHistoryHook.clearHistory();
}
function getTreeSnapshot(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;
}, {});
}
function resetMeasurements() {
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 = [];
}
function checkDebugID(debugID, allowRoot = false) {
if (allowRoot && debugID === 0) {
return;
}
if (!debugID) {
warning(false, 'ReactDebugTool: debugID may not be empty.');
}
}
function beginLifeCycleTimer(debugID, timerType) {
if (currentFlushNesting === 0) {
return;
}
if (currentTimerType && !lifeCycleTimerHasWarned) {
warning(
false,
'There is an internal error in the React performance measurement code. ' +
'Did 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;
}
function endLifeCycleTimer(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;
}
function pauseCurrentLifeCycleTimer() {
var currentTimer = {
startTime: currentTimerStartTime,
nestedFlushStartTime: performanceNow(),
debugID: currentTimerDebugID,
timerType: currentTimerType,
};
lifeCycleTimerStack.push(currentTimer);
currentTimerStartTime = 0;
currentTimerNestedFlushDuration = 0;
currentTimerDebugID = null;
currentTimerType = null;
}
function resumeCurrentLifeCycleTimer() {
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 =
// $FlowFixMe https://github.com/facebook/flow/issues/2345
typeof performance !== 'undefined' &&
typeof performance.mark === 'function' &&
typeof performance.clearMarks === 'function' &&
typeof performance.measure === 'function' &&
typeof performance.clearMeasures === 'function';
function shouldMark(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;
}
function markBegin(debugID, markType) {
if (!shouldMark(debugID)) {
return;
}
var markName = `${debugID}::${markType}`;
lastMarkTimeStamp = performanceNow();
performance.mark(markName);
}
function markEnd(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);
performance.clearMeasures(measurementName);
}
var 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');
},
};
// TODO remove these when RN/www gets updated
(ReactDebugTool: any).addDevtool = ReactDebugTool.addHook;
(ReactDebugTool: any).removeDevtool = ReactDebugTool.removeHook;
ReactDebugTool.addHook(ReactInvalidSetStateWarningHook);
ReactDebugTool.addHook(ReactComponentTreeHook);
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
if ((/[?&]react_perf\b/).test(url)) {
ReactDebugTool.beginProfiling();
}
module.exports = ReactDebugTool;

Просмотреть файл

@ -0,0 +1,23 @@
/**
* 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};

Просмотреть файл

@ -0,0 +1,459 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,267 @@
/**
* 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 ReactChildFiber
* @flow
*/
'use strict';
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
import type { PriorityLevel } from 'ReactPriorityLevel';
import type { ReactNodeList } from 'ReactTypes';
var REACT_ELEMENT_TYPE = require('ReactElementSymbol');
var {
REACT_COROUTINE_TYPE,
REACT_YIELD_TYPE,
} = require('ReactCoroutine');
var ReactFiber = require('ReactFiber');
var ReactReifiedYield = require('ReactReifiedYield');
const {
cloneFiber,
createFiberFromElement,
createFiberFromCoroutine,
createFiberFromYield,
} = ReactFiber;
const {
createReifiedYield,
} = ReactReifiedYield;
const isArray = Array.isArray;
function ChildReconciler(shouldClone) {
function createSubsequentChild(
returnFiber : Fiber,
existingChild : ?Fiber,
previousSibling : Fiber,
newChildren,
priority : PriorityLevel
) : Fiber {
if (typeof newChildren !== 'object' || newChildren === null) {
return previousSibling;
}
switch (newChildren.$$typeof) {
case REACT_ELEMENT_TYPE: {
const element = (newChildren : ReactElement<any>);
if (existingChild &&
element.type === existingChild.type &&
element.key === existingChild.key) {
// TODO: This is not sufficient since previous siblings could be new.
// Will fix reconciliation properly later.
const clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
if (!shouldClone) {
// TODO: This might be lowering the priority of nested unfinished work.
clone.pendingWorkPriority = priority;
}
clone.pendingProps = element.props;
clone.sibling = null;
clone.return = returnFiber;
previousSibling.sibling = clone;
return clone;
}
const child = createFiberFromElement(element, priority);
previousSibling.sibling = child;
child.return = returnFiber;
return child;
}
case REACT_COROUTINE_TYPE: {
const coroutine = (newChildren : ReactCoroutine);
const child = createFiberFromCoroutine(coroutine, priority);
previousSibling.sibling = child;
child.return = returnFiber;
return child;
}
case REACT_YIELD_TYPE: {
const yieldNode = (newChildren : ReactYield);
const reifiedYield = createReifiedYield(yieldNode);
const child = createFiberFromYield(yieldNode, priority);
child.output = reifiedYield;
previousSibling.sibling = child;
child.return = returnFiber;
return child;
}
}
if (isArray(newChildren)) {
let prev : Fiber = previousSibling;
let existing : ?Fiber = existingChild;
for (var i = 0; i < newChildren.length; i++) {
var nextExisting = existing && existing.sibling;
prev = createSubsequentChild(returnFiber, existing, prev, newChildren[i], priority);
if (prev && existing) {
// TODO: This is not correct because there could've been more
// than one sibling consumed but I don't want to return a tuple.
existing = nextExisting;
}
}
return prev;
} else {
// TODO: Throw for unknown children.
return previousSibling;
}
}
function createFirstChild(returnFiber, existingChild, newChildren, priority) {
if (typeof newChildren !== 'object' || newChildren === null) {
return null;
}
switch (newChildren.$$typeof) {
case REACT_ELEMENT_TYPE: {
/* $FlowFixMe(>=0.31.0): This is an unsafe cast. Consider adding a type
* annotation to the `newChildren` param of this
* function.
*/
const element = (newChildren : ReactElement<any>);
if (existingChild &&
element.type === existingChild.type &&
element.key === existingChild.key) {
// Get the clone of the existing fiber.
const clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
if (!shouldClone) {
// TODO: This might be lowering the priority of nested unfinished work.
clone.pendingWorkPriority = priority;
}
clone.pendingProps = element.props;
clone.sibling = null;
clone.return = returnFiber;
return clone;
}
const child = createFiberFromElement(element, priority);
child.return = returnFiber;
return child;
}
case REACT_COROUTINE_TYPE: {
/* $FlowFixMe(>=0.31.0): No 'handler' property found in object type
*/
const coroutine = (newChildren : ReactCoroutine);
const child = createFiberFromCoroutine(coroutine, priority);
child.return = returnFiber;
return child;
}
case REACT_YIELD_TYPE: {
// A yield results in a fragment fiber whose output is the continuation.
// TODO: When there is only a single child, we can optimize this to avoid
// the fragment.
/* $FlowFixMe(>=0.31.0): No 'continuation' property found in object
* type
*/
const yieldNode = (newChildren : ReactYield);
const reifiedYield = createReifiedYield(yieldNode);
const child = createFiberFromYield(yieldNode, priority);
child.output = reifiedYield;
child.return = returnFiber;
return child;
}
}
if (isArray(newChildren)) {
var first : ?Fiber = null;
var prev : ?Fiber = null;
var existing : ?Fiber = existingChild;
/* $FlowIssue(>=0.31.0) #12747709
*
* `Array.isArray` is matched syntactically for now until predicate
* support is complete.
*/
for (var i = 0; i < newChildren.length; i++) {
var nextExisting = existing && existing.sibling;
if (prev == null) {
prev = createFirstChild(returnFiber, existing, newChildren[i], priority);
first = prev;
} else {
prev = createSubsequentChild(returnFiber, existing, prev, newChildren[i], priority);
}
if (prev && existing) {
// TODO: This is not correct because there could've been more
// than one sibling consumed but I don't want to return a tuple.
existing = nextExisting;
}
}
return first;
} else {
// TODO: Throw for unknown children.
return null;
}
}
// TODO: This API won't work because we'll need to transfer the side-effects of
// unmounting children to the returnFiber.
function reconcileChildFibers(
returnFiber : Fiber,
currentFirstChild : ?Fiber,
newChildren : ReactNodeList,
priority : PriorityLevel
) : ?Fiber {
return createFirstChild(returnFiber, currentFirstChild, newChildren, priority);
}
return reconcileChildFibers;
}
exports.reconcileChildFibers = ChildReconciler(true);
exports.reconcileChildFibersInPlace = ChildReconciler(false);
function cloneSiblings(current : Fiber, workInProgress : Fiber, returnFiber : Fiber) {
workInProgress.return = returnFiber;
while (current.sibling) {
current = current.sibling;
workInProgress = workInProgress.sibling = cloneFiber(
current,
current.pendingWorkPriority
);
workInProgress.return = returnFiber;
}
workInProgress.sibling = null;
}
exports.cloneChildFibers = function(current : ?Fiber, workInProgress : Fiber) {
if (!workInProgress.child) {
return;
}
if (current && workInProgress.child === current.child) {
// We use workInProgress.child since that lets Flow know that it can't be
// null since we validated that already. However, as the line above suggests
// they're actually the same thing.
const currentChild = workInProgress.child;
// TODO: This used to reset the pending priority. Not sure if that is needed.
// workInProgress.pendingWorkPriority = current.pendingWorkPriority;
// TODO: The below priority used to be set to NoWork which would've
// dropped work. This is currently unobservable but will become
// observable when the first sibling has lower priority work remaining
// than the next sibling. At that point we should add tests that catches
// this.
const newChild = cloneFiber(currentChild, currentChild.pendingWorkPriority);
workInProgress.child = newChild;
cloneSiblings(currentChild, newChild, workInProgress);
}
// If there is no alternate, then we don't need to clone the children.
// If the children of the alternate fiber is a different set, then we don't
// need to clone. We need to reset the return fiber though since we'll
// traverse down into them.
let child = workInProgress.child;
while (child) {
child.return = workInProgress;
child = child.sibling;
}
};

Просмотреть файл

@ -0,0 +1,281 @@
/**
* 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 { ReactCoroutine, ReactYield } from 'ReactCoroutine';
import type { TypeOfWork } from 'ReactTypeOfWork';
import type { PriorityLevel } from 'ReactPriorityLevel';
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
var ReactTypeOfWork = require('ReactTypeOfWork');
var {
IndeterminateComponent,
ClassComponent,
HostContainer,
HostComponent,
CoroutineComponent,
YieldComponent,
} = ReactTypeOfWork;
var {
NoWork,
} = require('ReactPriorityLevel');
// 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.
type Instance = {
// 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.
};
// 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 = Instance & {
// 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,
// Singly Linked List Tree Structure.
child: ?Fiber,
sibling: ?Fiber,
// 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 : ?Object) => void,
// 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 local state updates.
updateQueue: ?UpdateQueue,
// The state used to create the output. This is a full state object.
memoizedState: any,
// Linked list of callbacks to call after updates are committed.
callbackList: ?UpdateQueue,
// Output is the return value of this fiber, or a linked list of return values
// if this returns multiple values. Such as a fragment.
output: any, // This type will be more specific once we overload the tag.
// Singly linked list fast path to the next fiber with side-effects.
nextEffect: ?Fiber,
// 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,
lastEffect: ?Fiber,
// 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,
// 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,
// Conceptual aliases
// workInProgress : Fiber -> alternate The alternate used for reuse happens
// to be the same as work in progress.
};
// 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) : Fiber {
return {
// Instance
tag: tag,
key: key,
type: null,
stateNode: null,
// Fiber
return: null,
child: null,
sibling: null,
ref: null,
pendingProps: null,
memoizedProps: null,
updateQueue: null,
memoizedState: null,
callbackList: null,
output: null,
nextEffect: null,
firstEffect: null,
lastEffect: null,
pendingWorkPriority: NoWork,
progressedPriority: NoWork,
progressedChild: null,
alternate: null,
};
};
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) {
// Whenever we clone, we do so to get a new work in progress.
// This ensures that we've reset these in the new tree.
alt.nextEffect = null;
alt.firstEffect = null;
alt.lastEffect = null;
} else {
// This should not have an alternate already
alt = createFiber(fiber.tag, fiber.key);
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.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;
alt.updateQueue = fiber.updateQueue;
alt.callbackList = fiber.callbackList;
alt.pendingWorkPriority = priorityLevel;
alt.memoizedProps = fiber.memoizedProps;
alt.output = fiber.output;
return alt;
};
exports.createHostContainerFiber = function() {
const fiber = createFiber(HostContainer, null);
return fiber;
};
exports.createFiberFromElement = function(element : ReactElement<*>, priorityLevel : PriorityLevel) {
// $FlowFixMe: ReactElement.key is currently defined as ?string but should be defined as null | string in Flow.
const fiber = createFiberFromElementType(element.type, element.key);
fiber.pendingProps = element.props;
fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
function createFiberFromElementType(type : mixed, key : null | string) {
let fiber;
if (typeof type === 'function') {
fiber = shouldConstruct(type) ?
createFiber(ClassComponent, key) :
createFiber(IndeterminateComponent, key);
fiber.type = type;
} else if (typeof type === 'string') {
fiber = createFiber(HostComponent, key);
fiber.type = type;
} else if (typeof type === 'object' && type !== null) {
// Currently assumed to be a continuation and therefore is a fiber already.
fiber = type;
} else {
throw new Error('Unknown component type: ' + typeof type);
}
return fiber;
}
exports.createFiberFromElementType = createFiberFromElementType;
exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine, priorityLevel : PriorityLevel) {
const fiber = createFiber(CoroutineComponent, coroutine.key);
fiber.type = coroutine.handler;
fiber.pendingProps = coroutine;
fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel : PriorityLevel) {
const fiber = createFiber(YieldComponent, yieldNode.key);
fiber.pendingProps = {};
return fiber;
};

Просмотреть файл

@ -0,0 +1,423 @@
/**
* 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
*/
'use strict';
import type { ReactCoroutine } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
import type { Scheduler } from 'ReactFiberScheduler';
import type { PriorityLevel } from 'ReactPriorityLevel';
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
var {
reconcileChildFibers,
reconcileChildFibersInPlace,
cloneChildFibers,
} = require('ReactChildFiber');
var { LowPriority } = require('ReactPriorityLevel');
var ReactTypeOfWork = require('ReactTypeOfWork');
var {
IndeterminateComponent,
FunctionalComponent,
ClassComponent,
HostContainer,
HostComponent,
CoroutineComponent,
CoroutineHandlerPhase,
YieldComponent,
} = ReactTypeOfWork;
var {
NoWork,
OffscreenPriority,
} = require('ReactPriorityLevel');
var {
createUpdateQueue,
addToQueue,
addCallbackToQueue,
mergeUpdateQueue,
} = require('ReactFiberUpdateQueue');
var ReactInstanceMap = require('ReactInstanceMap');
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler : () => Scheduler) {
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) {
// 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 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 && 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.
workInProgress.child = reconcileChildFibers(
workInProgress,
workInProgress.child,
nextChildren,
priorityLevel
);
} else {
// If, on the other hand, we don't have a current fiber or if 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
);
}
markChildAsProgressed(current, workInProgress, priorityLevel);
}
function updateFunctionalComponent(current, workInProgress) {
var fn = workInProgress.type;
var props = workInProgress.pendingProps;
// 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') {
if (workInProgress.memoizedProps !== null) {
if (!fn.shouldComponentUpdate(workInProgress.memoizedProps, props)) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
}
}
var nextChildren = fn(props);
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function scheduleUpdate(fiber: Fiber, updateQueue: UpdateQueue, priorityLevel : PriorityLevel): void {
const { scheduleDeferredWork } = getScheduler();
fiber.updateQueue = updateQueue;
// Schedule update on the alternate as well, since we don't know which tree
// is current.
if (fiber.alternate) {
fiber.alternate.updateQueue = updateQueue;
}
while (true) {
if (fiber.pendingWorkPriority === NoWork ||
fiber.pendingWorkPriority >= priorityLevel) {
fiber.pendingWorkPriority = priorityLevel;
}
if (fiber.alternate) {
if (fiber.alternate.pendingWorkPriority === NoWork ||
fiber.alternate.pendingWorkPriority >= priorityLevel) {
fiber.alternate.pendingWorkPriority = priorityLevel;
}
}
// Duck type root
if (fiber.stateNode && fiber.stateNode.containerInfo) {
const root : FiberRoot = (fiber.stateNode : any);
scheduleDeferredWork(root, priorityLevel);
return;
}
if (!fiber.return) {
throw new Error('No root!');
}
fiber = fiber.return;
}
}
// Class component state updater
const updater = {
enqueueSetState(instance, partialState) {
const fiber = ReactInstanceMap.get(instance);
const updateQueue = fiber.updateQueue ?
addToQueue(fiber.updateQueue, partialState) :
createUpdateQueue(partialState);
scheduleUpdate(fiber, updateQueue, LowPriority);
},
enqueueReplaceState(instance, state) {
const fiber = ReactInstanceMap.get(instance);
const updateQueue = createUpdateQueue(state);
updateQueue.isReplace = true;
scheduleUpdate(fiber, updateQueue, LowPriority);
},
enqueueForceUpdate(instance) {
const fiber = ReactInstanceMap.get(instance);
const updateQueue = fiber.updateQueue || createUpdateQueue(null);
updateQueue.isForced = true;
scheduleUpdate(fiber, updateQueue, LowPriority);
},
enqueueCallback(instance, callback) {
const fiber = ReactInstanceMap.get(instance);
let updateQueue = fiber.updateQueue ?
fiber.updateQueue :
createUpdateQueue(null);
addCallbackToQueue(updateQueue, callback);
fiber.updateQueue = updateQueue;
if (fiber.alternate) {
fiber.alternate.updateQueue = updateQueue;
}
},
};
function updateClassComponent(current : ?Fiber, workInProgress : Fiber) {
// A class component update is the result of either new props or new state.
// Account for the possibly of missing pending props by falling back to the
// memoized props.
var props = workInProgress.pendingProps;
if (!props && current) {
props = current.memoizedProps;
}
// Compute the state using the memoized state and the update queue.
var updateQueue = workInProgress.updateQueue;
var previousState = current ? current.memoizedState : null;
var state = updateQueue ?
mergeUpdateQueue(updateQueue, previousState, props) :
previousState;
var instance = workInProgress.stateNode;
if (!instance) {
var ctor = workInProgress.type;
workInProgress.stateNode = instance = new ctor(props);
state = instance.state || null;
// The initial state must be added to the update queue in case
// setState is called before the initial render.
if (state !== null) {
workInProgress.updateQueue = createUpdateQueue(state);
}
// The instance needs access to the fiber so that it can schedule updates
ReactInstanceMap.set(instance, workInProgress);
instance.updater = updater;
} else if (typeof instance.shouldComponentUpdate === 'function' &&
!(updateQueue && updateQueue.isForced)) {
if (workInProgress.memoizedProps !== null) {
// Reset the props, in case this is a ping-pong case rather than a
// completed update case. For the completed update case, the instance
// props will already be the memoizedProps.
instance.props = workInProgress.memoizedProps;
instance.state = workInProgress.memoizedState;
if (!instance.shouldComponentUpdate(props, state)) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
}
}
instance.props = props;
instance.state = state;
var nextChildren = instance.render();
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function updateHostComponent(current, workInProgress) {
const nextChildren = workInProgress.pendingProps.children;
if (workInProgress.pendingProps.hidden &&
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);
workInProgress.child = current ? current.child : null;
// Abort and don't process children yet.
return null;
} else {
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
}
function mountIndeterminateComponent(current, workInProgress) {
var fn = workInProgress.type;
var props = workInProgress.pendingProps;
var value = fn(props);
if (typeof value === 'object' && value && typeof value.render === 'function') {
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;
if (current) {
current.tag = ClassComponent;
}
value = value.render();
} else {
// Proceed under the assumption that this is a functional component
workInProgress.tag = FunctionalComponent;
if (current) {
current.tag = FunctionalComponent;
}
}
reconcileChildren(current, workInProgress, value);
return workInProgress.child;
}
function updateCoroutineComponent(current, workInProgress) {
var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
if (!coroutine) {
throw new Error('Should be resolved by now');
}
reconcileChildren(current, workInProgress, coroutine.children);
}
/*
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 {
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;
// }
cloneChildFibers(current, workInProgress);
markChildAsProgressed(current, workInProgress, priorityLevel);
return workInProgress.child;
}
function bailoutOnLowPriority(current, workInProgress) {
if (current) {
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.output = current.output;
}
return null;
}
function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
if (workInProgress.pendingWorkPriority === NoWork ||
workInProgress.pendingWorkPriority > priorityLevel) {
return bailoutOnLowPriority(current, workInProgress);
}
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;
}
if ((workInProgress.pendingProps === null || (
workInProgress.memoizedProps !== null &&
workInProgress.pendingProps === workInProgress.memoizedProps
)) &&
workInProgress.updateQueue === null) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
switch (workInProgress.tag) {
case IndeterminateComponent:
return mountIndeterminateComponent(current, workInProgress);
case FunctionalComponent:
return updateFunctionalComponent(current, workInProgress);
case ClassComponent:
return updateClassComponent(current, workInProgress);
case HostContainer:
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
// A yield component is just a placeholder, we can just run through the
// next one immediately.
if (workInProgress.child) {
return beginWork(
workInProgress.child.alternate,
workInProgress.child,
priorityLevel
);
}
return null;
case HostComponent:
if (workInProgress.stateNode && config.beginUpdate) {
config.beginUpdate(workInProgress.stateNode);
}
return updateHostComponent(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:
updateCoroutineComponent(current, workInProgress);
// This doesn't take arbitrary time so we could synchronously just begin
// eagerly do the work of workInProgress.child as an optimization.
if (workInProgress.child) {
return beginWork(
workInProgress.child.alternate,
workInProgress.child,
priorityLevel
);
}
return workInProgress.child;
case YieldComponent:
// A yield component is just a placeholder, we can just run through the
// next one immediately.
if (workInProgress.sibling) {
return beginWork(
workInProgress.sibling.alternate,
workInProgress.sibling,
priorityLevel
);
}
return null;
default:
throw new Error('Unknown unit of work tag');
}
}
return {
beginWork,
};
};

Просмотреть файл

@ -0,0 +1,80 @@
/**
* 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 { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
var ReactTypeOfWork = require('ReactTypeOfWork');
var {
ClassComponent,
HostContainer,
HostComponent,
} = ReactTypeOfWork;
var { callCallbacks } = require('ReactFiberUpdateQueue');
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
const updateContainer = config.updateContainer;
const commitUpdate = config.commitUpdate;
function commitWork(current : ?Fiber, finishedWork : Fiber) : void {
switch (finishedWork.tag) {
case ClassComponent: {
// Clear updates from current fiber. This must go before the callbacks
// are reset, in case an update is triggered from inside a callback. Is
// this safe? Relies on the assumption that work is only committed if
// the update queue is empty.
if (finishedWork.alternate) {
finishedWork.alternate.updateQueue = null;
}
if (finishedWork.callbackList) {
const { callbackList } = finishedWork;
finishedWork.callbackList = null;
callCallbacks(callbackList, finishedWork.stateNode);
}
// TODO: Fire componentDidMount/componentDidUpdate, update refs
return;
}
case HostContainer: {
// TODO: Attach children to root container.
const children = finishedWork.output;
const root : FiberRoot = finishedWork.stateNode;
const containerInfo : C = root.containerInfo;
updateContainer(containerInfo, children);
return;
}
case HostComponent: {
if (finishedWork.stateNode == null || !current) {
throw new Error('This should only be done during updates.');
}
// Commit the work prepared earlier.
const child = finishedWork.child;
const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
const instance : I = finishedWork.stateNode;
commitUpdate(instance, oldProps, newProps, children);
return;
}
default:
throw new Error('This unit of work tag should not have side-effects.');
}
}
return {
commitWork,
};
};

Просмотреть файл

@ -0,0 +1,215 @@
/**
* 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 { HostConfig } from 'ReactFiberReconciler';
import type { ReifiedYield } from 'ReactReifiedYield';
var { reconcileChildFibers } = require('ReactChildFiber');
var ReactTypeOfWork = require('ReactTypeOfWork');
var {
IndeterminateComponent,
FunctionalComponent,
ClassComponent,
HostContainer,
HostComponent,
CoroutineComponent,
CoroutineHandlerPhase,
YieldComponent,
} = ReactTypeOfWork;
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
const createInstance = config.createInstance;
const prepareUpdate = config.prepareUpdate;
function markForPreEffect(workInProgress : Fiber) {
// Schedule a side-effect on this fiber, BEFORE the children's side-effects.
if (workInProgress.firstEffect) {
workInProgress.nextEffect = workInProgress.firstEffect;
workInProgress.firstEffect = workInProgress;
} else {
workInProgress.firstEffect = workInProgress;
workInProgress.lastEffect = workInProgress;
}
}
// TODO: It's possible this will create layout thrash issues because mutations
// of the DOM and life-cycles are interleaved. E.g. if a componentDidMount
// of a sibling reads, then the next sibling updates and reads etc.
function markForPostEffect(workInProgress : Fiber) {
// Schedule a side-effect on this fiber, AFTER the children's side-effects.
if (workInProgress.lastEffect) {
workInProgress.lastEffect.nextEffect = workInProgress;
} else {
workInProgress.firstEffect = workInProgress;
}
workInProgress.lastEffect = workInProgress;
}
function transferOutput(child : ?Fiber, returnFiber : Fiber) {
// If we have a single result, we just pass that through as the output to
// avoid unnecessary traversal. When we have multiple output, we just pass
// the linked list of fibers that has the individual output values.
returnFiber.output = (child && !child.sibling) ? child.output : child;
returnFiber.memoizedProps = returnFiber.pendingProps;
}
function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
if (!output) {
// Ignore nulls etc.
} else if (output.tag !== undefined) { // TODO: Fix this fragile duck test.
// Detect if this is a fiber, if so it is a fragment result.
// $FlowFixMe: Refinement issue.
var item = (output : Fiber);
do {
recursivelyFillYields(yields, item.output);
item = item.sibling;
} while (item);
} else {
// $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield
yields.push(output);
}
}
function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
if (!coroutine) {
throw new Error('Should be resolved by now');
}
// 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<ReifiedYield> = [];
var child = workInProgress.child;
while (child) {
recursivelyFillYields(yields, child.output);
child = child.sibling;
}
var fn = coroutine.handler;
var props = coroutine.props;
var nextChildren = fn(props, yields);
var currentFirstChild = current ? current.stateNode : null;
// Inherit the priority of the returnFiber.
const priority = workInProgress.pendingWorkPriority;
workInProgress.stateNode = reconcileChildFibers(
workInProgress,
currentFirstChild,
nextChildren,
priority
);
return workInProgress.stateNode;
}
function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
switch (workInProgress.tag) {
case FunctionalComponent:
transferOutput(workInProgress.child, workInProgress);
return null;
case ClassComponent:
transferOutput(workInProgress.child, workInProgress);
// Don't use the state queue to compute the memoized state. We already
// merged it and assigned it to the instance. Transfer it from there.
// Also need to transfer the props, because pendingProps will be null
// in the case of an update
const { state, props } = workInProgress.stateNode;
workInProgress.memoizedState = state;
workInProgress.memoizedProps = props;
// Transfer update queue to callbackList field so callbacks can be
// called during commit phase.
workInProgress.callbackList = workInProgress.updateQueue;
markForPostEffect(workInProgress);
return null;
case HostContainer:
transferOutput(workInProgress.child, workInProgress);
// We don't know if a container has updated any children so we always
// need to update it right now. We schedule this side-effect before
// all the other side-effects in the subtree. We need to schedule it
// before so that the entire tree is up-to-date before the life-cycles
// are invoked.
markForPreEffect(workInProgress);
return null;
case HostComponent:
let newProps = workInProgress.pendingProps;
const child = workInProgress.child;
const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
if (current && 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.
if (!newProps) {
newProps = oldProps;
}
const instance : I = workInProgress.stateNode;
if (prepareUpdate(instance, oldProps, newProps, children)) {
// This returns true if there was something to update.
markForPreEffect(workInProgress);
}
// TODO: Is this actually ever going to change? Why set it every time?
workInProgress.output = instance;
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
throw new Error('We must have new props for new mounts.');
} else {
// This can happen when we abort work.
return null;
}
}
const instance = createInstance(workInProgress.type, newProps, children);
// TODO: This seems like unnecessary duplication.
workInProgress.stateNode = instance;
workInProgress.output = instance;
}
workInProgress.memoizedProps = newProps;
return null;
case CoroutineComponent:
return moveCoroutineToHandlerPhase(current, workInProgress);
case CoroutineHandlerPhase:
transferOutput(workInProgress.stateNode, workInProgress);
// Reset the tag to now be a first phase coroutine.
workInProgress.tag = CoroutineComponent;
return null;
case YieldComponent:
// Does nothing.
return null;
// Error cases
case IndeterminateComponent:
throw new Error('An indeterminate component should have become determinate before completing.');
default:
throw new Error('Unknown unit of work tag');
}
}
return {
completeWork,
};
};

Просмотреть файл

@ -0,0 +1,109 @@
/**
* 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 { TypeOfWork } from 'ReactTypeOfWork';
import type { PriorityLevel } from 'ReactPriorityLevel';
var { createFiberRoot } = require('ReactFiberRoot');
var ReactFiberScheduler = require('ReactFiberScheduler');
type Deadline = {
timeRemaining : () => number
};
type HostChildNode<I> = { tag: TypeOfWork, output: HostChildren<I>, sibling: any };
export type HostChildren<I> = null | void | I | HostChildNode<I>;
export type HostConfig<T, P, I, C> = {
// TODO: We don't currently have a quick way to detect that children didn't
// reorder so we host will always need to check the set. We should make a flag
// or something so that it can bailout easily.
updateContainer(containerInfo : C, children : HostChildren<I>) : void;
createInstance(type : T, props : P, children : HostChildren<I>) : I,
prepareUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I>) : boolean,
commitUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I>) : void,
deleteInstance(instance : I) : void,
scheduleAnimationCallback(callback : () => void) : void,
scheduleDeferredCallback(callback : (deadline : Deadline) => void) : void
};
type OpaqueNode = Fiber;
export type Reconciler<C> = {
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
unmountContainer(container : OpaqueNode) : void,
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
// Used to extract the return value from the initial render. Legacy API.
getPublicRootInstance(container : OpaqueNode) : (C | null),
};
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) : Reconciler<C> {
var { scheduleWork, performWithPriority } = ReactFiberScheduler(config);
return {
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode {
const root = createFiberRoot(containerInfo);
const container = root.current;
// TODO: Use pending work/state instead of props.
// TODO: This should not override the pendingWorkPriority if there is
// higher priority work in the subtree.
container.pendingProps = element;
scheduleWork(root);
// It may seem strange that we don't return the root here, but that will
// allow us to have containers that are in the middle of the tree instead
// of being roots.
return container;
},
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void {
// TODO: If this is a nested container, this won't be the root.
const root : FiberRoot = (container.stateNode : any);
// TODO: Use pending work/state instead of props.
root.current.pendingProps = element;
scheduleWork(root);
},
unmountContainer(container : OpaqueNode) : void {
// TODO: If this is a nested container, this won't be the root.
const root : FiberRoot = (container.stateNode : any);
// TODO: Use pending work/state instead of props.
root.current.pendingProps = [];
scheduleWork(root);
},
performWithPriority,
getPublicRootInstance(container : OpaqueNode) : (C | null) {
return null;
},
};
};

Просмотреть файл

@ -0,0 +1,42 @@
/**
* 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 { createHostContainerFiber } = 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,
};
exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostContainerFiber();
const root = {
current: uninitializedFiber,
containerInfo: containerInfo,
isScheduled: false,
nextScheduledRoot: null,
};
uninitializedFiber.stateNode = root;
return root;
};

Просмотреть файл

@ -0,0 +1,348 @@
/**
* 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 ReactFiberScheduler
* @flow
*/
'use strict';
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
import type { PriorityLevel } from 'ReactPriorityLevel';
var ReactFiberBeginWork = require('ReactFiberBeginWork');
var ReactFiberCompleteWork = require('ReactFiberCompleteWork');
var ReactFiberCommitWork = require('ReactFiberCommitWork');
var { cloneFiber } = require('ReactFiber');
var {
NoWork,
LowPriority,
AnimationPriority,
SynchronousPriority,
} = require('ReactPriorityLevel');
var timeHeuristicForUnitOfWork = 1;
export type Scheduler = {
scheduleDeferredWork: (root : FiberRoot, priority : PriorityLevel) => void
};
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
// Use a closure to circumvent the circular dependency between the scheduler
// and ReactFiberBeginWork. Don't know if there's a better way to do this.
let scheduler;
function getScheduler(): Scheduler {
return scheduler;
}
const { beginWork } = ReactFiberBeginWork(config, getScheduler);
const { completeWork } = ReactFiberCompleteWork(config);
const { commitWork } = ReactFiberCommitWork(config);
const scheduleAnimationCallback = config.scheduleAnimationCallback;
const scheduleDeferredCallback = config.scheduleDeferredCallback;
// The default priority to use for updates.
let defaultPriority : PriorityLevel = LowPriority;
// The next work in progress fiber that we're currently working on.
let nextUnitOfWork : ?Fiber = null;
let nextPriorityLevel : PriorityLevel = NoWork;
// Linked list of roots with scheduled work on them.
let nextScheduledRoot : ?FiberRoot = null;
let lastScheduledRoot : ?FiberRoot = null;
function findNextUnitOfWork() {
// Clear out roots with no more work on them.
while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
nextScheduledRoot.isScheduled = false;
if (nextScheduledRoot === lastScheduledRoot) {
nextScheduledRoot = null;
lastScheduledRoot = null;
nextPriorityLevel = NoWork;
return null;
}
nextScheduledRoot = nextScheduledRoot.nextScheduledRoot;
}
// TODO: This is scanning one root at a time. It should be scanning all
// roots for high priority work before moving on to lower priorities.
let root = nextScheduledRoot;
let highestPriorityRoot = null;
let highestPriorityLevel = NoWork;
while (root) {
if (highestPriorityLevel === NoWork ||
highestPriorityLevel > root.current.pendingWorkPriority) {
highestPriorityLevel = root.current.pendingWorkPriority;
highestPriorityRoot = root;
}
// We didn't find anything to do in this root, so let's try the next one.
root = root.nextScheduledRoot;
}
if (highestPriorityRoot) {
nextPriorityLevel = highestPriorityLevel;
return cloneFiber(
highestPriorityRoot.current,
highestPriorityLevel
);
}
nextPriorityLevel = NoWork;
return null;
}
function commitAllWork(finishedWork : Fiber) {
// Commit all the side-effects within a tree.
// TODO: Error handling.
let effectfulFiber = finishedWork.firstEffect;
while (effectfulFiber) {
const current = effectfulFiber.alternate;
commitWork(current, effectfulFiber);
const next = effectfulFiber.nextEffect;
// Ensure that we clean these up so that we don't accidentally keep them.
// I'm not actually sure this matters because we can't reset firstEffect
// and lastEffect since they're on every node, not just the effectful
// ones. So we have to clean everything as we reuse nodes anyway.
effectfulFiber.nextEffect = null;
effectfulFiber = next;
}
}
function resetWorkPriority(workInProgress : Fiber) {
let newPriority = NoWork;
// progressedChild is going to be the child set with the highest priority.
// Either it is the same as child, or it just bailed out because it choose
// not to do the work.
let child = workInProgress.progressedChild;
while (child) {
// Ensure that remaining work priority bubbles up.
if (child.pendingWorkPriority !== NoWork &&
(newPriority === NoWork ||
newPriority > child.pendingWorkPriority)) {
newPriority = child.pendingWorkPriority;
}
child = child.sibling;
}
workInProgress.pendingWorkPriority = newPriority;
}
function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
while (true) {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
const current = workInProgress.alternate;
const next = completeWork(current, workInProgress);
resetWorkPriority(workInProgress);
// The work is now done. We don't need this anymore. This flags
// to the system not to redo any work here.
workInProgress.pendingProps = null;
workInProgress.updateQueue = null;
const returnFiber = workInProgress.return;
if (returnFiber) {
// Ensure that the first and last effect of the parent corresponds
// to the children's first and last effect. This probably relies on
// children completing in order.
if (!returnFiber.firstEffect) {
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect) {
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
returnFiber.lastEffect = workInProgress.lastEffect;
}
}
if (next) {
// If completing this work spawned new work, do that next.
return next;
} else if (workInProgress.sibling) {
// If there is more work to do in this returnFiber, do that next.
return workInProgress.sibling;
} else if (returnFiber) {
// If there's no more work in this returnFiber. Complete the returnFiber.
workInProgress = returnFiber;
continue;
} else {
// If we're at the root, there's no more work to do. We can flush it.
const root : FiberRoot = (workInProgress.stateNode : any);
if (root.current === workInProgress) {
throw new Error(
'Cannot commit the same tree as before. This is probably a bug ' +
'related to the return field.'
);
}
root.current = workInProgress;
// TODO: We can be smarter here and only look for more work in the
// "next" scheduled work since we've already scanned passed. That
// also ensures that work scheduled during reconciliation gets deferred.
// const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
commitAllWork(workInProgress);
const nextWork = findNextUnitOfWork();
// if (!nextWork && hasMoreWork) {
// TODO: This can happen when some deep work completes and we don't
// know if this was the last one. We should be able to keep track of
// the highest priority still in the tree for one pass. But if we
// terminate an update we don't know.
// throw new Error('FiberRoots should not have flagged more work if there is none.');
// }
return nextWork;
}
}
}
function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
const current = workInProgress.alternate;
const next = beginWork(current, workInProgress, nextPriorityLevel);
if (next) {
// If this spawns new work, do that next.
return next;
} else {
// Otherwise, complete the current work.
return completeUnitOfWork(workInProgress);
}
}
function performDeferredWork(deadline) {
if (!nextUnitOfWork) {
nextUnitOfWork = findNextUnitOfWork();
}
while (nextUnitOfWork) {
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
if (!nextUnitOfWork) {
// Find more work. We might have time to complete some more.
nextUnitOfWork = findNextUnitOfWork();
}
} else {
scheduleDeferredCallback(performDeferredWork);
return;
}
}
}
function scheduleDeferredWork(root : FiberRoot, priority : PriorityLevel) {
// We must reset the current unit of work pointer so that we restart the
// search from the root during the next tick, in case there is now higher
// priority work somewhere earlier than before.
if (priority <= nextPriorityLevel) {
nextUnitOfWork = null;
}
// Set the priority on the root, without deprioritizing
if (root.current.pendingWorkPriority === NoWork ||
priority <= root.current.pendingWorkPriority) {
root.current.pendingWorkPriority = priority;
}
if (root.isScheduled) {
// If we're already scheduled, we can bail out.
return;
}
root.isScheduled = true;
if (lastScheduledRoot) {
// Schedule ourselves to the end.
lastScheduledRoot.nextScheduledRoot = root;
lastScheduledRoot = root;
} else {
// We're the only work scheduled.
nextScheduledRoot = root;
lastScheduledRoot = root;
scheduleDeferredCallback(performDeferredWork);
}
}
function performAnimationWork() {
// Always start from the root
nextUnitOfWork = findNextUnitOfWork();
while (nextUnitOfWork &&
nextPriorityLevel !== NoWork) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
if (!nextUnitOfWork) {
// Keep searching for animation work until there's no more left
nextUnitOfWork = findNextUnitOfWork();
}
// Stop if the next unit of work is low priority
if (nextPriorityLevel > AnimationPriority) {
scheduleDeferredCallback(performDeferredWork);
return;
}
}
}
function scheduleAnimationWork(root: FiberRoot, priorityLevel : PriorityLevel) {
// Set the priority on the root, without deprioritizing
if (root.current.pendingWorkPriority === NoWork ||
priorityLevel <= root.current.pendingWorkPriority) {
root.current.pendingWorkPriority = priorityLevel;
}
if (root.isScheduled) {
// If we're already scheduled, we can bail out.
return;
}
root.isScheduled = true;
if (lastScheduledRoot) {
// Schedule ourselves to the end.
lastScheduledRoot.nextScheduledRoot = root;
lastScheduledRoot = root;
} else {
// We're the only work scheduled.
nextScheduledRoot = root;
lastScheduledRoot = root;
scheduleAnimationCallback(performAnimationWork);
}
}
function scheduleWork(root : FiberRoot) {
if (defaultPriority === SynchronousPriority) {
throw new Error('Not implemented yet');
}
if (defaultPriority === NoWork) {
return;
}
if (defaultPriority > AnimationPriority) {
scheduleDeferredWork(root, defaultPriority);
return;
}
scheduleAnimationWork(root, defaultPriority);
}
function performWithPriority(priorityLevel : PriorityLevel, fn : Function) {
const previousDefaultPriority = defaultPriority;
defaultPriority = priorityLevel;
try {
fn();
} finally {
defaultPriority = previousDefaultPriority;
}
}
scheduler = {
scheduleWork: scheduleWork,
scheduleDeferredWork: scheduleDeferredWork,
performWithPriority: performWithPriority,
};
return scheduler;
};

Просмотреть файл

@ -0,0 +1,89 @@
/**
* 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';
type UpdateQueueNode = {
partialState: any,
callback: ?Function,
callbackWasCalled: boolean,
next: ?UpdateQueueNode,
};
export type UpdateQueue = UpdateQueueNode & {
isReplace: boolean,
isForced: boolean,
tail: UpdateQueueNode
};
exports.createUpdateQueue = function(partialState : mixed) : UpdateQueue {
const queue = {
partialState,
callback: null,
callbackWasCalled: false,
next: null,
isReplace: false,
isForced: false,
tail: (null : any),
};
queue.tail = queue;
return queue;
};
exports.addToQueue = function(queue : UpdateQueue, partialState : mixed) : UpdateQueue {
const node = {
partialState,
callback: null,
callbackWasCalled: false,
next: null,
};
queue.tail.next = node;
queue.tail = node;
return queue;
};
exports.addCallbackToQueue = function(queue : UpdateQueue, callback: Function) : UpdateQueue {
if (queue.tail.callback) {
// If the tail already as a callback, add an empty node to queue
exports.addToQueue(queue, null);
}
queue.tail.callback = callback;
return queue;
};
exports.callCallbacks = function(queue : UpdateQueue, context : any) {
let node : ?UpdateQueueNode = queue;
while (node) {
if (node.callback && !node.callbackWasCalled) {
node.callbackWasCalled = true;
node.callback.call(context);
}
node = node.next;
}
};
exports.mergeUpdateQueue = function(queue : UpdateQueue, prevState : any, props : any) : any {
let node : ?UpdateQueueNode = queue;
let state = queue.isReplace ? null : Object.assign({}, prevState);
while (node) {
let partialState;
if (typeof node.partialState === 'function') {
const updateFn = node.partialState;
partialState = updateFn(state, props);
} else {
partialState = node.partialState;
}
state = Object.assign(state || {}, partialState);
node = node.next;
}
return state;
};

Просмотреть файл

@ -0,0 +1,24 @@
/**
* 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;
module.exports = {
NoWork: 0, // No work is pending.
SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
AnimationPriority: 2, // Needs to complete before the next frame.
HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.
LowPriority: 4, // Data fetching, or result from updating stores.
OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.
};

Просмотреть файл

@ -0,0 +1,38 @@
/**
* 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 ReactReifiedYield
* @flow
*/
'use strict';
import type { ReactYield } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
var { createFiberFromElementType } = require('ReactFiber');
export type ReifiedYield = { continuation: Fiber, props: Object };
exports.createReifiedYield = function(yieldNode : ReactYield) : ReifiedYield {
var fiber = createFiberFromElementType(
yieldNode.continuation,
yieldNode.key
);
return {
continuation: fiber,
props: yieldNode.props,
};
};
exports.createUpdatedReifiedYield = function(previousYield : ReifiedYield, yieldNode : ReactYield) : ReifiedYield {
return {
continuation: previousYield.continuation,
props: yieldNode.props,
};
};

Просмотреть файл

@ -0,0 +1,26 @@
/**
* 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 ReactTypeOfWork
* @flow
*/
'use strict';
export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
module.exports = {
IndeterminateComponent: 0, // Before we know whether it is functional or class
FunctionalComponent: 1,
ClassComponent: 2,
HostContainer: 3, // Root of a host tree. Could be nested inside another node.
HostComponent: 4,
CoroutineComponent: 5,
CoroutineHandlerPhase: 6,
YieldComponent: 7,
};

Просмотреть файл

@ -0,0 +1,117 @@
/**
* 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 =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.coroutine')) ||
0xeac8;
var REACT_YIELD_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.yield')) ||
0xeac9;
type ReifiedYield = { continuation: Object, props: Object };
type CoroutineHandler<T> = (props: T, yields: Array<ReifiedYield>) => ReactNodeList;
export type ReactCoroutine = {
$$typeof: Symbol | number,
key: null | string,
children: any,
// This should be a more specific CoroutineHandler
handler: (props: any, yields: Array<ReifiedYield>) => ReactNodeList,
/* $FlowFixMe(>=0.31.0): Which is it? mixed? Or Object? Must match
* `ReactYield` type.
*/
props: mixed,
};
export type ReactYield = {
$$typeof: Symbol | number,
key: null | string,
props: Object,
continuation: 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(props : mixed, continuation : mixed, key : ?string = null) {
var yieldNode = {
// This tag allow us to uniquely identify this as a React Yield
$$typeof: REACT_YIELD_TYPE,
key: key == null ? null : '' + key,
props: props,
continuation: continuation,
};
if (__DEV__) {
// TODO: Add _store property for marking this as validated.
if (Object.freeze) {
Object.freeze(yieldNode.props);
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;

Просмотреть файл

@ -0,0 +1,25 @@
/**
* 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 ReactTypes
* @flow
*/
'use strict';
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
export type ReactNode = ReactElement<any> | ReactCoroutine | ReactYield | ReactText | ReactFragment;
export type ReactFragment = ReactEmpty | Iterable<ReactNode>;
export type ReactNodeList = ReactEmpty | ReactNode;
export type ReactText = string | number;
export type ReactEmpty = null | void | boolean;

Просмотреть файл

@ -0,0 +1,50 @@
/**
* 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}
);
var history: Array<Operation> = [];
var 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;

Просмотреть файл

@ -0,0 +1,40 @@
/**
* 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');
if (__DEV__) {
var processingChildContext = false;
var warnInvalidSetState = function() {
warning(
!processingChildContext,
'setState(...): Cannot call setState() inside getChildContext()'
);
};
}
var ReactInvalidSetStateWarningHook = {
onBeginProcessingChildContext(): void {
processingChildContext = true;
},
onEndProcessingChildContext(): void {
processingChildContext = false;
},
onSetState(): void {
warnInvalidSetState();
},
};
module.exports = ReactInvalidSetStateWarningHook;

Просмотреть файл

@ -0,0 +1,47 @@
/**
* 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;

Просмотреть файл

@ -0,0 +1,45 @@
/**
* 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 shouldUpdateReactComponent
*/
'use strict';
/**
* Given a `prevElement` and `nextElement`, determines if the existing
* instance should be updated as opposed to being destroyed or replaced by a new
* instance. Both arguments are elements. This ensures that this logic can
* operate on stateless trees without any backing instance.
*
* @param {?object} prevElement
* @param {?object} nextElement
* @return {boolean} True if the existing instance should be updated.
* @protected
*/
function shouldUpdateReactComponent(prevElement, nextElement) {
var prevEmpty = prevElement === null || prevElement === false;
var nextEmpty = nextElement === null || nextElement === false;
if (prevEmpty || nextEmpty) {
return prevEmpty === nextEmpty;
}
var prevType = typeof prevElement;
var nextType = typeof nextElement;
if (prevType === 'string' || prevType === 'number') {
return (nextType === 'string' || nextType === 'number');
} else {
return (
nextType === 'object' &&
prevElement.type === nextElement.type &&
prevElement.key === nextElement.key
);
}
}
module.exports = shouldUpdateReactComponent;

Просмотреть файл

@ -0,0 +1,96 @@
/**
* 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 EventConstants
*/
'use strict';
export type PropagationPhases = 'bubbled' | 'captured';
/**
* Types of raw signals from the browser caught at the top level.
*/
var topLevelTypes = {
topAbort: null,
topAnimationEnd: null,
topAnimationIteration: null,
topAnimationStart: null,
topBlur: null,
topCanPlay: null,
topCanPlayThrough: null,
topChange: null,
topClick: null,
topCompositionEnd: null,
topCompositionStart: null,
topCompositionUpdate: null,
topContextMenu: null,
topCopy: null,
topCut: null,
topDoubleClick: null,
topDrag: null,
topDragEnd: null,
topDragEnter: null,
topDragExit: null,
topDragLeave: null,
topDragOver: null,
topDragStart: null,
topDrop: null,
topDurationChange: null,
topEmptied: null,
topEncrypted: null,
topEnded: null,
topError: null,
topFocus: null,
topInput: null,
topInvalid: null,
topKeyDown: null,
topKeyPress: null,
topKeyUp: null,
topLoad: null,
topLoadedData: null,
topLoadedMetadata: null,
topLoadStart: null,
topMouseDown: null,
topMouseMove: null,
topMouseOut: null,
topMouseOver: null,
topMouseUp: null,
topPaste: null,
topPause: null,
topPlay: null,
topPlaying: null,
topProgress: null,
topRateChange: null,
topReset: null,
topScroll: null,
topSeeked: null,
topSeeking: null,
topSelectionChange: null,
topStalled: null,
topSubmit: null,
topSuspend: null,
topTextInput: null,
topTimeUpdate: null,
topTouchCancel: null,
topTouchEnd: null,
topTouchMove: null,
topTouchStart: null,
topTransitionEnd: null,
topVolumeChange: null,
topWaiting: null,
topWheel: null,
};
export type TopLevelTypes = $Enum<typeof topLevelTypes>;
var EventConstants = {
topLevelTypes,
};
module.exports = EventConstants;

Просмотреть файл

@ -0,0 +1,276 @@
/**
* 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 EventPluginHub
*/
'use strict';
var EventPluginRegistry = require('EventPluginRegistry');
var EventPluginUtils = require('EventPluginUtils');
var ReactErrorUtils = require('ReactErrorUtils');
var accumulateInto = require('accumulateInto');
var forEachAccumulated = require('forEachAccumulated');
var invariant = require('fbjs/lib/invariant');
/**
* Internal store for event listeners
*/
var listenerBank = {};
/**
* Internal queue of events that have accumulated their dispatches and are
* waiting to have their dispatches executed.
*/
var eventQueue = null;
/**
* Dispatches an event and releases it back into the pool, unless persistent.
*
* @param {?object} event Synthetic event to be dispatched.
* @param {boolean} simulated If the event is simulated (changes exn behavior)
* @private
*/
var executeDispatchesAndRelease = function(event, simulated) {
if (event) {
EventPluginUtils.executeDispatchesInOrder(event, simulated);
if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
var executeDispatchesAndReleaseSimulated = function(e) {
return executeDispatchesAndRelease(e, true);
};
var executeDispatchesAndReleaseTopLevel = function(e) {
return executeDispatchesAndRelease(e, false);
};
var getDictionaryKey = function(inst) {
// Prevents V8 performance issue:
// https://github.com/facebook/react/pull/7232
return '.' + inst._rootNodeID;
};
/**
* This is a unified interface for event plugins to be installed and configured.
*
* Event plugins can implement the following properties:
*
* `extractEvents` {function(string, DOMEventTarget, string, object): *}
* Required. When a top-level event is fired, this method is expected to
* extract synthetic events that will in turn be queued and dispatched.
*
* `eventTypes` {object}
* Optional, plugins that fire events must publish a mapping of registration
* names that are used to register listeners. Values of this mapping must
* be objects that contain `registrationName` or `phasedRegistrationNames`.
*
* `executeDispatch` {function(object, function, string)}
* Optional, allows plugins to override how an event gets dispatched. By
* default, the listener is simply invoked.
*
* Each plugin that is injected into `EventsPluginHub` is immediately operable.
*
* @public
*/
var EventPluginHub = {
/**
* Methods for injecting dependencies.
*/
injection: {
/**
* @param {array} InjectedEventPluginOrder
* @public
*/
injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder,
/**
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
*/
injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName,
},
/**
* Stores `listener` at `listenerBank[registrationName][key]`. Is idempotent.
*
* @param {object} inst The instance, which is the source of events.
* @param {string} registrationName Name of listener (e.g. `onClick`).
* @param {function} listener The callback to store.
*/
putListener: function(inst, registrationName, listener) {
invariant(
typeof listener === 'function',
'Expected %s listener to be a function, instead got type %s',
registrationName, typeof listener
);
var key = getDictionaryKey(inst);
var bankForRegistrationName =
listenerBank[registrationName] || (listenerBank[registrationName] = {});
bankForRegistrationName[key] = listener;
var PluginModule =
EventPluginRegistry.registrationNameModules[registrationName];
if (PluginModule && PluginModule.didPutListener) {
PluginModule.didPutListener(inst, registrationName, listener);
}
},
/**
* @param {object} inst The instance, which is the source of events.
* @param {string} registrationName Name of listener (e.g. `onClick`).
* @return {?function} The stored callback.
*/
getListener: function(inst, registrationName) {
var bankForRegistrationName = listenerBank[registrationName];
var key = getDictionaryKey(inst);
return bankForRegistrationName && bankForRegistrationName[key];
},
/**
* Deletes a listener from the registration bank.
*
* @param {object} inst The instance, which is the source of events.
* @param {string} registrationName Name of listener (e.g. `onClick`).
*/
deleteListener: function(inst, registrationName) {
var PluginModule =
EventPluginRegistry.registrationNameModules[registrationName];
if (PluginModule && PluginModule.willDeleteListener) {
PluginModule.willDeleteListener(inst, registrationName);
}
var bankForRegistrationName = listenerBank[registrationName];
// TODO: This should never be null -- when is it?
if (bankForRegistrationName) {
var key = getDictionaryKey(inst);
delete bankForRegistrationName[key];
}
},
/**
* Deletes all listeners for the DOM element with the supplied ID.
*
* @param {object} inst The instance, which is the source of events.
*/
deleteAllListeners: function(inst) {
var key = getDictionaryKey(inst);
for (var registrationName in listenerBank) {
if (!listenerBank.hasOwnProperty(registrationName)) {
continue;
}
if (!listenerBank[registrationName][key]) {
continue;
}
var PluginModule =
EventPluginRegistry.registrationNameModules[registrationName];
if (PluginModule && PluginModule.willDeleteListener) {
PluginModule.willDeleteListener(inst, registrationName);
}
delete listenerBank[registrationName][key];
}
},
/**
* Allows registered plugins an opportunity to extract events from top-level
* native browser events.
*
* @return {*} An accumulation of synthetic events.
* @internal
*/
extractEvents: function(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget) {
var events;
var plugins = EventPluginRegistry.plugins;
for (var i = 0; i < plugins.length; i++) {
// Not every plugin in the ordering may be loaded at runtime.
var possiblePlugin = plugins[i];
if (possiblePlugin) {
var extractedEvents = possiblePlugin.extractEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget
);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
return events;
},
/**
* Enqueues a synthetic event that should be dispatched when
* `processEventQueue` is invoked.
*
* @param {*} events An accumulation of synthetic events.
* @internal
*/
enqueueEvents: function(events) {
if (events) {
eventQueue = accumulateInto(eventQueue, events);
}
},
/**
* Dispatches all synthetic events on the event queue.
*
* @internal
*/
processEventQueue: function(simulated) {
// Set `eventQueue` to null before processing it so that we can tell if more
// events get enqueued while processing.
var processingEventQueue = eventQueue;
eventQueue = null;
if (simulated) {
forEachAccumulated(
processingEventQueue,
executeDispatchesAndReleaseSimulated
);
} else {
forEachAccumulated(
processingEventQueue,
executeDispatchesAndReleaseTopLevel
);
}
invariant(
!eventQueue,
'processEventQueue(): Additional events were enqueued while processing ' +
'an event queue. Support for this has not yet been implemented.'
);
// This would be a good time to rethrow if any of the event handlers threw.
ReactErrorUtils.rethrowCaughtError();
},
/**
* These are needed for tests only. Do not use!
*/
__purge: function() {
listenerBank = {};
},
__getListenerBank: function() {
return listenerBank;
},
};
module.exports = EventPluginHub;

Просмотреть файл

@ -0,0 +1,336 @@
/**
* 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 EventPluginRegistry
* @flow
*/
'use strict';
import type {
DispatchConfig,
ReactSyntheticEvent,
} from 'ReactSyntheticEventType';
import type {
AnyNativeEvent,
PluginName,
PluginModule,
} from 'PluginModuleType';
type NamesToPlugins = {[key: PluginName]: PluginModule<AnyNativeEvent>};
type EventPluginOrder = null | Array<PluginName>;
var invariant = require('fbjs/lib/invariant');
/**
* Injectable ordering of event plugins.
*/
var eventPluginOrder: EventPluginOrder = null;
/**
* Injectable mapping from names to event plugin modules.
*/
var namesToPlugins: NamesToPlugins = {};
/**
* Recomputes the plugin list using the injected plugins and plugin ordering.
*
* @private
*/
function recomputePluginOrdering(): void {
if (!eventPluginOrder) {
// Wait until an `eventPluginOrder` is injected.
return;
}
for (var pluginName in namesToPlugins) {
var pluginModule = namesToPlugins[pluginName];
var pluginIndex = eventPluginOrder.indexOf(pluginName);
invariant(
pluginIndex > -1,
'EventPluginRegistry: Cannot inject event plugins that do not exist in ' +
'the plugin ordering, `%s`.',
pluginName,
);
if (EventPluginRegistry.plugins[pluginIndex]) {
continue;
}
invariant(
pluginModule.extractEvents,
'EventPluginRegistry: Event plugins must implement an `extractEvents` ' +
'method, but `%s` does not.',
pluginName,
);
EventPluginRegistry.plugins[pluginIndex] = pluginModule;
var publishedEvents = pluginModule.eventTypes;
for (var eventName in publishedEvents) {
invariant(
publishEventForPlugin(
publishedEvents[eventName],
pluginModule,
eventName,
),
'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.',
eventName,
pluginName,
);
}
}
}
/**
* Publishes an event so that it can be dispatched by the supplied plugin.
*
* @param {object} dispatchConfig Dispatch configuration for the event.
* @param {object} PluginModule Plugin publishing the event.
* @return {boolean} True if the event was successfully published.
* @private
*/
function publishEventForPlugin(
dispatchConfig: DispatchConfig,
pluginModule: PluginModule<AnyNativeEvent>,
eventName: string,
): boolean {
invariant(
!EventPluginRegistry.eventNameDispatchConfigs.hasOwnProperty(eventName),
'EventPluginHub: More than one plugin attempted to publish the same ' +
'event name, `%s`.',
eventName,
);
EventPluginRegistry.eventNameDispatchConfigs[eventName] = dispatchConfig;
var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
if (phasedRegistrationNames) {
for (var phaseName in phasedRegistrationNames) {
if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
var phasedRegistrationName = phasedRegistrationNames[phaseName];
publishRegistrationName(
phasedRegistrationName,
pluginModule,
eventName,
);
}
}
return true;
} else if (dispatchConfig.registrationName) {
publishRegistrationName(
dispatchConfig.registrationName,
pluginModule,
eventName,
);
return true;
}
return false;
}
/**
* Publishes a registration name that is used to identify dispatched events and
* can be used with `EventPluginHub.putListener` to register listeners.
*
* @param {string} registrationName Registration name to add.
* @param {object} PluginModule Plugin publishing the event.
* @private
*/
function publishRegistrationName(
registrationName: string,
pluginModule: PluginModule<AnyNativeEvent>,
eventName: string,
): void {
invariant(
!EventPluginRegistry.registrationNameModules[registrationName],
'EventPluginHub: More than one plugin attempted to publish the same ' +
'registration name, `%s`.',
registrationName,
);
EventPluginRegistry.registrationNameModules[registrationName] = pluginModule;
EventPluginRegistry.registrationNameDependencies[registrationName] =
pluginModule.eventTypes[eventName].dependencies;
if (__DEV__) {
var lowerCasedName = registrationName.toLowerCase();
EventPluginRegistry.possibleRegistrationNames[lowerCasedName] =
registrationName;
if (registrationName === 'onDoubleClick') {
EventPluginRegistry.possibleRegistrationNames.ondblclick = registrationName;
}
}
}
/**
* Registers plugins so that they can extract and dispatch events.
*
* @see {EventPluginHub}
*/
var EventPluginRegistry = {
/**
* Ordered list of injected plugins.
*/
plugins: [],
/**
* Mapping from event name to dispatch config
*/
eventNameDispatchConfigs: {},
/**
* Mapping from registration name to plugin module
*/
registrationNameModules: {},
/**
* Mapping from registration name to event name
*/
registrationNameDependencies: {},
/**
* Mapping from lowercase registration names to the properly cased version,
* used to warn in the case of missing event handlers. Available
* only in __DEV__.
* @type {Object}
*/
possibleRegistrationNames: __DEV__ ? {} : (null: any),
// Trust the developer to only use possibleRegistrationNames in __DEV__
/**
* Injects an ordering of plugins (by plugin name). This allows the ordering
* to be decoupled from injection of the actual plugins so that ordering is
* always deterministic regardless of packaging, on-the-fly injection, etc.
*
* @param {array} InjectedEventPluginOrder
* @internal
* @see {EventPluginHub.injection.injectEventPluginOrder}
*/
injectEventPluginOrder: function(
injectedEventPluginOrder: EventPluginOrder,
): void {
invariant(
!eventPluginOrder,
'EventPluginRegistry: Cannot inject event plugin ordering more than ' +
'once. You are likely trying to load more than one copy of React.',
);
// Clone the ordering so it cannot be dynamically mutated.
eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
recomputePluginOrdering();
},
/**
* Injects plugins to be used by `EventPluginHub`. The plugin names must be
* in the ordering injected by `injectEventPluginOrder`.
*
* Plugins can be injected as part of page initialization or on-the-fly.
*
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
* @internal
* @see {EventPluginHub.injection.injectEventPluginsByName}
*/
injectEventPluginsByName: function(
injectedNamesToPlugins: NamesToPlugins
): void {
var isOrderingDirty = false;
for (var pluginName in injectedNamesToPlugins) {
if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
continue;
}
var pluginModule = injectedNamesToPlugins[pluginName];
if (!namesToPlugins.hasOwnProperty(pluginName) ||
namesToPlugins[pluginName] !== pluginModule) {
invariant(
!namesToPlugins[pluginName],
'EventPluginRegistry: Cannot inject two different event plugins ' +
'using the same name, `%s`.',
pluginName,
);
namesToPlugins[pluginName] = pluginModule;
isOrderingDirty = true;
}
}
if (isOrderingDirty) {
recomputePluginOrdering();
}
},
/**
* Looks up the plugin for the supplied event.
*
* @param {object} event A synthetic event.
* @return {?object} The plugin that created the supplied event.
* @internal
*/
getPluginModuleForEvent: function(
event: ReactSyntheticEvent,
): null | PluginModule<AnyNativeEvent> {
var dispatchConfig = event.dispatchConfig;
if (dispatchConfig.registrationName) {
return EventPluginRegistry.registrationNameModules[
dispatchConfig.registrationName
] || null;
}
if (dispatchConfig.phasedRegistrationNames !== undefined) {
// pulling phasedRegistrationNames out of dispatchConfig helps Flow see
// that it is not undefined.
var {phasedRegistrationNames} = dispatchConfig;
for (var phase in phasedRegistrationNames) {
if (!phasedRegistrationNames.hasOwnProperty(phase)) {
continue;
}
var pluginModule = EventPluginRegistry.registrationNameModules[
phasedRegistrationNames[phase]
];
if (pluginModule) {
return pluginModule;
}
}
}
return null;
},
/**
* Exposed for unit testing.
* @private
*/
_resetEventPlugins: function(): void {
eventPluginOrder = null;
for (var pluginName in namesToPlugins) {
if (namesToPlugins.hasOwnProperty(pluginName)) {
delete namesToPlugins[pluginName];
}
}
EventPluginRegistry.plugins.length = 0;
var eventNameDispatchConfigs = EventPluginRegistry.eventNameDispatchConfigs;
for (var eventName in eventNameDispatchConfigs) {
if (eventNameDispatchConfigs.hasOwnProperty(eventName)) {
delete eventNameDispatchConfigs[eventName];
}
}
var registrationNameModules = EventPluginRegistry.registrationNameModules;
for (var registrationName in registrationNameModules) {
if (registrationNameModules.hasOwnProperty(registrationName)) {
delete registrationNameModules[registrationName];
}
}
if (__DEV__) {
var possibleRegistrationNames =
EventPluginRegistry.possibleRegistrationNames;
for (var lowerCasedName in possibleRegistrationNames) {
if (possibleRegistrationNames.hasOwnProperty(lowerCasedName)) {
delete possibleRegistrationNames[lowerCasedName];
}
}
}
},
};
module.exports = EventPluginRegistry;

Просмотреть файл

@ -0,0 +1,258 @@
/**
* 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 EventPluginUtils
*/
'use strict';
var ReactErrorUtils = require('ReactErrorUtils');
var invariant = require('fbjs/lib/invariant');
var warning = require('fbjs/lib/warning');
/**
* Injected dependencies:
*/
/**
* - `ComponentTree`: [required] Module that can convert between React instances
* and actual node references.
*/
var ComponentTree;
var TreeTraversal;
var injection = {
injectComponentTree: function(Injected) {
ComponentTree = Injected;
if (__DEV__) {
warning(
Injected &&
Injected.getNodeFromInstance &&
Injected.getInstanceFromNode,
'EventPluginUtils.injection.injectComponentTree(...): Injected ' +
'module is missing getNodeFromInstance or getInstanceFromNode.'
);
}
},
injectTreeTraversal: function(Injected) {
TreeTraversal = Injected;
if (__DEV__) {
warning(
Injected && Injected.isAncestor && Injected.getLowestCommonAncestor,
'EventPluginUtils.injection.injectTreeTraversal(...): Injected ' +
'module is missing isAncestor or getLowestCommonAncestor.'
);
}
},
};
function isEndish(topLevelType) {
return topLevelType === 'topMouseUp' ||
topLevelType === 'topTouchEnd' ||
topLevelType === 'topTouchCancel';
}
function isMoveish(topLevelType) {
return topLevelType === 'topMouseMove' ||
topLevelType === 'topTouchMove';
}
function isStartish(topLevelType) {
return topLevelType === 'topMouseDown' ||
topLevelType === 'topTouchStart';
}
var validateEventDispatches;
if (__DEV__) {
validateEventDispatches = function(event) {
var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;
var listenersIsArr = Array.isArray(dispatchListeners);
var listenersLen = listenersIsArr ?
dispatchListeners.length :
dispatchListeners ? 1 : 0;
var instancesIsArr = Array.isArray(dispatchInstances);
var instancesLen = instancesIsArr ?
dispatchInstances.length :
dispatchInstances ? 1 : 0;
warning(
instancesIsArr === listenersIsArr && instancesLen === listenersLen,
'EventPluginUtils: Invalid `event`.'
);
};
}
/**
* Dispatch the event to the listener.
* @param {SyntheticEvent} event SyntheticEvent to handle
* @param {boolean} simulated If the event is simulated (changes exn behavior)
* @param {function} listener Application-level callback
* @param {*} inst Internal component instance
*/
function executeDispatch(event, simulated, listener, inst) {
var type = event.type || 'unknown-event';
event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);
if (simulated) {
ReactErrorUtils.invokeGuardedCallbackWithCatch(
type,
listener,
event
);
} else {
ReactErrorUtils.invokeGuardedCallback(type, listener, event);
}
event.currentTarget = null;
}
/**
* Standard/simple iteration through an event's collected dispatches.
*/
function executeDispatchesInOrder(event, simulated) {
var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;
if (__DEV__) {
validateEventDispatches(event);
}
if (Array.isArray(dispatchListeners)) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
// Listeners and Instances are two parallel arrays that are always in sync.
executeDispatch(
event,
simulated,
dispatchListeners[i],
dispatchInstances[i]
);
}
} else if (dispatchListeners) {
executeDispatch(event, simulated, dispatchListeners, dispatchInstances);
}
event._dispatchListeners = null;
event._dispatchInstances = null;
}
/**
* Standard/simple iteration through an event's collected dispatches, but stops
* at the first dispatch execution returning true, and returns that id.
*
* @return {?string} id of the first dispatch execution who's listener returns
* true, or null if no listener returned true.
*/
function executeDispatchesInOrderStopAtTrueImpl(event) {
var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;
if (__DEV__) {
validateEventDispatches(event);
}
if (Array.isArray(dispatchListeners)) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
// Listeners and Instances are two parallel arrays that are always in sync.
if (dispatchListeners[i](event, dispatchInstances[i])) {
return dispatchInstances[i];
}
}
} else if (dispatchListeners) {
if (dispatchListeners(event, dispatchInstances)) {
return dispatchInstances;
}
}
return null;
}
/**
* @see executeDispatchesInOrderStopAtTrueImpl
*/
function executeDispatchesInOrderStopAtTrue(event) {
var ret = executeDispatchesInOrderStopAtTrueImpl(event);
event._dispatchInstances = null;
event._dispatchListeners = null;
return ret;
}
/**
* Execution of a "direct" dispatch - there must be at most one dispatch
* accumulated on the event or it is considered an error. It doesn't really make
* sense for an event with multiple dispatches (bubbled) to keep track of the
* return values at each dispatch execution, but it does tend to make sense when
* dealing with "direct" dispatches.
*
* @return {*} The return value of executing the single dispatch.
*/
function executeDirectDispatch(event) {
if (__DEV__) {
validateEventDispatches(event);
}
var dispatchListener = event._dispatchListeners;
var dispatchInstance = event._dispatchInstances;
invariant(
!Array.isArray(dispatchListener),
'executeDirectDispatch(...): Invalid `event`.'
);
event.currentTarget = dispatchListener ? EventPluginUtils.getNodeFromInstance(dispatchInstance) : null;
var res = dispatchListener ? dispatchListener(event) : null;
event.currentTarget = null;
event._dispatchListeners = null;
event._dispatchInstances = null;
return res;
}
/**
* @param {SyntheticEvent} event
* @return {boolean} True iff number of dispatches accumulated is greater than 0.
*/
function hasDispatches(event) {
return !!event._dispatchListeners;
}
/**
* General utilities that are useful in creating custom Event Plugins.
*/
var EventPluginUtils = {
isEndish: isEndish,
isMoveish: isMoveish,
isStartish: isStartish,
executeDirectDispatch: executeDirectDispatch,
executeDispatchesInOrder: executeDispatchesInOrder,
executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue,
hasDispatches: hasDispatches,
getInstanceFromNode: function(node) {
return ComponentTree.getInstanceFromNode(node);
},
getNodeFromInstance: function(node) {
return ComponentTree.getNodeFromInstance(node);
},
isAncestor: function(a, b) {
return TreeTraversal.isAncestor(a, b);
},
getLowestCommonAncestor: function(a, b) {
return TreeTraversal.getLowestCommonAncestor(a, b);
},
getParentInstance: function(inst) {
return TreeTraversal.getParentInstance(inst);
},
traverseTwoPhase: function(target, fn, arg) {
return TreeTraversal.traverseTwoPhase(target, fn, arg);
},
traverseEnterLeave: function(from, to, fn, argFrom, argTo) {
return TreeTraversal.traverseEnterLeave(from, to, fn, argFrom, argTo);
},
injection: injection,
};
module.exports = EventPluginUtils;

Просмотреть файл

@ -0,0 +1,161 @@
/**
* 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 EventPropagators
*/
'use strict';
var EventPluginHub = require('EventPluginHub');
var EventPluginUtils = require('EventPluginUtils');
var accumulateInto = require('accumulateInto');
var forEachAccumulated = require('forEachAccumulated');
var warning = require('fbjs/lib/warning');
import type { PropagationPhases } from 'EventConstants';
var getListener = EventPluginHub.getListener;
/**
* Some event types have a notion of different registration names for different
* "phases" of propagation. This finds listeners by a given phase.
*/
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
var registrationName =
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(inst, registrationName);
}
/**
* Tags a `SyntheticEvent` with dispatched listeners. Creating this function
* here, allows us to not have to bind or create functions for each event.
* Mutating the event's members allows us to not have to create a wrapping
* "dispatch" object that pairs the event with the listener.
*/
function accumulateDirectionalDispatches(inst, phase, event) {
if (__DEV__) {
warning(
inst,
'Dispatching inst must not be null'
);
}
var listener = listenerAtPhase(inst, event, phase);
if (listener) {
event._dispatchListeners =
accumulateInto(event._dispatchListeners, listener);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
/**
* Collect dispatches (must be entirely collected before dispatching - see unit
* tests). Lazily allocate the array to conserve memory. We must loop through
* each event and perform the traversal for each one. We cannot perform a
* single traversal for the entire collection of events because each event may
* have a different target.
*/
function accumulateTwoPhaseDispatchesSingle(event) {
if (event && event.dispatchConfig.phasedRegistrationNames) {
EventPluginUtils.traverseTwoPhase(
event._targetInst,
accumulateDirectionalDispatches,
event
);
}
}
/**
* Same as `accumulateTwoPhaseDispatchesSingle`, but skips over the targetID.
*/
function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
if (event && event.dispatchConfig.phasedRegistrationNames) {
var targetInst = event._targetInst;
var parentInst =
targetInst ? EventPluginUtils.getParentInstance(targetInst) : null;
EventPluginUtils.traverseTwoPhase(
parentInst,
accumulateDirectionalDispatches,
event
);
}
}
/**
* Accumulates without regard to direction, does not look for phased
* registration names. Same as `accumulateDirectDispatchesSingle` but without
* requiring that the `dispatchMarker` be the same as the dispatched ID.
*/
function accumulateDispatches(inst, ignoredDirection, event) {
if (event && event.dispatchConfig.registrationName) {
var registrationName = event.dispatchConfig.registrationName;
var listener = getListener(inst, registrationName);
if (listener) {
event._dispatchListeners =
accumulateInto(event._dispatchListeners, listener);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
}
/**
* Accumulates dispatches on an `SyntheticEvent`, but only for the
* `dispatchMarker`.
* @param {SyntheticEvent} event
*/
function accumulateDirectDispatchesSingle(event) {
if (event && event.dispatchConfig.registrationName) {
accumulateDispatches(event._targetInst, null, event);
}
}
function accumulateTwoPhaseDispatches(events) {
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}
function accumulateTwoPhaseDispatchesSkipTarget(events) {
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
}
function accumulateEnterLeaveDispatches(leave, enter, from, to) {
EventPluginUtils.traverseEnterLeave(
from,
to,
accumulateDispatches,
leave,
enter
);
}
function accumulateDirectDispatches(events) {
forEachAccumulated(events, accumulateDirectDispatchesSingle);
}
/**
* A small set of propagation patterns, each of which will accept a small amount
* of information, and generate a set of "dispatch ready event objects" - which
* are sets of events that have already been annotated with a set of dispatched
* listener functions/ids. The API is designed this way to discourage these
* propagation strategies from actually executing the dispatches, since we
* always want to collect the entire set of dispatches before executing event a
* single one.
*
* @constructor EventPropagators
*/
var EventPropagators = {
accumulateTwoPhaseDispatches: accumulateTwoPhaseDispatches,
accumulateTwoPhaseDispatchesSkipTarget: accumulateTwoPhaseDispatchesSkipTarget,
accumulateDirectDispatches: accumulateDirectDispatches,
accumulateEnterLeaveDispatches: accumulateEnterLeaveDispatches,
};
module.exports = EventPropagators;

Просмотреть файл

@ -0,0 +1,49 @@
/**
* 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 PluginModuleType
* @flow
*/
'use strict';
import type {ReactInstance} from 'ReactInstanceType';
import type {
DispatchConfig,
ReactSyntheticEvent,
} from 'ReactSyntheticEventType';
export type EventTypes = {[key: string]: DispatchConfig};
export type AnyNativeEvent =
Event |
KeyboardEvent |
MouseEvent |
Touch;
export type PluginName = string;
export type PluginModule<NativeEvent> = {
eventTypes: EventTypes,
extractEvents: (
topLevelType: string,
targetInst: ReactInstance,
nativeTarget: NativeEvent,
nativeEventTarget: EventTarget,
) => null | ReactSyntheticEvent,
didPutListener?: (
inst: ReactInstance,
registrationName: string,
listener: () => void,
) => void,
willDeleteListener?: (
inst: ReactInstance,
registrationName: string,
) => void,
tapMoveThreshold?: number,
};

Просмотреть файл

@ -0,0 +1,36 @@
/*
* 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.
*
* Flow type for SyntheticEvent class that includes private properties
*
* @providesModule ReactSyntheticEventType
* @flow
*/
'use strict';
import type {ReactInstance} from 'ReactInstanceType';
export type DispatchConfig = {
dependencies: Array<string>,
phasedRegistrationNames?: {
bubbled: string,
captured: string,
},
registrationName?: string,
};
export type ReactSyntheticEvent = {
dispatchConfig: DispatchConfig;
getPooled: (
dispatchConfig: DispatchConfig,
targetInst: ReactInstance,
nativeTarget: Event,
nativeEventTarget: EventTarget,
) => ReactSyntheticEvent;
} & SyntheticEvent;

Просмотреть файл

@ -0,0 +1,303 @@
/**
* 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 SyntheticEvent
*/
'use strict';
var PooledClass = require('PooledClass');
var emptyFunction = require('fbjs/lib/emptyFunction');
var warning = require('fbjs/lib/warning');
var didWarnForAddedNewProperty = false;
var isProxySupported = typeof Proxy === 'function';
var shouldBeReleasedProperties = [
'dispatchConfig',
'_targetInst',
'nativeEvent',
'isDefaultPrevented',
'isPropagationStopped',
'_dispatchListeners',
'_dispatchInstances',
];
/**
* @interface Event
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
var EventInterface = {
type: null,
target: null,
// currentTarget is set when dispatching; no use in copying it here
currentTarget: emptyFunction.thatReturnsNull,
eventPhase: null,
bubbles: null,
cancelable: null,
timeStamp: function(event) {
return event.timeStamp || Date.now();
},
defaultPrevented: null,
isTrusted: null,
};
/**
* Synthetic events are dispatched by event plugins, typically in response to a
* top-level event delegation handler.
*
* These systems should generally use pooling to reduce the frequency of garbage
* collection. The system should check `isPersistent` to determine whether the
* event should be released into the pool after being dispatched. Users that
* need a persisted event should invoke `persist`.
*
* Synthetic events (and subclasses) implement the DOM Level 3 Events API by
* normalizing browser quirks. Subclasses do not necessarily have to implement a
* DOM interface; custom application-specific events can also subclass this.
*
* @param {object} dispatchConfig Configuration used to dispatch this event.
* @param {*} targetInst Marker identifying the event target.
* @param {object} nativeEvent Native browser event.
* @param {DOMEventTarget} nativeEventTarget Target node.
*/
function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
if (__DEV__) {
// these have a getter/setter for warnings
delete this.nativeEvent;
delete this.preventDefault;
delete this.stopPropagation;
}
this.dispatchConfig = dispatchConfig;
this._targetInst = targetInst;
this.nativeEvent = nativeEvent;
var Interface = this.constructor.Interface;
for (var propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}
if (__DEV__) {
delete this[propName]; // this has a getter/setter for warnings
}
var normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
if (propName === 'target') {
this.target = nativeEventTarget;
} else {
this[propName] = nativeEvent[propName];
}
}
}
var defaultPrevented = nativeEvent.defaultPrevented != null ?
nativeEvent.defaultPrevented :
nativeEvent.returnValue === false;
if (defaultPrevented) {
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
} else {
this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
}
this.isPropagationStopped = emptyFunction.thatReturnsFalse;
return this;
}
Object.assign(SyntheticEvent.prototype, {
preventDefault: function() {
this.defaultPrevented = true;
var event = this.nativeEvent;
if (!event) {
return;
}
if (event.preventDefault) {
event.preventDefault();
} else if (typeof event.returnValue !== 'unknown') { // eslint-disable-line valid-typeof
event.returnValue = false;
}
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
},
stopPropagation: function() {
var event = this.nativeEvent;
if (!event) {
return;
}
if (event.stopPropagation) {
event.stopPropagation();
} else if (typeof event.cancelBubble !== 'unknown') { // eslint-disable-line valid-typeof
// The ChangeEventPlugin registers a "propertychange" event for
// IE. This event does not support bubbling or cancelling, and
// any references to cancelBubble throw "Member not found". A
// typeof check of "unknown" circumvents this issue (and is also
// IE specific).
event.cancelBubble = true;
}
this.isPropagationStopped = emptyFunction.thatReturnsTrue;
},
/**
* We release all dispatched `SyntheticEvent`s after each event loop, adding
* them back into the pool. This allows a way to hold onto a reference that
* won't be added back into the pool.
*/
persist: function() {
this.isPersistent = emptyFunction.thatReturnsTrue;
},
/**
* Checks if this event should be released back into the pool.
*
* @return {boolean} True if this should not be released, false otherwise.
*/
isPersistent: emptyFunction.thatReturnsFalse,
/**
* `PooledClass` looks for `destructor` on each instance it releases.
*/
destructor: function() {
var Interface = this.constructor.Interface;
for (var propName in Interface) {
if (__DEV__) {
Object.defineProperty(this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName]));
} else {
this[propName] = null;
}
}
for (var i = 0; i < shouldBeReleasedProperties.length; i++) {
this[shouldBeReleasedProperties[i]] = null;
}
if (__DEV__) {
Object.defineProperty(
this,
'nativeEvent',
getPooledWarningPropertyDefinition('nativeEvent', null)
);
Object.defineProperty(
this,
'preventDefault',
getPooledWarningPropertyDefinition('preventDefault', emptyFunction)
);
Object.defineProperty(
this,
'stopPropagation',
getPooledWarningPropertyDefinition('stopPropagation', emptyFunction)
);
}
},
});
SyntheticEvent.Interface = EventInterface;
if (__DEV__) {
if (isProxySupported) {
/*eslint-disable no-func-assign */
SyntheticEvent = new Proxy(SyntheticEvent, {
construct: function(target, args) {
return this.apply(target, Object.create(target.prototype), args);
},
apply: function(constructor, that, args) {
return new Proxy(constructor.apply(that, args), {
set: function(target, prop, value) {
if (prop !== 'isPersistent' &&
!target.constructor.Interface.hasOwnProperty(prop) &&
shouldBeReleasedProperties.indexOf(prop) === -1) {
warning(
didWarnForAddedNewProperty || target.isPersistent(),
'This synthetic event is reused for performance reasons. If you\'re ' +
'seeing this, you\'re adding a new property in the synthetic event object. ' +
'The property is never released. See ' +
'https://fb.me/react-event-pooling for more information.'
);
didWarnForAddedNewProperty = true;
}
target[prop] = value;
return true;
},
});
},
});
/*eslint-enable no-func-assign */
}
}
/**
* Helper to reduce boilerplate when creating subclasses.
*
* @param {function} Class
* @param {?object} Interface
*/
SyntheticEvent.augmentClass = function(Class, Interface) {
var Super = this;
var E = function() {};
E.prototype = Super.prototype;
var prototype = new E();
Object.assign(prototype, Class.prototype);
Class.prototype = prototype;
Class.prototype.constructor = Class;
Class.Interface = Object.assign({}, Super.Interface, Interface);
Class.augmentClass = Super.augmentClass;
PooledClass.addPoolingTo(Class, PooledClass.fourArgumentPooler);
};
PooledClass.addPoolingTo(SyntheticEvent, PooledClass.fourArgumentPooler);
module.exports = SyntheticEvent;
/**
* Helper to nullify syntheticEvent instance properties when destructing
*
* @param {object} SyntheticEvent
* @param {String} propName
* @return {object} defineProperty object
*/
function getPooledWarningPropertyDefinition(propName, getVal) {
var isFunction = typeof getVal === 'function';
return {
configurable: true,
set: set,
get: get,
};
function set(val) {
var action = isFunction ? 'setting the method' : 'setting the property';
warn(action, 'This is effectively a no-op');
return val;
}
function get() {
var action = isFunction ? 'accessing the method' : 'accessing the property';
var result = isFunction ? 'This is a no-op function' : 'This is set to null';
warn(action, result);
return getVal;
}
function warn(action, result) {
var warningCondition = false;
warning(
warningCondition,
'This synthetic event is reused for performance reasons. If you\'re seeing this, ' +
'you\'re %s `%s` on a released/nullified synthetic event. %s. ' +
'If you must keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
action,
propName,
result
);
}
}

Просмотреть файл

@ -0,0 +1,598 @@
/**
* 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 ResponderEventPlugin
*/
'use strict';
var EventPluginUtils = require('EventPluginUtils');
var EventPropagators = require('EventPropagators');
var ResponderSyntheticEvent = require('ResponderSyntheticEvent');
var ResponderTouchHistoryStore = require('ResponderTouchHistoryStore');
var accumulate = require('accumulate');
var isStartish = EventPluginUtils.isStartish;
var isMoveish = EventPluginUtils.isMoveish;
var isEndish = EventPluginUtils.isEndish;
var executeDirectDispatch = EventPluginUtils.executeDirectDispatch;
var hasDispatches = EventPluginUtils.hasDispatches;
var executeDispatchesInOrderStopAtTrue =
EventPluginUtils.executeDispatchesInOrderStopAtTrue;
/**
* Instance of element that should respond to touch/move types of interactions,
* as indicated explicitly by relevant callbacks.
*/
var responderInst = null;
/**
* Count of current touches. A textInput should become responder iff the
* selection changes while there is a touch on the screen.
*/
var trackedTouchCount = 0;
/**
* Last reported number of active touches.
*/
var previousActiveTouches = 0;
var changeResponder = function(nextResponderInst, blockHostResponder) {
var oldResponderInst = responderInst;
responderInst = nextResponderInst;
if (ResponderEventPlugin.GlobalResponderHandler !== null) {
ResponderEventPlugin.GlobalResponderHandler.onChange(
oldResponderInst,
nextResponderInst,
blockHostResponder
);
}
};
var eventTypes = {
/**
* On a `touchStart`/`mouseDown`, is it desired that this element become the
* responder?
*/
startShouldSetResponder: {
phasedRegistrationNames: {
bubbled: 'onStartShouldSetResponder',
captured: 'onStartShouldSetResponderCapture',
},
},
/**
* On a `scroll`, is it desired that this element become the responder? This
* is usually not needed, but should be used to retroactively infer that a
* `touchStart` had occurred during momentum scroll. During a momentum scroll,
* a touch start will be immediately followed by a scroll event if the view is
* currently scrolling.
*
* TODO: This shouldn't bubble.
*/
scrollShouldSetResponder: {
phasedRegistrationNames: {
bubbled: 'onScrollShouldSetResponder',
captured: 'onScrollShouldSetResponderCapture',
},
},
/**
* On text selection change, should this element become the responder? This
* is needed for text inputs or other views with native selection, so the
* JS view can claim the responder.
*
* TODO: This shouldn't bubble.
*/
selectionChangeShouldSetResponder: {
phasedRegistrationNames: {
bubbled: 'onSelectionChangeShouldSetResponder',
captured: 'onSelectionChangeShouldSetResponderCapture',
},
},
/**
* On a `touchMove`/`mouseMove`, is it desired that this element become the
* responder?
*/
moveShouldSetResponder: {
phasedRegistrationNames: {
bubbled: 'onMoveShouldSetResponder',
captured: 'onMoveShouldSetResponderCapture',
},
},
/**
* Direct responder events dispatched directly to responder. Do not bubble.
*/
responderStart: {registrationName: 'onResponderStart'},
responderMove: {registrationName: 'onResponderMove'},
responderEnd: {registrationName: 'onResponderEnd'},
responderRelease: {registrationName: 'onResponderRelease'},
responderTerminationRequest: {
registrationName: 'onResponderTerminationRequest',
},
responderGrant: {registrationName: 'onResponderGrant'},
responderReject: {registrationName: 'onResponderReject'},
responderTerminate: {registrationName: 'onResponderTerminate'},
};
/**
*
* Responder System:
* ----------------
*
* - A global, solitary "interaction lock" on a view.
* - If a node becomes the responder, it should convey visual feedback
* immediately to indicate so, either by highlighting or moving accordingly.
* - To be the responder means, that touches are exclusively important to that
* responder view, and no other view.
* - While touches are still occurring, the responder lock can be transferred to
* a new view, but only to increasingly "higher" views (meaning ancestors of
* the current responder).
*
* Responder being granted:
* ------------------------
*
* - Touch starts, moves, and scrolls can cause an ID to become the responder.
* - We capture/bubble `startShouldSetResponder`/`moveShouldSetResponder` to
* the "appropriate place".
* - If nothing is currently the responder, the "appropriate place" is the
* initiating event's `targetID`.
* - If something *is* already the responder, the "appropriate place" is the
* first common ancestor of the event target and the current `responderInst`.
* - Some negotiation happens: See the timing diagram below.
* - Scrolled views automatically become responder. The reasoning is that a
* platform scroll view that isn't built on top of the responder system has
* began scrolling, and the active responder must now be notified that the
* interaction is no longer locked to it - the system has taken over.
*
* - Responder being released:
* As soon as no more touches that *started* inside of descendants of the
* *current* responderInst, an `onResponderRelease` event is dispatched to the
* current responder, and the responder lock is released.
*
* TODO:
* - on "end", a callback hook for `onResponderEndShouldRemainResponder` that
* determines if the responder lock should remain.
* - If a view shouldn't "remain" the responder, any active touches should by
* default be considered "dead" and do not influence future negotiations or
* bubble paths. It should be as if those touches do not exist.
* -- For multitouch: Usually a translate-z will choose to "remain" responder
* after one out of many touches ended. For translate-y, usually the view
* doesn't wish to "remain" responder after one of many touches end.
* - Consider building this on top of a `stopPropagation` model similar to
* `W3C` events.
* - Ensure that `onResponderTerminate` is called on touch cancels, whether or
* not `onResponderTerminationRequest` returns `true` or `false`.
*
*/
/* Negotiation Performed
+-----------------------+
/ \
Process low level events to + Current Responder + wantsResponderID
determine who to perform negot-| (if any exists at all) |
iation/transition | Otherwise just pass through|
-------------------------------+----------------------------+------------------+
Bubble to find first ID | |
to return true:wantsResponderID| |
| |
+-------------+ | |
| onTouchStart| | |
+------+------+ none | |
| return| |
+-----------v-------------+true| +------------------------+ |
|onStartShouldSetResponder|----->|onResponderStart (cur) |<-----------+
+-----------+-------------+ | +------------------------+ | |
| | | +--------+-------+
| returned true for| false:REJECT +-------->|onResponderReject
| wantsResponderID | | | +----------------+
| (now attempt | +------------------+-----+ |
| handoff) | | onResponder | |
+------------------->| TerminationRequest| |
| +------------------+-----+ |
| | | +----------------+
| true:GRANT +-------->|onResponderGrant|
| | +--------+-------+
| +------------------------+ | |
| | onResponderTerminate |<-----------+
| +------------------+-----+ |
| | | +----------------+
| +-------->|onResponderStart|
| | +----------------+
Bubble to find first ID | |
to return true:wantsResponderID| |
| |
+-------------+ | |
| onTouchMove | | |
+------+------+ none | |
| return| |
+-----------v-------------+true| +------------------------+ |
|onMoveShouldSetResponder |----->|onResponderMove (cur) |<-----------+
+-----------+-------------+ | +------------------------+ | |
| | | +--------+-------+
| returned true for| false:REJECT +-------->|onResponderRejec|
| wantsResponderID | | | +----------------+
| (now attempt | +------------------+-----+ |
| handoff) | | onResponder | |
+------------------->| TerminationRequest| |
| +------------------+-----+ |
| | | +----------------+
| true:GRANT +-------->|onResponderGrant|
| | +--------+-------+
| +------------------------+ | |
| | onResponderTerminate |<-----------+
| +------------------+-----+ |
| | | +----------------+
| +-------->|onResponderMove |
| | +----------------+
| |
| |
Some active touch started| |
inside current responder | +------------------------+ |
+------------------------->| onResponderEnd | |
| | +------------------------+ |
+---+---------+ | |
| onTouchEnd | | |
+---+---------+ | |
| | +------------------------+ |
+------------------------->| onResponderEnd | |
No active touches started| +-----------+------------+ |
inside current responder | | |
| v |
| +------------------------+ |
| | onResponderRelease | |
| +------------------------+ |
| |
+ + */
/**
* A note about event ordering in the `EventPluginHub`.
*
* Suppose plugins are injected in the following order:
*
* `[R, S, C]`
*
* To help illustrate the example, assume `S` is `SimpleEventPlugin` (for
* `onClick` etc) and `R` is `ResponderEventPlugin`.
*
* "Deferred-Dispatched Events":
*
* - The current event plugin system will traverse the list of injected plugins,
* in order, and extract events by collecting the plugin's return value of
* `extractEvents()`.
* - These events that are returned from `extractEvents` are "deferred
* dispatched events".
* - When returned from `extractEvents`, deferred-dispatched events contain an
* "accumulation" of deferred dispatches.
* - These deferred dispatches are accumulated/collected before they are
* returned, but processed at a later time by the `EventPluginHub` (hence the
* name deferred).
*
* In the process of returning their deferred-dispatched events, event plugins
* themselves can dispatch events on-demand without returning them from
* `extractEvents`. Plugins might want to do this, so that they can use event
* dispatching as a tool that helps them decide which events should be extracted
* in the first place.
*
* "On-Demand-Dispatched Events":
*
* - On-demand-dispatched events are not returned from `extractEvents`.
* - On-demand-dispatched events are dispatched during the process of returning
* the deferred-dispatched events.
* - They should not have side effects.
* - They should be avoided, and/or eventually be replaced with another
* abstraction that allows event plugins to perform multiple "rounds" of event
* extraction.
*
* Therefore, the sequence of event dispatches becomes:
*
* - `R`s on-demand events (if any) (dispatched by `R` on-demand)
* - `S`s on-demand events (if any) (dispatched by `S` on-demand)
* - `C`s on-demand events (if any) (dispatched by `C` on-demand)
* - `R`s extracted events (if any) (dispatched by `EventPluginHub`)
* - `S`s extracted events (if any) (dispatched by `EventPluginHub`)
* - `C`s extracted events (if any) (dispatched by `EventPluginHub`)
*
* In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
* on-demand dispatch returns `true` (and some other details are satisfied) the
* `onResponderGrant` deferred dispatched event is returned from
* `extractEvents`. The sequence of dispatch executions in this case
* will appear as follows:
*
* - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
* - `touchStartCapture` (`EventPluginHub` dispatches as usual)
* - `touchStart` (`EventPluginHub` dispatches as usual)
* - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
*/
function setResponderAndExtractTransfer(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget
) {
var shouldSetEventType =
isStartish(topLevelType) ? eventTypes.startShouldSetResponder :
isMoveish(topLevelType) ? eventTypes.moveShouldSetResponder :
topLevelType === 'topSelectionChange' ?
eventTypes.selectionChangeShouldSetResponder :
eventTypes.scrollShouldSetResponder;
// TODO: stop one short of the current responder.
var bubbleShouldSetFrom = !responderInst ?
targetInst :
EventPluginUtils.getLowestCommonAncestor(responderInst, targetInst);
// When capturing/bubbling the "shouldSet" event, we want to skip the target
// (deepest ID) if it happens to be the current responder. The reasoning:
// It's strange to get an `onMoveShouldSetResponder` when you're *already*
// the responder.
var skipOverBubbleShouldSetFrom = bubbleShouldSetFrom === responderInst;
var shouldSetEvent = ResponderSyntheticEvent.getPooled(
shouldSetEventType,
bubbleShouldSetFrom,
nativeEvent,
nativeEventTarget
);
shouldSetEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
if (skipOverBubbleShouldSetFrom) {
EventPropagators.accumulateTwoPhaseDispatchesSkipTarget(shouldSetEvent);
} else {
EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
}
var wantsResponderInst = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
if (!shouldSetEvent.isPersistent()) {
shouldSetEvent.constructor.release(shouldSetEvent);
}
if (!wantsResponderInst || wantsResponderInst === responderInst) {
return null;
}
var extracted;
var grantEvent = ResponderSyntheticEvent.getPooled(
eventTypes.responderGrant,
wantsResponderInst,
nativeEvent,
nativeEventTarget
);
grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(grantEvent);
var blockHostResponder = executeDirectDispatch(grantEvent) === true;
if (responderInst) {
var terminationRequestEvent = ResponderSyntheticEvent.getPooled(
eventTypes.responderTerminationRequest,
responderInst,
nativeEvent,
nativeEventTarget
);
terminationRequestEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
executeDirectDispatch(terminationRequestEvent);
if (!terminationRequestEvent.isPersistent()) {
terminationRequestEvent.constructor.release(terminationRequestEvent);
}
if (shouldSwitch) {
var terminateEvent = ResponderSyntheticEvent.getPooled(
eventTypes.responderTerminate,
responderInst,
nativeEvent,
nativeEventTarget
);
terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(terminateEvent);
extracted = accumulate(extracted, [grantEvent, terminateEvent]);
changeResponder(wantsResponderInst, blockHostResponder);
} else {
var rejectEvent = ResponderSyntheticEvent.getPooled(
eventTypes.responderReject,
wantsResponderInst,
nativeEvent,
nativeEventTarget
);
rejectEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(rejectEvent);
extracted = accumulate(extracted, rejectEvent);
}
} else {
extracted = accumulate(extracted, grantEvent);
changeResponder(wantsResponderInst, blockHostResponder);
}
return extracted;
}
/**
* A transfer is a negotiation between a currently set responder and the next
* element to claim responder status. Any start event could trigger a transfer
* of responderInst. Any move event could trigger a transfer.
*
* @param {string} topLevelType Record from `EventConstants`.
* @return {boolean} True if a transfer of responder could possibly occur.
*/
function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {
return topLevelInst && (
// responderIgnoreScroll: We are trying to migrate away from specifically
// tracking native scroll events here and responderIgnoreScroll indicates we
// will send topTouchCancel to handle canceling touch events instead
(topLevelType === 'topScroll' &&
!nativeEvent.responderIgnoreScroll) ||
(trackedTouchCount > 0 &&
topLevelType === 'topSelectionChange') ||
isStartish(topLevelType) ||
isMoveish(topLevelType)
);
}
/**
* Returns whether or not this touch end event makes it such that there are no
* longer any touches that started inside of the current `responderInst`.
*
* @param {NativeEvent} nativeEvent Native touch end event.
* @return {boolean} Whether or not this touch end event ends the responder.
*/
function noResponderTouches(nativeEvent) {
var touches = nativeEvent.touches;
if (!touches || touches.length === 0) {
return true;
}
for (var i = 0; i < touches.length; i++) {
var activeTouch = touches[i];
var target = activeTouch.target;
if (target !== null && target !== undefined && target !== 0) {
// Is the original touch location inside of the current responder?
var targetInst = EventPluginUtils.getInstanceFromNode(target);
if (EventPluginUtils.isAncestor(responderInst, targetInst)) {
return false;
}
}
}
return true;
}
var ResponderEventPlugin = {
/* For unit testing only */
_getResponderID: function() {
return responderInst ? responderInst._rootNodeID : null;
},
eventTypes: eventTypes,
/**
* We must be resilient to `targetInst` being `null` on `touchMove` or
* `touchEnd`. On certain platforms, this means that a native scroll has
* assumed control and the original touch targets are destroyed.
*/
extractEvents: function(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget
) {
if (isStartish(topLevelType)) {
trackedTouchCount += 1;
} else if (isEndish(topLevelType)) {
if (trackedTouchCount >= 0) {
trackedTouchCount -= 1;
} else {
console.error(
'Ended a touch event which was not counted in `trackedTouchCount`.'
);
return null;
}
}
ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent);
var extracted = canTriggerTransfer(topLevelType, targetInst, nativeEvent) ?
setResponderAndExtractTransfer(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget) :
null;
// Responder may or may not have transferred on a new touch start/move.
// Regardless, whoever is the responder after any potential transfer, we
// direct all touch start/move/ends to them in the form of
// `onResponderMove/Start/End`. These will be called for *every* additional
// finger that move/start/end, dispatched directly to whoever is the
// current responder at that moment, until the responder is "released".
//
// These multiple individual change touch events are are always bookended
// by `onResponderGrant`, and one of
// (`onResponderRelease/onResponderTerminate`).
var isResponderTouchStart = responderInst && isStartish(topLevelType);
var isResponderTouchMove = responderInst && isMoveish(topLevelType);
var isResponderTouchEnd = responderInst && isEndish(topLevelType);
var incrementalTouch =
isResponderTouchStart ? eventTypes.responderStart :
isResponderTouchMove ? eventTypes.responderMove :
isResponderTouchEnd ? eventTypes.responderEnd :
null;
if (incrementalTouch) {
var gesture =
ResponderSyntheticEvent.getPooled(
incrementalTouch,
responderInst,
nativeEvent,
nativeEventTarget
);
gesture.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(gesture);
extracted = accumulate(extracted, gesture);
}
var isResponderTerminate =
responderInst &&
topLevelType === 'topTouchCancel';
var isResponderRelease =
responderInst &&
!isResponderTerminate &&
isEndish(topLevelType) &&
noResponderTouches(nativeEvent);
var finalTouch =
isResponderTerminate ? eventTypes.responderTerminate :
isResponderRelease ? eventTypes.responderRelease :
null;
if (finalTouch) {
var finalEvent = ResponderSyntheticEvent.getPooled(
finalTouch, responderInst, nativeEvent, nativeEventTarget
);
finalEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(finalEvent);
extracted = accumulate(extracted, finalEvent);
changeResponder(null);
}
var numberActiveTouches =
ResponderTouchHistoryStore.touchHistory.numberActiveTouches;
if (ResponderEventPlugin.GlobalInteractionHandler &&
numberActiveTouches !== previousActiveTouches) {
ResponderEventPlugin.GlobalInteractionHandler.onChange(
numberActiveTouches
);
}
previousActiveTouches = numberActiveTouches;
return extracted;
},
GlobalResponderHandler: null,
GlobalInteractionHandler: null,
injection: {
/**
* @param {{onChange: (ReactID, ReactID) => void} GlobalResponderHandler
* Object that handles any change in responder. Use this to inject
* integration with an existing touch handling system etc.
*/
injectGlobalResponderHandler: function(GlobalResponderHandler) {
ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;
},
/**
* @param {{onChange: (numberActiveTouches) => void} GlobalInteractionHandler
* Object that handles any change in the number of active touches.
*/
injectGlobalInteractionHandler: function(GlobalInteractionHandler) {
ResponderEventPlugin.GlobalInteractionHandler = GlobalInteractionHandler;
},
},
};
module.exports = ResponderEventPlugin;

Просмотреть файл

@ -0,0 +1,39 @@
/**
* 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 ResponderSyntheticEvent
*/
'use strict';
var SyntheticEvent = require('SyntheticEvent');
/**
* `touchHistory` isn't actually on the native event, but putting it in the
* interface will ensure that it is cleaned up when pooled/destroyed. The
* `ResponderEventPlugin` will populate it appropriately.
*/
var ResponderEventInterface = {
touchHistory: function(nativeEvent) {
return null; // Actually doesn't even look at the native event.
},
};
/**
* @param {object} dispatchConfig Configuration used to dispatch this event.
* @param {string} dispatchMarker Marker identifying the event target.
* @param {object} nativeEvent Native event.
* @extends {SyntheticEvent}
*/
function ResponderSyntheticEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
return SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
}
SyntheticEvent.augmentClass(ResponderSyntheticEvent, ResponderEventInterface);
module.exports = ResponderSyntheticEvent;

Просмотреть файл

@ -0,0 +1,228 @@
/**
* 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 ResponderTouchHistoryStore
* @flow
*/
'use strict';
const EventPluginUtils = require('EventPluginUtils');
const invariant = require('fbjs/lib/invariant');
const warning = require('fbjs/lib/warning');
const {
isEndish,
isMoveish,
isStartish,
} = EventPluginUtils;
/**
* Tracks the position and time of each active touch by `touch.identifier`. We
* should typically only see IDs in the range of 1-20 because IDs get recycled
* when touches end and start again.
*/
type TouchRecord = {
touchActive: boolean,
startPageX: number,
startPageY: number,
startTimeStamp: number,
currentPageX: number,
currentPageY: number,
currentTimeStamp: number,
previousPageX: number,
previousPageY: number,
previousTimeStamp: number,
};
const MAX_TOUCH_BANK = 20;
const touchBank: Array<TouchRecord> = [];
const touchHistory = {
touchBank,
numberActiveTouches: 0,
// If there is only one active touch, we remember its location. This prevents
// us having to loop through all of the touches all the time in the most
// common case.
indexOfSingleActiveTouch: -1,
mostRecentTimeStamp: 0,
};
type Touch = {
identifier: ?number,
pageX: number,
pageY: number,
timestamp: number,
};
type TouchEvent = {
changedTouches: Array<Touch>,
touches: Array<Touch>,
};
function timestampForTouch(touch: Touch): number {
// The legacy internal implementation provides "timeStamp", which has been
// renamed to "timestamp". Let both work for now while we iron it out
// TODO (evv): rename timeStamp to timestamp in internal code
return (touch: any).timeStamp || touch.timestamp;
}
/**
* TODO: Instead of making gestures recompute filtered velocity, we could
* include a built in velocity computation that can be reused globally.
*/
function createTouchRecord(touch: Touch): TouchRecord {
return {
touchActive: true,
startPageX: touch.pageX,
startPageY: touch.pageY,
startTimeStamp: timestampForTouch(touch),
currentPageX: touch.pageX,
currentPageY: touch.pageY,
currentTimeStamp: timestampForTouch(touch),
previousPageX: touch.pageX,
previousPageY: touch.pageY,
previousTimeStamp: timestampForTouch(touch),
};
}
function resetTouchRecord(touchRecord: TouchRecord, touch: Touch): void {
touchRecord.touchActive = true;
touchRecord.startPageX = touch.pageX;
touchRecord.startPageY = touch.pageY;
touchRecord.startTimeStamp = timestampForTouch(touch);
touchRecord.currentPageX = touch.pageX;
touchRecord.currentPageY = touch.pageY;
touchRecord.currentTimeStamp = timestampForTouch(touch);
touchRecord.previousPageX = touch.pageX;
touchRecord.previousPageY = touch.pageY;
touchRecord.previousTimeStamp = timestampForTouch(touch);
}
function getTouchIdentifier({identifier}: Touch): number {
invariant(identifier != null, 'Touch object is missing identifier.');
warning(
identifier <= MAX_TOUCH_BANK,
'Touch identifier %s is greater than maximum supported %s which causes ' +
'performance issues backfilling array locations for all of the indices.',
identifier,
MAX_TOUCH_BANK
);
return identifier;
}
function recordTouchStart(touch: Touch): void {
const identifier = getTouchIdentifier(touch);
const touchRecord = touchBank[identifier];
if (touchRecord) {
resetTouchRecord(touchRecord, touch);
} else {
touchBank[identifier] = createTouchRecord(touch);
}
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
}
function recordTouchMove(touch: Touch): void {
const touchRecord = touchBank[getTouchIdentifier(touch)];
if (touchRecord) {
touchRecord.touchActive = true;
touchRecord.previousPageX = touchRecord.currentPageX;
touchRecord.previousPageY = touchRecord.currentPageY;
touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
touchRecord.currentPageX = touch.pageX;
touchRecord.currentPageY = touch.pageY;
touchRecord.currentTimeStamp = timestampForTouch(touch);
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
} else {
console.error(
'Cannot record touch move without a touch start.\n' +
'Touch Move: %s\n',
'Touch Bank: %s',
printTouch(touch),
printTouchBank()
);
}
}
function recordTouchEnd(touch: Touch): void {
const touchRecord = touchBank[getTouchIdentifier(touch)];
if (touchRecord) {
touchRecord.touchActive = false;
touchRecord.previousPageX = touchRecord.currentPageX;
touchRecord.previousPageY = touchRecord.currentPageY;
touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
touchRecord.currentPageX = touch.pageX;
touchRecord.currentPageY = touch.pageY;
touchRecord.currentTimeStamp = timestampForTouch(touch);
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
} else {
console.error(
'Cannot record touch end without a touch start.\n' +
'Touch End: %s\n',
'Touch Bank: %s',
printTouch(touch),
printTouchBank()
);
}
}
function printTouch(touch: Touch): string {
return JSON.stringify({
identifier: touch.identifier,
pageX: touch.pageX,
pageY: touch.pageY,
timestamp: timestampForTouch(touch),
});
}
function printTouchBank(): string {
let printed = JSON.stringify(touchBank.slice(0, MAX_TOUCH_BANK));
if (touchBank.length > MAX_TOUCH_BANK) {
printed += ' (original size: ' + touchBank.length + ')';
}
return printed;
}
const ResponderTouchHistoryStore = {
recordTouchTrack(topLevelType: string, nativeEvent: TouchEvent): void {
if (isMoveish(topLevelType)) {
nativeEvent.changedTouches.forEach(recordTouchMove);
} else if (isStartish(topLevelType)) {
nativeEvent.changedTouches.forEach(recordTouchStart);
touchHistory.numberActiveTouches = nativeEvent.touches.length;
if (touchHistory.numberActiveTouches === 1) {
touchHistory.indexOfSingleActiveTouch =
nativeEvent.touches[0].identifier;
}
} else if (isEndish(topLevelType)) {
nativeEvent.changedTouches.forEach(recordTouchEnd);
touchHistory.numberActiveTouches = nativeEvent.touches.length;
if (touchHistory.numberActiveTouches === 1) {
for (let i = 0; i < touchBank.length; i++) {
const touchTrackToCheck = touchBank[i];
if (touchTrackToCheck != null && touchTrackToCheck.touchActive) {
touchHistory.indexOfSingleActiveTouch = i;
break;
}
}
if (__DEV__) {
const activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch];
warning(
activeRecord != null &&
activeRecord.touchActive,
'Cannot find single active touch.'
);
}
}
}
},
touchHistory,
};
module.exports = ResponderTouchHistoryStore;

Просмотреть файл

@ -0,0 +1,122 @@
/**
* @providesModule TouchHistoryMath
*/
'use strict';
var TouchHistoryMath = {
/**
* This code is optimized and not intended to look beautiful. This allows
* computing of touch centroids that have moved after `touchesChangedAfter`
* timeStamp. You can compute the current centroid involving all touches
* moves after `touchesChangedAfter`, or you can compute the previous
* centroid of all touches that were moved after `touchesChangedAfter`.
*
* @param {TouchHistoryMath} touchHistory Standard Responder touch track
* data.
* @param {number} touchesChangedAfter timeStamp after which moved touches
* are considered "actively moving" - not just "active".
* @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension.
* @param {boolean} ofCurrent Compute current centroid for actively moving
* touches vs. previous centroid of now actively moving touches.
* @return {number} value of centroid in specified dimension.
*/
centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
var touchBank = touchHistory.touchBank;
var total = 0;
var count = 0;
var oneTouchData = touchHistory.numberActiveTouches === 1 ?
touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null;
if (oneTouchData !== null) {
if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) {
total += ofCurrent && isXAxis ? oneTouchData.currentPageX :
ofCurrent && !isXAxis ? oneTouchData.currentPageY :
!ofCurrent && isXAxis ? oneTouchData.previousPageX :
oneTouchData.previousPageY;
count = 1;
}
} else {
for (var i = 0; i < touchBank.length; i++) {
var touchTrack = touchBank[i];
if (touchTrack !== null &&
touchTrack !== undefined &&
touchTrack.touchActive &&
touchTrack.currentTimeStamp >= touchesChangedAfter) {
var toAdd; // Yuck, program temporarily in invalid state.
if (ofCurrent && isXAxis) {
toAdd = touchTrack.currentPageX;
} else if (ofCurrent && !isXAxis) {
toAdd = touchTrack.currentPageY;
} else if (!ofCurrent && isXAxis) {
toAdd = touchTrack.previousPageX;
} else {
toAdd = touchTrack.previousPageY;
}
total += toAdd;
count++;
}
}
}
return count > 0 ? total / count : TouchHistoryMath.noCentroid;
},
currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
true, // isXAxis
true // ofCurrent
);
},
currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
false, // isXAxis
true // ofCurrent
);
},
previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
true, // isXAxis
false // ofCurrent
);
},
previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
return TouchHistoryMath.centroidDimension(
touchHistory,
touchesChangedAfter,
false, // isXAxis
false // ofCurrent
);
},
currentCentroidX: function(touchHistory) {
return TouchHistoryMath.centroidDimension(
touchHistory,
0, // touchesChangedAfter
true, // isXAxis
true // ofCurrent
);
},
currentCentroidY: function(touchHistory) {
return TouchHistoryMath.centroidDimension(
touchHistory,
0, // touchesChangedAfter
false, // isXAxis
true // ofCurrent
);
},
noCentroid: -1,
};
module.exports = TouchHistoryMath;

Просмотреть файл

@ -0,0 +1,196 @@
/**
* 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 ReactChildReconciler
*/
'use strict';
var ReactReconciler = require('ReactReconciler');
var instantiateReactComponent = require('instantiateReactComponent');
var KeyEscapeUtils = require('KeyEscapeUtils');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var traverseAllChildren = require('traverseAllChildren');
var warning = require('fbjs/lib/warning');
var ReactComponentTreeHook;
if (
typeof process !== 'undefined' &&
process.env &&
process.env.NODE_ENV === 'test'
) {
// Temporary hack.
// Inline requires don't work well with Jest:
// https://github.com/facebook/react/issues/7240
// Remove the inline requires when we don't need them anymore:
// https://github.com/facebook/react/pull/7178
ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook');
}
function instantiateChild(childInstances, child, name, selfDebugID) {
// We found a component instance.
var keyUnique = (childInstances[name] === undefined);
if (__DEV__) {
if (!ReactComponentTreeHook) {
ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook');
}
if (!keyUnique) {
warning(
false,
'flattenChildren(...): Encountered two children with the same key, ' +
'`%s`. Child keys must be unique; when two children share a key, only ' +
'the first child will be used.%s',
KeyEscapeUtils.unescape(name),
ReactComponentTreeHook.getStackAddendumByID(selfDebugID)
);
}
}
if (child != null && keyUnique) {
childInstances[name] = instantiateReactComponent(child, true);
}
}
/**
* ReactChildReconciler provides helpers for initializing or updating a set of
* children. Its output is suitable for passing it onto ReactMultiChild which
* does diffed reordering and insertion.
*/
var ReactChildReconciler = {
/**
* Generates a "mount image" for each of the supplied children. In the case
* of `ReactDOMComponent`, a mount image is a string of markup.
*
* @param {?object} nestedChildNodes Nested child maps.
* @return {?object} A set of child instances.
* @internal
*/
instantiateChildren: function(
nestedChildNodes,
transaction,
context,
selfDebugID // 0 in production and for roots
) {
if (nestedChildNodes == null) {
return null;
}
var childInstances = {};
if (__DEV__) {
traverseAllChildren(
nestedChildNodes,
(childInsts, child, name) => instantiateChild(
childInsts,
child,
name,
selfDebugID
),
childInstances
);
} else {
traverseAllChildren(nestedChildNodes, instantiateChild, childInstances);
}
return childInstances;
},
/**
* Updates the rendered children and returns a new set of children.
*
* @param {?object} prevChildren Previously initialized set of children.
* @param {?object} nextChildren Flat child element maps.
* @param {ReactReconcileTransaction} transaction
* @param {object} context
* @return {?object} A new set of child instances.
* @internal
*/
updateChildren: function(
prevChildren,
nextChildren,
mountImages,
removedNodes,
transaction,
hostParent,
hostContainerInfo,
context,
selfDebugID // 0 in production and for roots
) {
// We currently don't have a way to track moves here but if we use iterators
// instead of for..in we can zip the iterators and check if an item has
// moved.
// TODO: If nothing has changed, return the prevChildren object so that we
// can quickly bailout if nothing has changed.
if (!nextChildren && !prevChildren) {
return;
}
var name;
var prevChild;
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue;
}
prevChild = prevChildren && prevChildren[name];
var prevElement = prevChild && prevChild._currentElement;
var nextElement = nextChildren[name];
if (prevChild != null &&
shouldUpdateReactComponent(prevElement, nextElement)) {
ReactReconciler.receiveComponent(
prevChild, nextElement, transaction, context
);
nextChildren[name] = prevChild;
} else {
if (prevChild) {
removedNodes[name] = ReactReconciler.getHostNode(prevChild);
ReactReconciler.unmountComponent(prevChild, false);
}
// The child must be instantiated before it's mounted.
var nextChildInstance = instantiateReactComponent(nextElement, true);
nextChildren[name] = nextChildInstance;
// Creating mount image now ensures refs are resolved in right order
// (see https://github.com/facebook/react/pull/7101 for explanation).
var nextChildMountImage = ReactReconciler.mountComponent(
nextChildInstance,
transaction,
hostParent,
hostContainerInfo,
context,
selfDebugID
);
mountImages.push(nextChildMountImage);
}
}
// Unmount children that are no longer present.
for (name in prevChildren) {
if (prevChildren.hasOwnProperty(name) &&
!(nextChildren && nextChildren.hasOwnProperty(name))) {
prevChild = prevChildren[name];
removedNodes[name] = ReactReconciler.getHostNode(prevChild);
ReactReconciler.unmountComponent(prevChild, false);
}
}
},
/**
* Unmounts all rendered children. This should be used to clean up children
* when this component is unmounted.
*
* @param {?object} renderedChildren Previously initialized set of children.
* @internal
*/
unmountChildren: function(renderedChildren, safely) {
for (var name in renderedChildren) {
if (renderedChildren.hasOwnProperty(name)) {
var renderedChild = renderedChildren[name];
ReactReconciler.unmountComponent(renderedChild, safely);
}
}
},
};
module.exports = ReactChildReconciler;

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше