From a2f73b4d7725bcb05b74ffadc2516fb42c16f1fd Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Tue, 12 May 2015 18:23:33 -0700 Subject: [PATCH] [react-native] Add React.findNodeHandle --- Libraries/Components/ScrollView/ScrollView.js | 8 +- Libraries/ReactNative/ReactNative.js | 2 + .../ReactNativeBaseComponentMixin.js | 53 +-------- Libraries/ReactNative/findNodeHandle.js | 112 ++++++++++++++++++ Libraries/Text/Text.js | 1 - 5 files changed, 121 insertions(+), 55 deletions(-) create mode 100644 Libraries/ReactNative/findNodeHandle.js diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 368b4a6a0..3a87d05a8 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -207,19 +207,19 @@ var ScrollView = React.createClass({ }, getInnerViewNode: function(): any { - return this.refs[INNERVIEW].getNodeHandle(); + return React.findNodeHandle(this.refs[INNERVIEW]); }, scrollTo: function(destY?: number, destX?: number) { if (Platform.OS === 'android') { RCTUIManager.dispatchViewManagerCommand( - this.getNodeHandle(), + React.findNodeHandle(this), RCTUIManager.RCTScrollView.Commands.scrollTo, [destX || 0, destY || 0] ); } else { RCTUIManager.scrollTo( - this.getNodeHandle(), + React.findNodeHandle(this), destX || 0, destY || 0 ); @@ -228,7 +228,7 @@ var ScrollView = React.createClass({ scrollWithoutAnimationTo: function(destY?: number, destX?: number) { RCTUIManager.scrollWithoutAnimationTo( - this.getNodeHandle(), + React.findNodeHandle(this), destX || 0, destY || 0 ); diff --git a/Libraries/ReactNative/ReactNative.js b/Libraries/ReactNative/ReactNative.js index 9ec36fdfa..abe31a361 100644 --- a/Libraries/ReactNative/ReactNative.js +++ b/Libraries/ReactNative/ReactNative.js @@ -24,6 +24,7 @@ var ReactNativeMount = require('ReactNativeMount'); var ReactPropTypes = require('ReactPropTypes'); var deprecated = require('deprecated'); +var findNodeHandle = require('findNodeHandle'); var invariant = require('invariant'); var onlyChild = require('onlyChild'); @@ -90,6 +91,7 @@ var ReactNative = { createFactory: createFactory, cloneElement: cloneElement, _augmentElement: augmentElement, + findNodeHandle: findNodeHandle, render: render, unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode, diff --git a/Libraries/ReactNative/ReactNativeBaseComponentMixin.js b/Libraries/ReactNative/ReactNativeBaseComponentMixin.js index 5e17db9fa..6f10ce7c2 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponentMixin.js +++ b/Libraries/ReactNative/ReactNativeBaseComponentMixin.js @@ -11,49 +11,8 @@ */ 'use strict'; -var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var ReactInstanceMap = require('ReactInstanceMap'); +var findNodeHandle = require('findNodeHandle'); -/** - * 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 - * - * - * `mountImage`: A way to represent the potential to create lower level - * resources whos `nodeHandle` can be discovered immediately by knowing the - * `rootNodeID`. Today's web React represents this with `innerHTML` annotated - * with DOM ids that match the `rootNodeID`. - * - * Opaque name TodaysWebReact FutureWebWorkerReact ReactNative - * ---------------------------------------------------------------------------- - * mountImage innerHTML innerHTML {rootNodeID, tag} - * - */ var ReactNativeComponentMixin = { /** * This has no particular meaning in ReactNative. If this were in the DOM, this @@ -62,17 +21,11 @@ var ReactNativeComponentMixin = { * `getNodeHandle`. */ getNativeNode: function() { - // TODO (balpert): Wrap iOS native components in a composite wrapper, then - // ReactInstanceMap.get here will always succeed - return ReactNativeTagHandles.rootNodeIDToTag[ - (ReactInstanceMap.get(this) || this)._rootNodeID - ]; + return findNodeHandle(this); }, getNodeHandle: function() { - return ReactNativeTagHandles.rootNodeIDToTag[ - (ReactInstanceMap.get(this) || this)._rootNodeID - ]; + return findNodeHandle(this); } }; diff --git a/Libraries/ReactNative/findNodeHandle.js b/Libraries/ReactNative/findNodeHandle.js new file mode 100644 index 000000000..37c772760 --- /dev/null +++ b/Libraries/ReactNative/findNodeHandle.js @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule findNodeHandle + * @flow + */ + +'use strict'; + +var ReactCurrentOwner = require('ReactCurrentOwner'); +var ReactInstanceMap = require('ReactInstanceMap'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); + +var invariant = require('invariant'); +var warning = require('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 ReactNativeTagHandles.rootNodeIDToTag[internalInstance._rootNodeID]; + } else { + var rootNodeID = component._rootNodeID; + if (rootNodeID) { + return ReactNativeTagHandles.rootNodeIDToTag[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; diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 2ea93263f..dee0d8543 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -176,7 +176,6 @@ var Text = React.createClass({ for (var key in this.props) { props[key] = this.props[key]; } - props.ref = this.getNodeHandle(); // Text is accessible by default if (props.accessible !== false) { props.accessible = true;