最新消息:看到那些跳动的图片、文字了吗?点击点击 O(∩_∩)O~~

React Navigation 5.x 试用体验 01

若思若想 onlyling 2620浏览

年初做了 React Native 的 Android 小应用,里面使用到了 React Navigation 4.x,完成后 2、3 个月 React Navigation 发布了 5.x,又过了小半年才来尝鲜。

首先接触到最大的改变是路由配置不再是函数创建的,而是组件化,有点像 React Route 3.x 到 4.x 的改变。

// 4.x 创建路由
const defaultConfig = (s: any) => {
  return {
    screen: s,
    navigationOptions: {
      headerShown: false,
    },
  };
};

export default createStackNavigator(
  {
    [BOTTOM_TAB_KEY]: {
      screen: TabContainer,
      navigationOptions: {
        headerShown: false,
      },
    },
    [RouteKeyMap.myApp]: defaultConfig(PageMyApp),
  }
)

// 5.x 创建路由
const NestingNavigators: React.FC = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home" headerMode="none">
        <Stack.Screen name="Home" component={TabsView} />

        <Stack.Screen name="List" component={ListView} />

        <Stack.Screen name="Details" component={DetailsView} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

export default NestingNavigators;

新版本在做沉浸式布局更容易了。

在 4.x 的时候,组件内通过 StatusBar 可以修改状态栏的背景颜色、文字颜色等,但是在路由嵌套/层级多了,多个状态栏的样式就会有问题。

例如: a(红色) -> b(蓝色) -> c(灰色),从 a 到 c 一层一层进入,状态栏显示正常,但是一层一层返回,状态栏只会显示最新(c 灰色)的那个。在 BottomTab 的页面直接切换,也是同样的状况。

在原文档中也给出了对应的方案,通过手动监听组件 didFocus 事件,手动设置/重置状态栏。当时我们写了一个高阶组件处理这个问题,ILayout.widthStatusBar({ full: true, backgroundColor: 'transparent'})(Home)

import { View, StatusBar, StatusBarStyle } from 'react-native';

interface WidthStatusBarParam {
  /** 状态栏的背景色 */
  backgroundColor?: string;
  /** 设置状态栏文本的颜色。 */
  barStyle?: StatusBarStyle;
  /** 是否隐藏状态栏。 */
  hidden?: boolean;
  /** 全屏 */
  full?: boolean;
  /** 背景颜色 */
  pageBackgroundColor?: string;
}

/**
 * 设置状态栏
 * @param barStyle 设置状态栏文本的颜色。
 * @param backgroundColor 状态栏的背景色
 * @param hidden 是否隐藏状态栏。
 */
export const setBarStyle = (barStyle: StatusBarStyle, backgroundColor: string, hidden: boolean) => {
  StatusBar.setBarStyle(barStyle as StatusBarStyle);
  StatusBar.setHidden(hidden);

  if (Helpers.iOS) {
    StatusBar.setNetworkActivityIndicatorVisible(true);
  } else {
    StatusBar.setTranslucent(true);
    StatusBar.setBackgroundColor(backgroundColor);
  }
};

/**
 * 设置当前页面的状态栏
 * @param {Object} statusbarProps 参数
 * @param {string} statusbarProps.backgroundColor 状态栏背景色
 *
 * @example widthStatusBar({})(YourNode);
 */
const widthStatusBar = ({
  backgroundColor = Configs.Colors.primary,
  barStyle = 'light-content',
  hidden = false,
  full = false,
  pageBackgroundColor = Configs.Colors.pageBackgroundColor,
}: WidthStatusBarParam) => <T, P, C>(WrappedComponent: React.ComponentType<T>) => {
  type Props = T & NavigationStackScreenProps;

  return class WidthStatusBar extends React.PureComponent<Props, P, C> {
    _navListener!: NavigationEventSubscription;

    constructor(props: Props) {
      super(props);
      this._navListener = props.navigation.addListener('willFocus', () => {
        setBarStyle(barStyle, 'transparent', hidden);
      });
    }
    componentWillUnmount() {
      this._navListener.remove();
    }

    render() {
      // TODO 优化 iOS SafeAreaView
      return (
        <View style={{ flex: 1, backgroundColor: pageBackgroundColor }}>
          <View
            style={{ height: full ? 0 : StatusBar.currentHeight, backgroundColor: backgroundColor }}
          />
          <WrappedComponent {...this.props} />
        </View>
      );
    }
  };
};

export default widthStatusBar;

现在嵌套中已经能显示正常了,但是 BottomTab 还是有点小问题,不过现在已经不需要这样麻烦了。

import React from 'react';
import { StatusBar, StatusBarProps } from 'react-native';
import { useIsFocused } from '@react-navigation/native';

const FocusAwareStatusBar: React.FC<StatusBarProps> = (props) => {
  const isFocused = useIsFocused();

  return isFocused ? <StatusBar {...props} /> : null;
};

export default FocusAwareStatusBar;

使用这个组件轻松搞定。

文档:Different status bar configuration based on route

不过,目前最让我感到有趣的是对 TypeScript 的支持越来越好了。

以前在路由组件中需要从 route 拿值需要针对单个组件进行 NavigationStackScreenProps 配置,但是在 navigation.push 的时候并不知道具体有哪些参数。

现在通过配置一个根组件状态,就能实现在 navigation.push 提示可以跳转到哪个路由,并且必填的参数也会提醒。

import React from 'react';
import { NavigationContainer, CompositeNavigationProp } from '@react-navigation/native';
import {
  createStackNavigator,
  StackScreenProps,
  StackNavigationProp,
} from '@react-navigation/stack';
import { BottomTabNavigationProp as BottomTabNavigationPropOriginal } from '@react-navigation/bottom-tabs';

import ListView from '@~/pages/list/list';
import DetailsView from '@~/pages/details/details';

import TabsView, { BottomTabParamList } from './bottom-tab';

/** 当前所有 Stack 路由的参数 */
type RootStackParamList = {
  Home: undefined;
  List: undefined;
  Details: {
    id: number;
  };
};

/** Stack 路由的 props */
export type RootStackScreenProps<T extends keyof RootStackParamList> = StackScreenProps<
  RootStackParamList,
  T
>;

/** BottomTab 路由的 navigation prop */
export type BottomTabNavigationProp<T extends keyof BottomTabParamList> = CompositeNavigationProp<
  BottomTabNavigationPropOriginal<BottomTabParamList, T>,
  StackNavigationProp<RootStackParamList>
>;

/** BottomTab 路由的 props */
export type BottomTabScreenProps<T extends keyof BottomTabParamList> = {
  navigation: BottomTabNavigationProp<T>;
};

const Stack = createStackNavigator<RootStackParamList>();

const NestingNavigators: React.FC = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home" headerMode="none">
        <Stack.Screen name="Home" component={TabsView} />

        <Stack.Screen name="List" component={ListView} />

        <Stack.Screen name="Details" component={DetailsView} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

export default NestingNavigators;
import React from 'react';
import { View, Text } from 'react-native';

import FocusAwareStatusBar from '@~/components/focus-aware-status-bar/focus-aware-status-bar';
import * as Routes from '@~/routes';

type UserCenterProps = {} & Routes.BottomTabScreenProps<'UserCenter'>;

const UserCenter: React.FC<UserCenterProps> = ({ navigation }) => {
  return (
    <View>
      <FocusAwareStatusBar backgroundColor="#f99" />

      <View>
        <Text>UserCenter</Text>
      </View>

      <View>
        <Text
          onPress={() => {
            navigation.push('Details', {
              id: 3333,
            });
          }}
        >
          Go Details
        </Text>
      </View>
    </View>
  );
};

export default UserCenter;

文档:Type checking with TypeScript

到目前为止,体验已经比以前好太多了,单单路由跳转的代码提示就已经提升很多幸福感了。

转载请注明:OnlyLing - Web 前端开发者 » React Navigation 5.x 试用体验 01