[change] move bridge code into createDOMElement

Moves event normalization and the ResponderEventPlugin injection from
'View' to 'createDOMElement'.

The 'react-native-web/lite' variant is removed from the performance
directory as the implementation is not substantially different.
Micro-optimizations to marginally narrow the performance gap to
css-modules.
This commit is contained in:
Nicolas Gallagher
2017-03-18 17:27:53 -07:00
parent 808790505e
commit 976320916e
7 changed files with 56 additions and 151 deletions
+7 -14
View File
@@ -10,25 +10,18 @@ open ./performance/index.html
## Notes
The components used in the render benchmarks are simple enough to be
implemented by multiple styling libraries. The implementations are not
equivalent but are useful for framing the relative performance of
`react-native-web` against these tests.
The implementations are not equivalent. For example, the `react-native-web`
implementation of `View` does more than just styling. The
`react-native-web/lite` variant implements a minimal `View` that allows for a
more direct comparison with the `css-modules` baseline.
implemented by multiple UI or style libraries. The implementations are not
equivalent in functionality.
## Benchmark results
Typical render timings*: mean / two standard deviations
Typical render timings*: mean ± two standard deviations
| Implementation | Deep tree (ms) | Wide tree (ms) |
| :--- | ---: | ---: |
| css-modules | `75.40` `±15.93` | `162.15` 22.20` |
| react-native-web/lite@0.0.77 | `83.93` 13.80` | `177.57` `±20.045` |
| react-native-web@0.0.77 | `106.72` 15.48` | `217.63` `±25.70` |
| styled-components@2.0.0-7 | `255.19` `±35.09` | `569.74` 59.94` |
| glamor@3.0.0-1 | `268.94` `±38.96` | `458.69` `±32.30` |
| `css-modules` | `76.66` `±18.46` | `157.03` 19.79` |
| `react-native-web@0.0.78` | `90.13` 20.91` | `198.72` `±24.44` |
| `styled-components@2.0.0-7` | `263.06` 31.87` | `564.53` `±27.62` |
| `glamor@3.0.0-1` | `267.49` `±35.12` | `451.99` 37.32` |
*MacBook Pro (13-inch, Early 2011); 2.7 GHz Intel Core i7; 16 GB 1600 MHz DDR3. Google Chrome 56.
@@ -1,49 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
import StyleSheet from 'react-native/apis/StyleSheet';
import View from '../View/lite';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View
{...other}
style={[
styles[`color${color}`],
fixed && styles.fixed,
layout === 'row' && styles.row,
outer && styles.outer
]}
/>
);
const styles = StyleSheet.create({
outer: {
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#222'
},
color1: {
backgroundColor: '#666'
},
color2: {
backgroundColor: '#999'
},
color3: {
backgroundColor: 'blue'
},
color4: {
backgroundColor: 'orange'
},
color5: {
backgroundColor: 'red'
},
fixed: {
width: 20,
height: 20
}
});
module.exports = Box;
@@ -1,32 +0,0 @@
import createDOMElement from 'react-native/modules/createDOMElement';
import StyleSheet from 'react-native/apis/StyleSheet';
const View = props => createDOMElement('div', { ...props, style: [styles.initial, props.style] });
const styles = StyleSheet.create({
initial: {
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
margin: 0,
padding: 0,
position: 'relative',
// button and anchor reset
backgroundColor: 'transparent',
color: 'inherit',
font: 'inherit',
textAlign: 'inherit',
textDecorationLine: 'none',
// list reset
listStyle: 'none',
// fix flexbox bugs
minHeight: 0,
minWidth: 0
}
});
module.exports = View;
-7
View File
@@ -1,7 +0,0 @@
import Box from './Box/lite';
import View from './View/lite';
export default {
Box,
View
};
-3
View File
@@ -1,7 +1,6 @@
import cssModules from './implementations/css-modules';
import glamor from './implementations/glamor';
import reactNative from './implementations/react-native-web';
import reactNativeLite from './implementations/react-native-web/lite';
import styledComponents from './implementations/styled-components';
import renderDeepTree from './tests/renderDeepTree';
@@ -10,13 +9,11 @@ import renderWideTree from './tests/renderWideTree';
const tests = [
// deep tree
() => renderDeepTree('css-modules', cssModules),
() => renderDeepTree('react-native-web/lite', reactNativeLite),
() => renderDeepTree('react-native-web', reactNative),
() => renderDeepTree('styled-components', styledComponents),
() => renderDeepTree('glamor', glamor),
// wide tree
() => renderWideTree('css-modules', cssModules),
() => renderWideTree('react-native-web/lite', reactNativeLite),
() => renderWideTree('react-native-web', reactNative),
() => renderWideTree('styled-components', styledComponents),
() => renderWideTree('glamor', glamor)
+4 -45
View File
@@ -1,49 +1,11 @@
import '../../modules/injectResponderEventPlugin';
import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import createDOMElement from '../../modules/createDOMElement';
import normalizeNativeEvent from '../../modules/normalizeNativeEvent';
import StyleSheet from '../../apis/StyleSheet';
import ViewPropTypes from './ViewPropTypes';
import { Component, PropTypes } from 'react';
const eventHandlerNames = [
'onClick',
'onClickCapture',
'onMoveShouldSetResponder',
'onMoveShouldSetResponderCapture',
'onResponderGrant',
'onResponderMove',
'onResponderReject',
'onResponderRelease',
'onResponderTerminate',
'onResponderTerminationRequest',
'onStartShouldSetResponder',
'onStartShouldSetResponderCapture',
'onTouchCancel',
'onTouchCancelCapture',
'onTouchEnd',
'onTouchEndCapture',
'onTouchMove',
'onTouchMoveCapture',
'onTouchStart',
'onTouchStartCapture'
];
const _normalizeEventForHandler = handler => e => {
e.nativeEvent = normalizeNativeEvent(e.nativeEvent);
return handler(e);
};
const normalizeEventHandlers = props => {
eventHandlerNames.forEach(handlerName => {
const handler = props[handlerName];
if (typeof handler === 'function') {
props[handlerName] = _normalizeEventForHandler(handler);
}
});
};
const emptyObject = {};
class View extends Component {
static displayName = 'View';
@@ -63,9 +25,9 @@ class View extends Component {
};
getChildContext() {
return {
isInAButtonView: this.props.accessibilityRole === 'button'
};
const isInAButtonView = this.props.accessibilityRole === 'button' ||
this.context.isInAButtonView;
return isInAButtonView ? { isInAButtonView } : emptyObject;
}
render() {
@@ -87,9 +49,6 @@ class View extends Component {
const component = this.context.isInAButtonView ? 'span' : 'div';
// DOM events need to be normalized to expect RN format
normalizeEventHandlers(otherProps);
otherProps.style = [styles.initial, style, pointerEvents && pointerEventStyles[pointerEvents]];
return createDOMElement(component, otherProps);
+45 -1
View File
@@ -1,3 +1,6 @@
import '../injectResponderEventPlugin';
import normalizeNativeEvent from '../normalizeNativeEvent';
import React from 'react';
import StyleRegistry from '../../apis/StyleSheet/registry';
@@ -19,23 +22,64 @@ const roleComponents = {
region: 'section'
};
const eventHandlerNames = {
onClick: true,
onClickCapture: true,
onMoveShouldSetResponder: true,
onMoveShouldSetResponderCapture: true,
onResponderGrant: true,
onResponderMove: true,
onResponderReject: true,
onResponderRelease: true,
onResponderTerminate: true,
onResponderTerminationRequest: true,
onStartShouldSetResponder: true,
onStartShouldSetResponderCapture: true,
onTouchCancel: true,
onTouchCancelCapture: true,
onTouchEnd: true,
onTouchEndCapture: true,
onTouchMove: true,
onTouchMoveCapture: true,
onTouchStart: true,
onTouchStartCapture: true
};
const wrapEventHandler = handler => e => {
e.nativeEvent = normalizeNativeEvent(e.nativeEvent);
return handler(e);
};
const createDOMElement = (component, rnProps) => {
const {
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRole,
accessible = true,
style: rnStyle, // we need to remove the RN styles from 'domProps'
style: rnStyle,
testID,
type,
...domProps
} = rnProps || emptyObject;
// use equivalent platform elements where possible
const accessibilityComponent = accessibilityRole && roleComponents[accessibilityRole];
const Component = accessibilityComponent || component;
// convert React Native styles to DOM styles
const { className, style } = StyleRegistry.resolve(rnStyle) || emptyObject;
// normalize DOM events to match React Native events
// TODO: move this out of the render path
for (const prop in domProps) {
if (Object.prototype.hasOwnProperty.call(domProps, prop)) {
const isEventHandler = typeof prop === 'function' && eventHandlerNames[prop];
if (isEventHandler) {
domProps[prop] = wrapEventHandler(prop);
}
}
}
if (!accessible) {
domProps['aria-hidden'] = true;
}