[ReactNative] Avoid navigator scene renders during transition

This commit is contained in:
Eric Vicenti 2015-04-10 15:48:19 -07:00
parent 50959dd42c
commit 75d52b0f33
1 changed files with 60 additions and 38 deletions

View File

@ -71,7 +71,7 @@ var styles = StyleSheet.create({
bottom: 0, bottom: 0,
top: 0, top: 0,
}, },
presentNavItem: { currentScene: {
position: 'absolute', position: 'absolute',
overflow: 'hidden', overflow: 'hidden',
left: 0, left: 0,
@ -79,7 +79,7 @@ var styles = StyleSheet.create({
bottom: 0, bottom: 0,
top: 0, top: 0,
}, },
futureNavItem: { futureScene: {
overflow: 'hidden', overflow: 'hidden',
position: 'absolute', position: 'absolute',
left: 0, left: 0,
@ -991,9 +991,42 @@ var Navigator = React.createClass({
} }
}, },
_routeToOptimizedStackItem: function(route, i) { _renderOptimizedScenes: function() {
var shouldUpdateChild = // To avoid rendering scenes that are not visible, we use
this.state.updatingRangeLength !== 0 && // updatingRangeStart and updatingRangeLength to track the scenes that need
// to be updated.
// To avoid visual glitches, we never re-render scenes during a transition.
// We assume that `state.updatingRangeLength` will have a length during the
// initial render of any scene
var shouldRenderScenes = !this.state.isAnimating &&
this.state.updatingRangeLength !== 0;
if (shouldRenderScenes) {
return (
<StaticContainer shouldUpdate={true}>
<View
style={styles.transitioner}
{...this.panGesture.panHandlers}
onResponderTerminationRequest={
this._handleResponderTerminationRequest
}>
{this.state.routeStack.map(this._renderOptimizedScene)}
</View>
</StaticContainer>
);
}
// If no scenes are changing, we can save render time. React will notice
// that we are rendering a StaticContainer in the same place, so the
// existing element will be updated. When React asks the element
// shouldComponentUpdate, the StaticContainer will return false, and the
// children from the previous reconciliation will remain.
return (
<StaticContainer shouldUpdate={false} />
);
},
_renderOptimizedScene: function(route, i) {
var shouldRenderScene =
i >= this.state.updatingRangeStart && i >= this.state.updatingRangeStart &&
i <= this.state.updatingRangeStart + this.state.updatingRangeLength; i <= this.state.updatingRangeStart + this.state.updatingRangeLength;
var sceneNavigatorContext = { var sceneNavigatorContext = {
@ -1003,49 +1036,38 @@ var Navigator = React.createClass({
this.navigatorContext.setHandlerForRoute(route, handler); this.navigatorContext.setHandlerForRoute(route, handler);
}, },
}; };
var child = this.props.renderScene( var scene = shouldRenderScene ?
route, this._renderScene(route, i, sceneNavigatorContext) : null;
sceneNavigatorContext
);
var initialSceneStyle =
i === this.state.presentedIndex ? styles.presentNavItem : styles.futureNavItem;
return ( return (
<NavigatorStaticContextContainer <NavigatorStaticContextContainer
navigatorContext={sceneNavigatorContext} navigatorContext={sceneNavigatorContext}
key={'nav' + i} key={'nav' + i}
shouldUpdate={shouldUpdateChild}> shouldUpdate={shouldRenderScene}>
<View {scene}
key={this.state.idStack[i]}
ref={'scene_' + i}
style={[initialSceneStyle, this.props.sceneStyle]}>
{React.cloneElement(child, {
ref: this._handleItemRef.bind(null, this.state.idStack[i]),
})}
</View>
</NavigatorStaticContextContainer> </NavigatorStaticContextContainer>
); );
}, },
renderNavigationStackItems: function() { _renderScene: function(route, i, sceneNavigatorContext) {
var shouldRecurseToNavigator = this.state.updatingRangeLength !== 0; var child = this.props.renderScene(
// If not recursing update to navigator at all, may as well avoid route,
// computation of navigator children. sceneNavigatorContext
var items = shouldRecurseToNavigator ? );
this.state.routeStack.map(this._routeToOptimizedStackItem) : null; var initialSceneStyle = i === this.state.presentedIndex ?
styles.currentScene : styles.futureScene;
return ( return (
<StaticContainer shouldUpdate={shouldRecurseToNavigator}> <View
<View key={this.state.idStack[i]}
style={styles.transitioner} ref={'scene_' + i}
{...this.panGesture.panHandlers} style={[initialSceneStyle, this.props.sceneStyle]}>
onResponderTerminationRequest={this._handleResponderTerminationRequest}> {React.cloneElement(child, {
{items} ref: this._handleItemRef.bind(null, this.state.idStack[i]),
</View> })}
</StaticContainer> </View>
); );
}, },
renderNavigationStackBar: function() { _renderNavigationBar: function() {
if (!this.props.navigationBar) { if (!this.props.navigationBar) {
return null; return null;
} }
@ -1059,8 +1081,8 @@ var Navigator = React.createClass({
render: function() { render: function() {
return ( return (
<View style={[styles.container, this.props.style]}> <View style={[styles.container, this.props.style]}>
{this.renderNavigationStackItems()} {this._renderOptimizedScenes()}
{this.renderNavigationStackBar()} {this._renderNavigationBar()}
</View> </View>
); );
}, },