diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-10-05 17:19:10 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-10-08 13:00:17 +0200 |
| commit | f9891e59134557d3cb52f06b6febef143f4da286 (patch) | |
| tree | 62da5462ffb8e47ae8a27a97063b06a666fba250 /gui | |
| parent | 282409db1722b836fe02cb27fcbbc3ba373ee4cc (diff) | |
| download | mullvadvpn-f9891e59134557d3cb52f06b6febef143f4da286.tar.xz mullvadvpn-f9891e59134557d3cb52f06b6febef143f4da286.zip | |
Optimize NavigationBar performance by eliminating render() when possible
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/packages/desktop/src/renderer/components/NavigationBar.js | 147 |
1 files changed, 95 insertions, 52 deletions
diff --git a/gui/packages/desktop/src/renderer/components/NavigationBar.js b/gui/packages/desktop/src/renderer/components/NavigationBar.js index e3108cc8ac..a0fef6dc81 100644 --- a/gui/packages/desktop/src/renderer/components/NavigationBar.js +++ b/gui/packages/desktop/src/renderer/components/NavigationBar.js @@ -132,14 +132,14 @@ export const NavigationScrollbars = React.forwardRef(function NavigationScrollba ); }); -type NavigationBarTitleProps = { +type PrivateTitleBarItemContainerProps = { visible: boolean, titleAdjustment: number, - children?: React.Node, + children?: React.Element<typeof TitleBarItem>, }; -class NavigationBarTitle extends Component<NavigationBarTitleProps> { - shouldComponentUpdate(nextProps: NavigationBarTitleProps) { +class PrivateTitleBarItemContainer extends Component<PrivateTitleBarItemContainerProps> { + shouldComponentUpdate(nextProps: PrivateTitleBarItemContainerProps) { return ( this.props.visible !== nextProps.visible || this.props.titleAdjustment !== nextProps.titleAdjustment || @@ -159,25 +159,25 @@ class NavigationBarTitle extends Component<NavigationBarTitleProps> { return ( <View style={[styles.navigationBarTitle, titleAdjustmentStyle]}> - <NavigationBarAnimatedTitle visible={this.props.visible}> + <PrivateBarItemAnimationContainer visible={this.props.visible}> {this.props.children} - </NavigationBarAnimatedTitle> + </PrivateBarItemAnimationContainer> </View> ); } } -type NavigationBarAnimatedTitleProps = { +type PrivateBarItemAnimationContainerProps = { visible: boolean, children?: React.Node, }; -class NavigationBarAnimatedTitle extends Component<NavigationBarAnimatedTitleProps> { +class PrivateBarItemAnimationContainer extends Component<PrivateBarItemAnimationContainerProps> { _opacityValue: Animated.Value; _opacityStyle: Styles.AnimatedViewStyle; _animation: ?Animated.Animation; - constructor(props: NavigationBarAnimatedTitleProps) { + constructor(props: PrivateBarItemAnimationContainerProps) { super(props); this._opacityValue = Animated.createValue(props.visible ? 1 : 0); @@ -186,7 +186,7 @@ class NavigationBarAnimatedTitle extends Component<NavigationBarAnimatedTitlePro }); } - shouldComponentUpdate(nextProps: NavigationBarAnimatedTitleProps) { + shouldComponentUpdate(nextProps: PrivateBarItemAnimationContainerProps) { return this.props.visible !== nextProps.visible || this.props.children !== nextProps.children; } @@ -222,17 +222,101 @@ class NavigationBarAnimatedTitle extends Component<NavigationBarAnimatedTitlePro } } +type NavigationBarProps = { + scrollTop: number, + children?: React.Node, +}; + type NavigationBarState = { titleAdjustment: number, + showsBarSeparator: boolean, + showsBarTitle: boolean, }; -export class NavigationBar extends Component<{}, NavigationBarState> { +/* $FlowFixMe: React.forwardRef is not supported yet by Flow. + See: https://github.com/facebook/flow/issues/6103 */ +export const NavigationBar = React.forwardRef(function NavigationBar(props, ref) { + return ( + <NavigationScrollContext.Consumer> + {(context) => ( + <PrivateNavigationBar ref={ref} scrollTop={context.scrollTop}> + {props.children} + </PrivateNavigationBar> + )} + </NavigationScrollContext.Consumer> + ); +}); + +class PrivateNavigationBar extends Component<NavigationBarProps, NavigationBarState> { + static defaultProps = { + scrollTop: 0, + }; + state = { titleAdjustment: 0, + showsBarSeparator: false, + showsBarTitle: false, }; _titleViewRef = React.createRef(); + static getDerivedStateFromProps(props, state) { + const showsBarSeparator = PrivateNavigationBar._shouldShowBarSeparator(props.scrollTop); + const showsBarTitle = PrivateNavigationBar._shouldShowNavigationTitle(props.scrollTop); + + return { + ...state, + showsBarSeparator, + showsBarTitle, + }; + } + + shouldComponentUpdate(nextProps: NavigationBarProps, nextState: NavigationBarState) { + return ( + this.props.children !== nextProps.children || + this.state.titleAdjustment !== nextState.titleAdjustment || + this.state.showsBarSeparator !== nextState.showsBarSeparator || + this.state.showsBarTitle !== nextState.showsBarTitle + ); + } + + render() { + return ( + <View + style={[ + styles.navigationBar.default, + this.state.showsBarSeparator && styles.navigationBar.separator, + styles.navigationBar[process.platform], + ]} + onLayout={this._onLayout}> + {React.Children.map(this.props.children, (element) => { + if (element.type === TitleBarItem) { + return ( + <PrivateTitleBarItemContainer + titleAdjustment={this.state.titleAdjustment} + visible={this.state.showsBarTitle} + ref={this._titleViewRef}> + {element} + </PrivateTitleBarItemContainer> + ); + } else { + return <View>{element}</View>; + } + })} + </View> + ); + } + + static _shouldShowBarSeparator(scrollTop: number): boolean { + // that's where SettingsHeader.HeaderTitle intersects the navigation bar + return scrollTop > 11; + } + + static _shouldShowNavigationTitle(scrollTop: number): boolean { + // that's when SettingsHeader.HeaderTitle goes behind the navigation bar + return scrollTop > 30; + } + _onLayout = async (containerLayout) => { const titleView = this._titleViewRef.current; if (titleView) { @@ -250,47 +334,6 @@ export class NavigationBar extends Component<{}, NavigationBarState> { }); } }; - - render() { - return ( - <NavigationScrollContext.Consumer> - {(context) => ( - <View - style={[ - styles.navigationBar.default, - this._shouldShowBarSeparator(context.scrollTop) && styles.navigationBar.separator, - styles.navigationBar[process.platform], - ]} - onLayout={this._onLayout}> - {React.Children.map(this.props.children, (element) => { - if (element.type === TitleBarItem) { - return ( - <NavigationBarTitle - titleAdjustment={this.state.titleAdjustment} - visible={this._shouldShowNavigationTitle(context.scrollTop)} - ref={this._titleViewRef}> - {element} - </NavigationBarTitle> - ); - } else { - return <View>{element}</View>; - } - })} - </View> - )} - </NavigationScrollContext.Consumer> - ); - } - - _shouldShowBarSeparator(scrollTop: number): boolean { - // that's where SettingsHeader.HeaderTitle intersects the navigation bar - return scrollTop > 11; - } - - _shouldShowNavigationTitle(scrollTop: number): boolean { - // that's when SettingsHeader.HeaderTitle goes behind the navigation bar - return scrollTop > 30; - } } export class TitleBarItem extends Component { |
