Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make createClass 10-15% faster on complex specs #5550

Merged
merged 2 commits into from
Dec 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 243 additions & 0 deletions scripts/bench/bench-createclass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
React.createClass({
displayName: "UnimplementedView",
setNativeProps: function() {},
render: function() {},
});
var mixin1 = {
measure: function() {},
measureLayout: function() {},
setNativeProps: function() {},
focus: function() {},
blur: function() {},
componentWillMount: function() {},
componentWillReceiveProps: function() {},
};
React.createClass({
displayName: "View",
mixins: [mixin1],
viewConfig: {uiViewClassName: "RCTView", validAttributes: null},
statics: {AccessibilityTraits: null, AccessibilityComponentType: null},
propTypes: {accessible: function() {}, accessibilityLabel: function() {}, accessibilityComponentType: function() {}, accessibilityLiveRegion: function() {}, importantForAccessibility: function() {}, accessibilityTraits: function() {}, onAccessibilityTap: function() {}, onMagicTap: function() {}, testID: function() {}, onResponderGrant: function() {}, onResponderMove: function() {}, onResponderReject: function() {}, onResponderRelease: function() {}, onResponderTerminate: function() {}, onResponderTerminationRequest: function() {}, onStartShouldSetResponder: function() {}, onStartShouldSetResponderCapture: function() {}, onMoveShouldSetResponder: function() {}, onMoveShouldSetResponderCapture: function() {}, onLayout: function() {}, pointerEvents: function() {}, style: function() {}, removeClippedSubviews: function() {}, renderToHardwareTextureAndroid: function() {}, shouldRasterizeIOS: function() {}, collapsable: function() {}, needsOffscreenAlphaCompositing: function() {}},
render: function() {},
});
var mixin2 = {
componentWillUnmount: function() {},
setTimeout: function() {},
clearTimeout: function() {},
setInterval: function() {},
clearInterval: function() {},
setImmediate: function() {},
clearImmediate: function() {},
requestAnimationFrame: function() {},
cancelAnimationFrame: function() {},
};
var mixin3 = {
componentWillUnmount: function() {},
_interactionMixinHandles: {},
createInteractionHandle: function() {},
clearInteractionHandle: function() {},
runAfterInteractions: function() {},
};
var mixin4 = {
componentWillMount: function() {},
componentWillUnmount: function() {},
addListenerOn: function() {},
};
React.createClass({
displayName: "Navigator",
propTypes: {configureScene: function() {}, renderScene: function() {}, initialRoute: function() {}, initialRouteStack: function() {}, onWillFocus: function() {}, onDidFocus: function() {}, navigationBar: function() {}, navigator: function() {}, sceneStyle: function() {}},
statics: {BreadcrumbNavigationBar: null, NavigationBar: null, SceneConfigs: null},
mixins: [mixin2, mixin3, mixin4],
getDefaultProps: function() {},
getInitialState: function() {},
componentWillMount: function() {},
componentDidMount: function() {},
componentWillUnmount: function() {},
immediatelyResetRouteStack: function() {},
_transitionTo: function() {},
_handleSpringUpdate: function() {},
_completeTransition: function() {},
_emitDidFocus: function() {},
_emitWillFocus: function() {},
_hideScenes: function() {},
_disableScene: function() {},
_enableScene: function() {},
_onAnimationStart: function() {},
_onAnimationEnd: function() {},
_setRenderSceneToHardwareTextureAndroid: function() {},
_handleTouchStart: function() {},
_handleMoveShouldSetPanResponder: function() {},
_doesGestureOverswipe: function() {},
_deltaForGestureAction: function() {},
_handlePanResponderRelease: function() {},
_handlePanResponderTerminate: function() {},
_attachGesture: function() {},
_detachGesture: function() {},
_handlePanResponderMove: function() {},
_moveAttachedGesture: function() {},
_matchGestureAction: function() {},
_transitionSceneStyle: function() {},
_transitionBetween: function() {},
_handleResponderTerminationRequest: function() {},
_getDestIndexWithinBounds: function() {},
_jumpN: function() {},
jumpTo: function() {},
jumpForward: function() {},
jumpBack: function() {},
push: function() {},
_popN: function() {},
pop: function() {},
replaceAtIndex: function() {},
replace: function() {},
replacePrevious: function() {},
popToTop: function() {},
popToRoute: function() {},
replacePreviousAndPop: function() {},
resetTo: function() {},
getCurrentRoutes: function() {},
_cleanScenesPastIndex: function() {},
_renderScene: function() {},
_renderNavigationBar: function() {},
render: function() {},
_getNavigationContext: function() {},
});
var mixin5 = {
mixins: [mixin4],
statics: {DecelerationRate: null},
scrollResponderMixinGetInitialState: function() {},
scrollResponderHandleScrollShouldSetResponder: function() {},
scrollResponderHandleStartShouldSetResponder: function() {},
scrollResponderHandleStartShouldSetResponderCapture: function() {},
scrollResponderHandleResponderReject: function() {},
scrollResponderHandleTerminationRequest: function() {},
scrollResponderHandleTouchEnd: function() {},
scrollResponderHandleResponderRelease: function() {},
scrollResponderHandleScroll: function() {},
scrollResponderHandleResponderGrant: function() {},
scrollResponderHandleScrollBeginDrag: function() {},
scrollResponderHandleScrollEndDrag: function() {},
scrollResponderHandleMomentumScrollBegin: function() {},
scrollResponderHandleMomentumScrollEnd: function() {},
scrollResponderHandleTouchStart: function() {},
scrollResponderHandleTouchMove: function() {},
scrollResponderIsAnimating: function() {},
scrollResponderScrollTo: function() {},
scrollResponderScrollWithouthAnimationTo: function() {},
scrollResponderZoomTo: function() {},
scrollResponderScrollNativeHandleToKeyboard: function() {},
scrollResponderInputMeasureAndScrollToKeyboard: function() {},
scrollResponderTextInputFocusError: function() {},
componentWillMount: function() {},
scrollResponderKeyboardWillShow: function() {},
scrollResponderKeyboardWillHide: function() {},
scrollResponderKeyboardDidShow: function() {},
scrollResponderKeyboardDidHide: function() {},
};
React.createClass({
displayName: "ScrollView",
propTypes: {accessible: function() {}, accessibilityLabel: function() {}, accessibilityComponentType: function() {}, accessibilityLiveRegion: function() {}, importantForAccessibility: function() {}, accessibilityTraits: function() {}, onAccessibilityTap: function() {}, onMagicTap: function() {}, testID: function() {}, onResponderGrant: function() {}, onResponderMove: function() {}, onResponderReject: function() {}, onResponderRelease: function() {}, onResponderTerminate: function() {}, onResponderTerminationRequest: function() {}, onStartShouldSetResponder: function() {}, onStartShouldSetResponderCapture: function() {}, onMoveShouldSetResponder: function() {}, onMoveShouldSetResponderCapture: function() {}, onLayout: function() {}, pointerEvents: function() {}, style: function() {}, removeClippedSubviews: function() {}, renderToHardwareTextureAndroid: function() {}, shouldRasterizeIOS: function() {}, collapsable: function() {}, needsOffscreenAlphaCompositing: function() {}, automaticallyAdjustContentInsets: function() {}, contentInset: function() {}, contentOffset: function() {}, bounces: function() {}, bouncesZoom: function() {}, alwaysBounceHorizontal: function() {}, alwaysBounceVertical: function() {}, centerContent: function() {}, contentContainerStyle: function() {}, decelerationRate: function() {}, horizontal: function() {}, directionalLockEnabled: function() {}, canCancelContentTouches: function() {}, keyboardDismissMode: function() {}, keyboardShouldPersistTaps: function() {}, maximumZoomScale: function() {}, minimumZoomScale: function() {}, onScroll: function() {}, onScrollAnimationEnd: function() {}, onContentSizeChange: function() {}, pagingEnabled: function() {}, scrollEnabled: function() {}, scrollEventThrottle: function() {}, scrollIndicatorInsets: function() {}, scrollsToTop: function() {}, showsHorizontalScrollIndicator: function() {}, showsVerticalScrollIndicator: function() {}, stickyHeaderIndices: function() {}, snapToInterval: function() {}, snapToAlignment: function() {}, zoomScale: function() {}, onRefreshStart: function() {}},
mixins: [mixin5],
getInitialState: function() {},
setNativeProps: function() {},
endRefreshing: function() {},
getScrollResponder: function() {},
getInnerViewNode: function() {},
scrollTo: function() {},
scrollWithoutAnimationTo: function() {},
handleScroll: function() {},
_handleContentOnLayout: function() {},
render: function() {},
});
var mixin6 = {
componentWillUnmount: function() {},
setTimeout: function() {},
clearTimeout: function() {},
setInterval: function() {},
clearInterval: function() {},
setImmediate: function() {},
clearImmediate: function() {},
requestAnimationFrame: function() {},
cancelAnimationFrame: function() {},
};
React.createClass({
displayName: "AdsManagerTabsModalView",
contextTypes: {navigation: function() {}},
mixins: [mixin6, mixin4],
getDefaultProps: function() {},
getInitialState: function() {},
componentWillMount: function() {},
componentDidMount: function() {},
componentWillUnmount: function() {},
onTabSelect: function() {},
_handleConnectivityChange: function() {},
_onTabTap: function() {},
startCreateFlow: function() {},
render: function() {},
_renderTabs: function() {},
_onAdCreated: function() {},
_onRemoteNotification: function() {},
_setAccountFromURL: function() {},
_updateBadgeCount: function() {},
_onRetry: function() {},
_getUnseenNotifsCount: function() {},
_pushNotifPermalink: function() {},
_onMobileConfigsLoadDone: function() {},
_getAccountRoute: function() {},
_getNotifsRoute: function() {},
_getSettingsRoute: function() {},
_getCampaignsRoute: function() {},
_getNavStack: function() {},
_onAccountChanged: function() {},
_onHelpCenterRequested: function() {},
_onGlobalError: function() {},
_onGlobalErrorToastDidClose: function() {},
_onShowNUX: function() {},
_closeToastAndPopover: function() {},
_onTabLayout: function() {},
_onRootNavigationWillChange: function() {},
_handleOpenURL: function() {},
_updateAccountIDFromInLink: function() {},
_pushInLink: function() {},
_pushExternalRoute: function() {},
_canPushExernalRoute: function() {},
_hasAccountTab: function() {},
_hasCampaignsTab: function() {},
_getTabForURL: function() {},
_getTabForExternalRoute: function() {},
});
React.createClass({
displayName: "ActionBarButton",
mixins: [mixin1, mixin6],
propTypes: {label: function() {}, iconOnly: function() {}, imageSource: function() {}, style: function() {}, onContentMeasured: function() {}, onPress: function() {}},
componentDidMount: function() {},
render: function() {},
});
React.createClass({
displayName: "ListView",
mixins: [mixin5, mixin2],
statics: {DataSource: null},
propTypes: {accessible: function() {}, accessibilityLabel: function() {}, accessibilityComponentType: function() {}, accessibilityLiveRegion: function() {}, importantForAccessibility: function() {}, accessibilityTraits: function() {}, onAccessibilityTap: function() {}, onMagicTap: function() {}, testID: function() {}, onResponderGrant: function() {}, onResponderMove: function() {}, onResponderReject: function() {}, onResponderRelease: function() {}, onResponderTerminate: function() {}, onResponderTerminationRequest: function() {}, onStartShouldSetResponder: function() {}, onStartShouldSetResponderCapture: function() {}, onMoveShouldSetResponder: function() {}, onMoveShouldSetResponderCapture: function() {}, onLayout: function() {}, pointerEvents: function() {}, style: function() {}, removeClippedSubviews: function() {}, renderToHardwareTextureAndroid: function() {}, shouldRasterizeIOS: function() {}, collapsable: function() {}, needsOffscreenAlphaCompositing: function() {}, automaticallyAdjustContentInsets: function() {}, contentInset: function() {}, contentOffset: function() {}, bounces: function() {}, bouncesZoom: function() {}, alwaysBounceHorizontal: function() {}, alwaysBounceVertical: function() {}, centerContent: function() {}, contentContainerStyle: function() {}, decelerationRate: function() {}, horizontal: function() {}, directionalLockEnabled: function() {}, canCancelContentTouches: function() {}, keyboardDismissMode: function() {}, keyboardShouldPersistTaps: function() {}, maximumZoomScale: function() {}, minimumZoomScale: function() {}, onScroll: function() {}, onScrollAnimationEnd: function() {}, onContentSizeChange: function() {}, pagingEnabled: function() {}, scrollEnabled: function() {}, scrollEventThrottle: function() {}, scrollIndicatorInsets: function() {}, scrollsToTop: function() {}, showsHorizontalScrollIndicator: function() {}, showsVerticalScrollIndicator: function() {}, stickyHeaderIndices: function() {}, snapToInterval: function() {}, snapToAlignment: function() {}, zoomScale: function() {}, onRefreshStart: function() {}, dataSource: function() {}, renderSeparator: function() {}, renderRow: function() {}, initialListSize: function() {}, onEndReached: function() {}, onEndReachedThreshold: function() {}, pageSize: function() {}, renderFooter: function() {}, renderHeader: function() {}, renderSectionHeader: function() {}, renderScrollComponent: function() {}, scrollRenderAheadDistance: function() {}, onChangeVisibleRows: function() {}},
getMetrics: function() {},
getScrollResponder: function() {},
setNativeProps: function() {},
getDefaultProps: function() {},
getInitialState: function() {},
getInnerViewNode: function() {},
componentWillMount: function() {},
componentDidMount: function() {},
componentWillReceiveProps: function() {},
componentDidUpdate: function() {},
onRowHighlighted: function() {},
render: function() {},
_measureAndUpdateScrollProps: function() {},
_onContentSizeChange: function() {},
_onLayout: function() {},
_setScrollVisibleLength: function() {},
_updateChildFrames: function() {},
_maybeCallOnEndReached: function() {},
_renderMoreRowsIfNeeded: function() {},
_pageInNewRows: function() {},
_getDistanceFromEnd: function() {},
_updateVisibleRows: function() {},
_onScroll: function() {},
});
33 changes: 16 additions & 17 deletions src/isomorphic/classic/class/ReactClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ function validateTypeDef(Constructor, typeDef, location) {
}
}

function validateMethodOverride(proto, name) {
function validateMethodOverride(isAlreadyDefined, name) {
var specPolicy = ReactClassInterface.hasOwnProperty(name) ?
ReactClassInterface[name] :
null;
Expand All @@ -431,7 +431,7 @@ function validateMethodOverride(proto, name) {
}

// Disallow defining methods more than once unless explicitly allowed.
if (proto.hasOwnProperty(name)) {
if (isAlreadyDefined) {
invariant(
specPolicy === SpecPolicy.DEFINE_MANY ||
specPolicy === SpecPolicy.DEFINE_MANY_MERGED,
Expand Down Expand Up @@ -465,6 +465,7 @@ function mixSpecIntoComponent(Constructor, spec) {
);

var proto = Constructor.prototype;
var autoBindPairs = proto.__reactAutoBindPairs;

// By handling mixins before any other properties, we ensure the same
// chaining order is applied to methods with DEFINE_MANY policy, whether
Expand All @@ -484,7 +485,8 @@ function mixSpecIntoComponent(Constructor, spec) {
}

var property = spec[name];
validateMethodOverride(proto, name);
var isAlreadyDefined = proto.hasOwnProperty(name);
validateMethodOverride(isAlreadyDefined, name);

if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
RESERVED_SPEC_KEYS[name](Constructor, property);
Expand All @@ -495,7 +497,6 @@ function mixSpecIntoComponent(Constructor, spec) {
// 2. Overridden methods (that were mixed in).
var isReactClassMethod =
ReactClassInterface.hasOwnProperty(name);
var isAlreadyDefined = proto.hasOwnProperty(name);
var isFunction = typeof property === 'function';
var shouldAutoBind =
isFunction &&
Expand All @@ -504,10 +505,7 @@ function mixSpecIntoComponent(Constructor, spec) {
spec.autobind !== false;

if (shouldAutoBind) {
if (!proto.__reactAutoBindMap) {
proto.__reactAutoBindMap = {};
}
proto.__reactAutoBindMap[name] = property;
autoBindPairs.push(name, property);
proto[name] = property;
} else {
if (isAlreadyDefined) {
Expand Down Expand Up @@ -700,14 +698,14 @@ function bindAutoBindMethod(component, method) {
* @param {object} component Component whose method is going to be bound.
*/
function bindAutoBindMethods(component) {
for (var autoBindKey in component.__reactAutoBindMap) {
if (component.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
var method = component.__reactAutoBindMap[autoBindKey];
component[autoBindKey] = bindAutoBindMethod(
component,
method
);
}
var pairs = component.__reactAutoBindPairs;
for (var i = 0; i < pairs.length; i += 2) {
var autoBindKey = pairs[i];
var method = pairs[i + 1];
component[autoBindKey] = bindAutoBindMethod(
component,
method
);
}
}

Expand Down Expand Up @@ -812,7 +810,7 @@ var ReactClass = {
}

// Wire up auto-binding
if (this.__reactAutoBindMap) {
if (this.__reactAutoBindPairs.length) {
bindAutoBindMethods(this);
}

Expand Down Expand Up @@ -846,6 +844,7 @@ var ReactClass = {
};
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];

injectedMixins.forEach(
mixSpecIntoComponent.bind(null, Constructor)
Expand Down