[ReactNative] Element Inspector

Summary:
This adds new development feature to React Native that provides information
about selected element (see the demo in Test Plan).

This is how it works:

App's root component is rendered to a container that also has a hidden layer called
`<InspectorOverlay/>`. When activated, it shows full screen view and captures all
touches. On every touch we ask UIManager to find a view for given {x,y} coordinates.

Then, we use React's internals to find corresponding React component. `setRootInstance`
is used to remember the top level component to start search from, lmk if you have a
better idea how to do this. Given a component, we can climb up its owners tree
to provice more context on how/where the component is used.

In future we could use the `hierarchy` array to inspect and print their props/state.

Known bugs and limitations:
* InspectorOverlay sometimes receives touches with incorrect coordinates (wtf)
* Not integrated with React Chrome Devtools (maybe in followup diffs)
* Doesn't work with popovers (maybe put the element inspector into an `<Overlay/>`?)

@public

Test Plan:
https://www.facebook.com/pxlcld/mn5k
Works nicely with scrollviews
This commit is contained in:
Alex Kotliarskyi
2015-05-26 11:16:25 -07:00
parent 4273af9e29
commit cfa4b13472
5 changed files with 252 additions and 7 deletions

View File

@@ -7,17 +7,59 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule renderApplication
* @flow
*/
'use strict';
var InspectorOverlay = require('InspectorOverlay');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var React = require('React');
var StyleSheet = require('StyleSheet');
var Subscribable = require('Subscribable');
var View = require('View');
var WarningBox = require('WarningBox');
var invariant = require('invariant');
var AppContainer = React.createClass({
mixins: [Subscribable.Mixin],
getInitialState: function() {
return { inspector: null };
},
toggleElementInspector: function() {
var inspector = this.state.inspector
? null
: <InspectorOverlay
rootTag={this.props.rootTag}
inspectedViewTag={React.findNodeHandle(this.refs.main)}
/>;
this.setState({inspector});
},
componentDidMount: function() {
this.addListenerOn(
RCTDeviceEventEmitter,
'toggleElementInspector',
this.toggleElementInspector
);
},
render: function() {
var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled;
var warningBox = shouldRenderWarningBox ? <WarningBox /> : null;
return (
<View style={styles.appContainer}>
<View style={styles.appContainer} ref="main">
{this.props.children}
</View>
{warningBox}
{this.state.inspector}
</View>
);
}
});
function renderApplication<D, P, S>(
RootComponent: ReactClass<D, P, S>,
initialProps: P,
@@ -27,15 +69,12 @@ function renderApplication<D, P, S>(
rootTag,
'Expect to have a valid rootTag, instead got ', rootTag
);
var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled;
var warningBox = shouldRenderWarningBox ? <WarningBox /> : null;
React.render(
<View style={styles.appContainer}>
<AppContainer rootTag={rootTag}>
<RootComponent
{...initialProps}
/>
{warningBox}
</View>,
</AppContainer>,
rootTag
);
}