mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-23 06:48:35 +00:00
[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:
+7
-14
@@ -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;
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './Box/lite';
|
||||
import View from './View/lite';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user