Skip to content

Commit acd38bd

Browse files
authored
Infra/optimize tab controller (#803)
* Support passing items to TabController in order to reduce render calls * Reduce TabBar render by moving enableScroll calculation * forwardRef correctly to Incubator.TouchableOpacity * Optimize how we lock TabBarItem widths * Update TabController example screen to pass items to controller and tabbar * minor fix with registerTabItems
1 parent d02a681 commit acd38bd

File tree

5 files changed

+68
-58
lines changed

5 files changed

+68
-58
lines changed

demo/src/screens/componentScreens/TabControllerScreen/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,11 @@ class TabControllerScreen extends Component {
109109
key={key}
110110
asCarousel={asCarousel}
111111
selectedIndex={selectedIndex}
112-
onChangeIndex={this.onChangeIndex}
112+
onChangeIndex={this.onChangeIndex}
113+
items={items}
113114
>
114115
<TabController.TabBar
115-
items={items}
116+
// items={items}
116117
// key={key}
117118
// uppercase
118119
// indicatorStyle={{backgroundColor: 'green', height: 3}}

src/components/tabController/TabBar.js

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class TabBar extends PureComponent {
123123
LogService.warn('uilib: Please pass the "items" prop to TabController.TabBar instead of children');
124124
}
125125

126-
const itemsCount = this.itemsCount;
126+
const itemsCount = this.getItemsCount();
127127

128128
this.tabBar = React.createRef();
129129
this.tabBarScrollOffset = 0;
@@ -143,8 +143,11 @@ class TabBar extends PureComponent {
143143
itemsWidths: undefined
144144
};
145145

146-
this.registerTabItems();
147-
if (props.items && props.optimize) {
146+
if ((props.items || this.children) && !context.items) {
147+
this.registerTabItems();
148+
}
149+
150+
if (this.items && props.optimize) {
148151
this.measureItems();
149152
}
150153
}
@@ -157,21 +160,27 @@ class TabBar extends PureComponent {
157160
return _.filter(this.props.children, (child) => !!child);
158161
}
159162

160-
get itemsCount() {
161-
const {items} = this.props;
162-
if (items) {
163-
return _.size(items);
164-
} else {
165-
return React.Children.count(this.children);
166-
}
167-
}
168163

169164
get centerOffset() {
170165
const {centerSelected} = this.props;
171166
const guesstimateCenterValue = 60;
172167
return centerSelected ? this.containerWidth / 2 - guesstimateCenterValue : 0;
173168
}
174169

170+
get items() {
171+
const {items: contextItems} = this.context;
172+
const {items: propsItems} = this.props;
173+
return contextItems || propsItems;
174+
}
175+
176+
getItemsCount() {
177+
if (this.items) {
178+
return _.size(this.items);
179+
} else {
180+
return React.Children.count(this.children);
181+
}
182+
}
183+
175184
getSnapBreakpoints() {
176185
const {centerSelected} = this.props;
177186
const {itemsWidths, itemsOffsets} = this.state;
@@ -187,8 +196,10 @@ class TabBar extends PureComponent {
187196
}
188197

189198
measureItems = async () => {
190-
const {labelStyle} = this.props;
191-
const measuring = _.map(this.props.items, (item) => {
199+
const {items: contextItems} = this.context;
200+
const {labelStyle, items: propsItems} = this.props;
201+
const items = contextItems || propsItems;
202+
const measuring = _.map(items, (item) => {
192203
return Typography.measureTextSize(item.label, labelStyle);
193204
});
194205
const results = await Promise.all(measuring);
@@ -265,8 +276,10 @@ class TabBar extends PureComponent {
265276
const {selectedIndex} = this.context;
266277
const itemsOffsets = _.map(this._itemsOffsets, (offset) => offset + INDICATOR_INSET);
267278
const itemsWidths = _.map(this._itemsWidths, (width) => width - INDICATOR_INSET * 2);
279+
this.contentWidth = _.sum(this._itemsWidths);
280+
const scrollEnabled = this.contentWidth > this.containerWidth;
268281

269-
this.setState({itemsWidths, itemsOffsets});
282+
this.setState({itemsWidths, itemsOffsets, scrollEnabled});
270283
this.focusSelected([selectedIndex], false);
271284
};
272285

@@ -281,7 +294,7 @@ class TabBar extends PureComponent {
281294
} else if (this.tabBarScrollOffset <= leftThreshold && fadeLeft) {
282295
stateUpdate.fadeLeft = false;
283296
}
284-
297+
285298
const rightThreshold = (this.contentWidth - this.containerWidth);
286299
if (this.tabBarScrollOffset < rightThreshold && !fadeRight) {
287300
stateUpdate.fadeRight = true;
@@ -294,12 +307,6 @@ class TabBar extends PureComponent {
294307
}
295308
};
296309

297-
onContentSizeChange = (width) => {
298-
if (width > this.containerWidth && !this.contentWidth) {
299-
this.contentWidth = width;
300-
this.setState({scrollEnabled: true});
301-
}
302-
};
303310

304311
renderSelectedIndicator() {
305312
const {itemsWidths} = this.state;
@@ -313,7 +320,6 @@ class TabBar extends PureComponent {
313320
const {itemStates} = this.context;
314321
const {
315322
optimize,
316-
items,
317323
labelColor,
318324
selectedLabelColor,
319325
labelStyle,
@@ -328,8 +334,8 @@ class TabBar extends PureComponent {
328334
return;
329335
}
330336

331-
if (items) {
332-
return _.map(items, (item, index) => {
337+
if (this.items) {
338+
return _.map(this.items, (item, index) => {
333339
return (
334340
<TabBarItem
335341
labelColor={labelColor}
@@ -341,7 +347,7 @@ class TabBar extends PureComponent {
341347
selectedIconColor={selectedIconColor}
342348
activeBackgroundColor={activeBackgroundColor}
343349
key={item.label}
344-
width={this._itemsWidths[index]}
350+
// width={this._itemsWidths[index]}
345351
{...item}
346352
{...this.context}
347353
index={index}
@@ -409,7 +415,6 @@ class TabBar extends PureComponent {
409415
style={styles.tabBarScroll}
410416
contentContainerStyle={{minWidth: this.containerWidth}}
411417
scrollEnabled={scrollEnabled}
412-
onContentSizeChange={this.onContentSizeChange}
413418
onScroll={this.onScroll}
414419
scrollEventThrottle={16}
415420
testID={testID}

src/components/tabController/TabBarItem.js

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {Colors, Typography, Spacings} from '../../style';
1010
import Badge from '../../components/badge';
1111
import {TouchableOpacity} from '../../incubator';
1212

13-
const {cond, eq, call, block, event, and, defined} = Reanimated;
13+
const {cond, eq, call, block, event, and} = Reanimated;
1414

1515
const DEFAULT_LABEL_COLOR = Colors.black;
1616
const DEFAULT_SELECTED_LABEL_COLOR = Colors.blue30;
@@ -106,9 +106,9 @@ export default class TabBarItem extends PureComponent {
106106
onPress: _.noop
107107
};
108108

109-
state = {
110-
itemWidth: undefined
111-
};
109+
state = {};
110+
itemWidth = this.props.width;
111+
itemRef = React.createRef();
112112

113113
onStateChange = event([
114114
{
@@ -123,10 +123,10 @@ export default class TabBarItem extends PureComponent {
123123
}
124124
}) => {
125125
const {index, onLayout} = this.props;
126-
const {itemWidth} = this.state;
127-
if (!itemWidth) {
126+
if (!this.itemWidth) {
127+
this.itemWidth = width;
128+
this.itemRef.current.setNativeProps({style: {width, paddingHorizontal: null, flex: null}});
128129
if (onLayout) {
129-
this.setState({itemWidth: width});
130130
onLayout({width, x}, index);
131131
}
132132
}
@@ -138,8 +138,7 @@ export default class TabBarItem extends PureComponent {
138138
};
139139

140140
getItemStyle() {
141-
const {state, width} = this.props;
142-
const {itemWidth} = this.state;
141+
const {state} = this.props;
143142
const opacity = block([
144143
cond(eq(state, State.END), call([], this.onPress)),
145144
cond(eq(state, State.BEGAN), this.props.activeOpacity, 1)
@@ -149,44 +148,36 @@ export default class TabBarItem extends PureComponent {
149148
opacity
150149
};
151150

152-
if (width || itemWidth) {
153-
style.flex = undefined;
154-
style.width = width || itemWidth;
155-
style.paddingHorizontal = undefined;
156-
}
151+
// if (this.itemWidth) {
152+
// style.flex = undefined;
153+
// style.width = this.itemWidth;
154+
// style.paddingHorizontal = undefined;
155+
// }
157156

158157
return style;
159158
}
160159

161160
getLabelStyle() {
162-
const {itemWidth} = this.state;
163-
const {
164-
index,
165-
currentPage,
166-
targetPage,
167-
labelColor,
168-
selectedLabelColor,
169-
ignore
170-
} = this.props;
161+
const {index, currentPage, targetPage, labelColor, selectedLabelColor, ignore} = this.props;
171162

172163
const labelStyle = this.props.labelStyle;
173164
const selectedLabelStyle = this.props.selectedLabelStyle;
174165
let fontWeight, letterSpacing, fontFamily;
175166

176167
if (labelStyle.fontWeight || selectedLabelStyle.fontWeight) {
177-
fontWeight = cond(and(eq(targetPage, index), defined(itemWidth)),
168+
fontWeight = cond(and(eq(targetPage, index) /* , defined(itemWidth) */),
178169
selectedLabelStyle.fontWeight || 'normal',
179170
labelStyle.fontWeight || 'normal');
180171
}
181172

182173
if (labelStyle.letterSpacing || selectedLabelStyle.letterSpacing) {
183-
letterSpacing = cond(and(eq(targetPage, index), defined(itemWidth)),
174+
letterSpacing = cond(and(eq(targetPage, index) /* , defined(itemWidth) */),
184175
selectedLabelStyle.letterSpacing || 0,
185176
labelStyle.letterSpacing || 0);
186177
}
187178

188179
if (labelStyle.fontFamily || selectedLabelStyle.fontFamily) {
189-
fontFamily = cond(and(eq(targetPage, index), defined(itemWidth)),
180+
fontFamily = cond(and(eq(targetPage, index) /* , defined(itemWidth) */),
190181
selectedLabelStyle.fontFamily,
191182
labelStyle.fontFamily);
192183
}
@@ -207,7 +198,8 @@ export default class TabBarItem extends PureComponent {
207198
fontWeight,
208199
letterSpacing,
209200
color
210-
}, _.isUndefined)
201+
},
202+
_.isUndefined)
211203
];
212204
}
213205

@@ -233,6 +225,7 @@ export default class TabBarItem extends PureComponent {
233225

234226
return (
235227
<TouchableOpacity
228+
ref={this.itemRef}
236229
pressState={state}
237230
style={[styles.tabItem, this.getItemStyle()]}
238231
onLayout={this.onLayout}

src/components/tabController/index.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,28 @@ class TabController extends Component {
7474
constructor(props) {
7575
super(props);
7676

77+
let itemStates = [];
78+
let ignoredItems = [];
79+
if (props.items) {
80+
const itemsCount = _.chain(props.items).filter(item => !item.ignore).size().value();
81+
itemStates = _.times(itemsCount, () => new Value(State.UNDETERMINED));
82+
ignoredItems = _.filter(props.items, item => item.ignore);
83+
}
84+
7785
this.state = {
7886
selectedIndex: this.props.selectedIndex,
7987
asCarousel: this.props.asCarousel,
80-
itemStates: [],
8188
pageWidth: this.pageWidth,
89+
// items
90+
items: props.items,
91+
itemStates,
92+
ignoredItems,
8293
// animated values
8394
targetPage: new Value(this.props.selectedIndex),
8495
currentPage: new Value(this.props.selectedIndex),
8596
carouselOffset: new Value(this.props.selectedIndex * Math.round(this.pageWidth)),
8697
containerWidth: new Value(this.pageWidth),
87-
// // callbacks
98+
// callbacks
8899
registerTabItems: this.registerTabItems,
89100
onChangeIndex: this.props.onChangeIndex
90101
};

src/incubator/TouchableOpacity.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,11 @@ class TouchableOpacity extends PureComponent<TouchableOpacityPropTypes & BaseCom
139139
<TapGestureHandler
140140
onHandlerStateChange={this.onStateChange}
141141
shouldCancelWhenOutside
142-
ref={forwardedRef}
143142
enabled={!disabled}
144143
>
145144
<Reanimated.View
146145
{...others}
146+
ref={forwardedRef}
147147
style={[
148148
borderRadius && {borderRadius},
149149
flexStyle,

0 commit comments

Comments
 (0)