[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 ## Notes
The components used in the render benchmarks are simple enough to be The components used in the render benchmarks are simple enough to be
implemented by multiple styling libraries. The implementations are not implemented by multiple UI or style libraries. The implementations are not
equivalent but are useful for framing the relative performance of equivalent in functionality.
`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.
## Benchmark results ## Benchmark results
Typical render timings*: mean / two standard deviations Typical render timings*: mean ± two standard deviations
| Implementation | Deep tree (ms) | Wide tree (ms) | | Implementation | Deep tree (ms) | Wide tree (ms) |
| :--- | ---: | ---: | | :--- | ---: | ---: |
| css-modules | `75.40` `±15.93` | `162.15` 22.20` | | `css-modules` | `76.66` `±18.46` | `157.03` 19.79` |
| react-native-web/lite@0.0.77 | `83.93` 13.80` | `177.57` `±20.045` | | `react-native-web@0.0.78` | `90.13` 20.91` | `198.72` `±24.44` |
| react-native-web@0.0.77 | `106.72` 15.48` | `217.63` `±25.70` | | `styled-components@2.0.0-7` | `263.06` 31.87` | `564.53` `±27.62` |
| styled-components@2.0.0-7 | `255.19` `±35.09` | `569.74` 59.94` | | `glamor@3.0.0-1` | `267.49` `±35.12` | `451.99` 37.32` |
| glamor@3.0.0-1 | `268.94` `±38.96` | `458.69` `±32.30` |
*MacBook Pro (13-inch, Early 2011); 2.7 GHz Intel Core i7; 16 GB 1600 MHz DDR3. Google Chrome 56. *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 cssModules from './implementations/css-modules';
import glamor from './implementations/glamor'; import glamor from './implementations/glamor';
import reactNative from './implementations/react-native-web'; import reactNative from './implementations/react-native-web';
import reactNativeLite from './implementations/react-native-web/lite';
import styledComponents from './implementations/styled-components'; import styledComponents from './implementations/styled-components';
import renderDeepTree from './tests/renderDeepTree'; import renderDeepTree from './tests/renderDeepTree';
@@ -10,13 +9,11 @@ import renderWideTree from './tests/renderWideTree';
const tests = [ const tests = [
// deep tree // deep tree
() => renderDeepTree('css-modules', cssModules), () => renderDeepTree('css-modules', cssModules),
() => renderDeepTree('react-native-web/lite', reactNativeLite),
() => renderDeepTree('react-native-web', reactNative), () => renderDeepTree('react-native-web', reactNative),
() => renderDeepTree('styled-components', styledComponents), () => renderDeepTree('styled-components', styledComponents),
() => renderDeepTree('glamor', glamor), () => renderDeepTree('glamor', glamor),
// wide tree // wide tree
() => renderWideTree('css-modules', cssModules), () => renderWideTree('css-modules', cssModules),
() => renderWideTree('react-native-web/lite', reactNativeLite),
() => renderWideTree('react-native-web', reactNative), () => renderWideTree('react-native-web', reactNative),
() => renderWideTree('styled-components', styledComponents), () => renderWideTree('styled-components', styledComponents),
() => renderWideTree('glamor', glamor) () => renderWideTree('glamor', glamor)
+4 -45
View File
@@ -1,49 +1,11 @@
import '../../modules/injectResponderEventPlugin';
import applyLayout from '../../modules/applyLayout'; import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods'; import applyNativeMethods from '../../modules/applyNativeMethods';
import createDOMElement from '../../modules/createDOMElement'; import createDOMElement from '../../modules/createDOMElement';
import normalizeNativeEvent from '../../modules/normalizeNativeEvent';
import StyleSheet from '../../apis/StyleSheet'; import StyleSheet from '../../apis/StyleSheet';
import ViewPropTypes from './ViewPropTypes'; import ViewPropTypes from './ViewPropTypes';
import { Component, PropTypes } from 'react'; import { Component, PropTypes } from 'react';
const eventHandlerNames = [ const emptyObject = {};
'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);
}
});
};
class View extends Component { class View extends Component {
static displayName = 'View'; static displayName = 'View';
@@ -63,9 +25,9 @@ class View extends Component {
}; };
getChildContext() { getChildContext() {
return { const isInAButtonView = this.props.accessibilityRole === 'button' ||
isInAButtonView: this.props.accessibilityRole === 'button' this.context.isInAButtonView;
}; return isInAButtonView ? { isInAButtonView } : emptyObject;
} }
render() { render() {
@@ -87,9 +49,6 @@ class View extends Component {
const component = this.context.isInAButtonView ? 'span' : 'div'; 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]]; otherProps.style = [styles.initial, style, pointerEvents && pointerEventStyles[pointerEvents]];
return createDOMElement(component, otherProps); return createDOMElement(component, otherProps);
+45 -1
View File
@@ -1,3 +1,6 @@
import '../injectResponderEventPlugin';
import normalizeNativeEvent from '../normalizeNativeEvent';
import React from 'react'; import React from 'react';
import StyleRegistry from '../../apis/StyleSheet/registry'; import StyleRegistry from '../../apis/StyleSheet/registry';
@@ -19,23 +22,64 @@ const roleComponents = {
region: 'section' 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 createDOMElement = (component, rnProps) => {
const { const {
accessibilityLabel, accessibilityLabel,
accessibilityLiveRegion, accessibilityLiveRegion,
accessibilityRole, accessibilityRole,
accessible = true, accessible = true,
style: rnStyle, // we need to remove the RN styles from 'domProps' style: rnStyle,
testID, testID,
type, type,
...domProps ...domProps
} = rnProps || emptyObject; } = rnProps || emptyObject;
// use equivalent platform elements where possible
const accessibilityComponent = accessibilityRole && roleComponents[accessibilityRole]; const accessibilityComponent = accessibilityRole && roleComponents[accessibilityRole];
const Component = accessibilityComponent || component; const Component = accessibilityComponent || component;
// convert React Native styles to DOM styles
const { className, style } = StyleRegistry.resolve(rnStyle) || emptyObject; 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) { if (!accessible) {
domProps['aria-hidden'] = true; domProps['aria-hidden'] = true;
} }