Skip to content

Commit 4ce0358

Browse files
sahrensfacebook-github-bot-0
authored andcommitted
Improve Text perf
Summary: public Most apps create tons of text components but they are actually quite heavy because of the the Touchable mixin which requires binding tons of functions for every instance created. This diff makes the binding lazy, so that the main handlers are only bound if there is a valid touch action configured (e.g. onPress), and the Touchable mixin functions are only bound the first time the node is actually touched and becomes the responder. ScanLab testing shows 5-10% win on render time and memory for various products. Reviewed By: sebmarkbage Differential Revision: D2716823 fb-gh-sync-id: 30adb2ed2231c5635c9336369616cf31c776b930
1 parent e25d5c2 commit 4ce0358

File tree

1 file changed

+120
-118
lines changed

1 file changed

+120
-118
lines changed

Libraries/Text/Text.js

Lines changed: 120 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,22 @@
1111
*/
1212
'use strict';
1313

14-
var NativeMethodsMixin = require('NativeMethodsMixin');
15-
var Platform = require('Platform');
16-
var React = require('React');
17-
var ReactInstanceMap = require('ReactInstanceMap');
18-
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
19-
var StyleSheetPropType = require('StyleSheetPropType');
20-
var TextStylePropTypes = require('TextStylePropTypes');
21-
var Touchable = require('Touchable');
22-
23-
var createReactNativeComponentClass =
14+
const NativeMethodsMixin = require('NativeMethodsMixin');
15+
const Platform = require('Platform');
16+
const React = require('React');
17+
const ReactInstanceMap = require('ReactInstanceMap');
18+
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
19+
const StyleSheetPropType = require('StyleSheetPropType');
20+
const TextStylePropTypes = require('TextStylePropTypes');
21+
const Touchable = require('Touchable');
22+
23+
const createReactNativeComponentClass =
2424
require('createReactNativeComponentClass');
25-
var merge = require('merge');
25+
const merge = require('merge');
2626

27-
var stylePropType = StyleSheetPropType(TextStylePropTypes);
27+
const stylePropType = StyleSheetPropType(TextStylePropTypes);
2828

29-
var viewConfig = {
29+
const viewConfig = {
3030
validAttributes: merge(ReactNativeViewAttributes.UIView, {
3131
isHighlighted: true,
3232
numberOfLines: true,
@@ -68,10 +68,7 @@ var viewConfig = {
6868
* ```
6969
*/
7070

71-
var Text = React.createClass({
72-
73-
mixins: [Touchable.Mixin, NativeMethodsMixin],
74-
71+
const Text = React.createClass({
7572
propTypes: {
7673
/**
7774
* Used to truncate the text with an elipsis after computing the text
@@ -105,121 +102,126 @@ var Text = React.createClass({
105102
*/
106103
allowFontScaling: React.PropTypes.bool,
107104
},
108-
109-
viewConfig: viewConfig,
110-
111-
getInitialState: function(): Object {
112-
return merge(this.touchableGetInitialState(), {
113-
isHighlighted: false,
114-
});
115-
},
116-
getDefaultProps: function(): Object {
105+
getDefaultProps(): Object {
117106
return {
107+
accessible: true,
118108
allowFontScaling: true,
119109
};
120110
},
121-
122-
onStartShouldSetResponder: function(): bool {
123-
var shouldSetFromProps = this.props.onStartShouldSetResponder &&
124-
this.props.onStartShouldSetResponder();
125-
return shouldSetFromProps || !!this.props.onPress;
126-
},
127-
128-
/*
129-
* Returns true to allow responder termination
130-
*/
131-
handleResponderTerminationRequest: function(): bool {
132-
// Allow touchable or props.onResponderTerminationRequest to deny
133-
// the request
134-
var allowTermination = this.touchableHandleResponderTerminationRequest();
135-
if (allowTermination && this.props.onResponderTerminationRequest) {
136-
allowTermination = this.props.onResponderTerminationRequest();
137-
}
138-
return allowTermination;
139-
},
140-
141-
handleResponderGrant: function(e: SyntheticEvent, dispatchID: string) {
142-
this.touchableHandleResponderGrant(e, dispatchID);
143-
this.props.onResponderGrant &&
144-
this.props.onResponderGrant.apply(this, arguments);
145-
},
146-
147-
handleResponderMove: function(e: SyntheticEvent) {
148-
this.touchableHandleResponderMove(e);
149-
this.props.onResponderMove &&
150-
this.props.onResponderMove.apply(this, arguments);
151-
},
152-
153-
handleResponderRelease: function(e: SyntheticEvent) {
154-
this.touchableHandleResponderRelease(e);
155-
this.props.onResponderRelease &&
156-
this.props.onResponderRelease.apply(this, arguments);
157-
},
158-
159-
handleResponderTerminate: function(e: SyntheticEvent) {
160-
this.touchableHandleResponderTerminate(e);
161-
this.props.onResponderTerminate &&
162-
this.props.onResponderTerminate.apply(this, arguments);
163-
},
164-
165-
touchableHandleActivePressIn: function() {
166-
if (this.props.suppressHighlighting || !this.props.onPress) {
167-
return;
168-
}
169-
this.setState({
170-
isHighlighted: true,
171-
});
172-
},
173-
174-
touchableHandleActivePressOut: function() {
175-
if (this.props.suppressHighlighting || !this.props.onPress) {
176-
return;
177-
}
178-
this.setState({
111+
getInitialState: function(): Object {
112+
return merge(Touchable.Mixin.touchableGetInitialState(), {
179113
isHighlighted: false,
180114
});
181115
},
182-
183-
touchableHandlePress: function() {
184-
this.props.onPress && this.props.onPress();
185-
},
186-
187-
touchableGetPressRectOffset: function(): RectOffset {
188-
return PRESS_RECT_OFFSET;
189-
},
190-
191-
getChildContext: function(): Object {
116+
mixins: [NativeMethodsMixin],
117+
viewConfig: viewConfig,
118+
getChildContext(): Object {
192119
return {isInAParentText: true};
193120
},
194-
195121
childContextTypes: {
196122
isInAParentText: React.PropTypes.bool
197123
},
198-
199-
render: function() {
200-
var props = {};
201-
for (var key in this.props) {
202-
props[key] = this.props[key];
203-
}
204-
// Text is accessible by default
205-
if (props.accessible !== false) {
206-
props.accessible = true;
124+
contextTypes: {
125+
isInAParentText: React.PropTypes.bool
126+
},
127+
/**
128+
* Only assigned if touch is needed.
129+
*/
130+
_handlers: (null: ?Object),
131+
/**
132+
* These are assigned lazily the first time the responder is set to make plain
133+
* text nodes as cheap as possible.
134+
*/
135+
touchableHandleActivePressIn: (null: ?Function),
136+
touchableHandleActivePressOut: (null: ?Function),
137+
touchableHandlePress: (null: ?Function),
138+
touchableGetPressRectOffset: (null: ?Function),
139+
render(): ReactElement {
140+
let newProps = this.props;
141+
if (this.props.onStartShouldSetResponder || this.props.onPress) {
142+
if (!this._handlers) {
143+
this._handlers = {
144+
onStartShouldSetResponder: (): bool => {
145+
const shouldSetFromProps = this.props.onStartShouldSetResponder &&
146+
this.props.onStartShouldSetResponder();
147+
const setResponder = shouldSetFromProps || !!this.props.onPress;
148+
if (setResponder && !this.touchableHandleActivePressIn) {
149+
// Attach and bind all the other handlers only the first time a touch
150+
// actually happens.
151+
for (let key in Touchable.Mixin) {
152+
if (typeof Touchable.Mixin[key] === 'function') {
153+
(this: any)[key] = Touchable.Mixin[key].bind(this);
154+
}
155+
}
156+
this.touchableHandleActivePressIn = () => {
157+
if (this.props.suppressHighlighting || !this.props.onPress) {
158+
return;
159+
}
160+
this.setState({
161+
isHighlighted: true,
162+
});
163+
};
164+
165+
this.touchableHandleActivePressOut = () => {
166+
if (this.props.suppressHighlighting || !this.props.onPress) {
167+
return;
168+
}
169+
this.setState({
170+
isHighlighted: false,
171+
});
172+
};
173+
174+
this.touchableHandlePress = () => {
175+
this.props.onPress && this.props.onPress();
176+
};
177+
178+
this.touchableGetPressRectOffset = function(): RectOffset {
179+
return PRESS_RECT_OFFSET;
180+
};
181+
}
182+
return setResponder;
183+
},
184+
onResponderGrant: (e: SyntheticEvent, dispatchID: string) => {
185+
this.touchableHandleResponderGrant(e, dispatchID);
186+
this.props.onResponderGrant &&
187+
this.props.onResponderGrant.apply(this, arguments);
188+
},
189+
onResponderMove: (e: SyntheticEvent) => {
190+
this.touchableHandleResponderMove(e);
191+
this.props.onResponderMove &&
192+
this.props.onResponderMove.apply(this, arguments);
193+
},
194+
onResponderRelease: (e: SyntheticEvent) => {
195+
this.touchableHandleResponderRelease(e);
196+
this.props.onResponderRelease &&
197+
this.props.onResponderRelease.apply(this, arguments);
198+
},
199+
onResponderTerminate: (e: SyntheticEvent) => {
200+
this.touchableHandleResponderTerminate(e);
201+
this.props.onResponderTerminate &&
202+
this.props.onResponderTerminate.apply(this, arguments);
203+
},
204+
onResponderTerminationRequest: (): bool => {
205+
// Allow touchable or props.onResponderTerminationRequest to deny
206+
// the request
207+
var allowTermination = this.touchableHandleResponderTerminationRequest();
208+
if (allowTermination && this.props.onResponderTerminationRequest) {
209+
allowTermination = this.props.onResponderTerminationRequest();
210+
}
211+
return allowTermination;
212+
},
213+
};
214+
}
215+
newProps = {
216+
...this.props,
217+
...this._handlers,
218+
isHighlighted: this.state.isHighlighted,
219+
};
207220
}
208-
props.isHighlighted = this.state.isHighlighted;
209-
props.onStartShouldSetResponder = this.onStartShouldSetResponder;
210-
props.onResponderTerminationRequest =
211-
this.handleResponderTerminationRequest;
212-
props.onResponderGrant = this.handleResponderGrant;
213-
props.onResponderMove = this.handleResponderMove;
214-
props.onResponderRelease = this.handleResponderRelease;
215-
props.onResponderTerminate = this.handleResponderTerminate;
216-
217-
// TODO: Switch to use contextTypes and this.context after React upgrade
218-
var context = ReactInstanceMap.get(this)._context;
219-
if (context.isInAParentText) {
220-
return <RCTVirtualText {...props} />;
221+
if (this.context.isInAParentText) {
222+
return <RCTVirtualText {...newProps} />;
221223
} else {
222-
return <RCTText {...props} />;
224+
return <RCTText {...newProps} />;
223225
}
224226
},
225227
});

0 commit comments

Comments
 (0)