React Navigation 5.x(一)常用知识点梳理

2021-01-09  本文已影响0人  AizawaSayo

为了阅读体验,分为上下两篇。算不上教程,主要目的还是摘取使用这个库时的常用知识点和解决方案,便于自己记忆和查阅。

本篇梳理React Navigation 5.x 的一些基础API、嵌套导航注意事项、如何设计合理的嵌套路由等。
第二篇主要讲如何实现我自身App的两个需求:1、在嵌套路由中动态配置顶部标题栏(tab导航器嵌套在根部stack导航器的首屏);2、监听tab点击事件,触发时将对应screen重置到初始状态(数据)。会把自己的代码结构放出来。React Navigation 5.x(二)嵌套路由动态配置标题栏及自定义tabbar点击事件

循序渐进,我们先看一些干货,最后再来实现这两个功能。

一、堆栈导航器Navigator和Screen的具体参数配置及说明

这边只列举比较实用的。如有需要可以去官网查阅API:createStackNavigator

1. 导航器组件Navigator常用参数(Props):
参数名 说明
initialRouteName 导航器初次加载时要渲染的路由的名字,对应Screen(屏幕组件)的name,默认渲染该栈内第一个Sreen
screenOptions 为该栈内所有Sreen配置通用属性,即可把Screen的options里的属性提到这里统一设置。当这些Screen有相同的属性时,没有必要复制多份并在它们的options上重复设置。相同的属性,Screen的options里配置的优先级更高。
keyboardHandlingEnabled 默认值为true,若设置为false,屏幕上的键盘将不会在导航到新屏幕时自动消失。
headerMode 设置该栈标题栏的形式 'float''(ios模式)|'screen'(Android模式)|'none' (不显示头部标题栏)
2. 导航器内的屏幕组件Screen常用参数(统一在options里配置):
参数名 类型 说明
title String Screen标题栏的文字
header Function 返回一个React Element作为自定义标题栏。要使用这个配置,必须先确保设置Navigator的headerMode:'screen',以及定义好标题栏高度,e.g. headerStyle: { height: 80 } 参考示例(1)
headerTitle String | Function 如果是函数,返回一个接收参数的React Element,作为自定义标题栏文字组件。 参考示例(2)
headerShown Boolean 屏幕的标题栏显示与否,在父Navigator没有设置headerMode: 'none'的情况下,默认是true
headerTitleAlign Boolean 屏幕标题栏文字的对齐方式,可选值:leftcenter,未设置时,iOS默认居中,Android则靠左。
headerRight Function 返回一个React Element以自定义标题栏的右侧。
headerLeft Function 返回一个React Element以自定义标题栏的左侧。默认使用HeaderBackButton组件,你可以使用它来覆盖后退按钮,参考示例(3):
headerStyle Object 标题栏样式,如背景颜色等
headerTitleStyle Object 标题栏文字组件(headerTitle)样式
headerTintColor String 标题栏文字颜色

(1) header配置示例

header: ({ scene, previous, navigation }) => {
  const { options } = scene.descriptor;
  const title =
    options.headerTitle !== undefined
      ? options.headerTitle
      : options.title !== undefined
      ? options.title
      : scene.route.name;
  return (
    <MyHeader
      title={title}
      leftButton={
        previous ? <MyBackButton onPress={navigation.goBack} /> : undefined
      }
      style={options.headerStyle}
    />
  );
};

(2) headerTitle配置示例

function LogoTitle(props) {
  return (
    <>
      <Image
        style={{ width: 50, height: 50 }}
        source={require('@expo/snack-static/react-native-logo.png')}
      />
      <Text>{props.props.title}</Text>
    </>
  );
}
function StackScreen() {
  const title = '自定义标题' ;
  // 这边标题一般都是我们通过获取路由参数再经过方法判断确定的,这里写死为了方便演示如何把title额外传给自定义组件。
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ headerTitle: props => <LogoTitle props={{...props, title}} /> }}
      />
    </Stack.Navigator>
  );
}

(3) headerLeft示例

import { HeaderBackButton } from '@react-navigation/stack';
<Screen
  name="Home"
  component={HomeScreen}
  options={{
    headerLeft: (props) => (
      <HeaderBackButton
        {...props}
        onPress={() => {
          // Do something
        }}
      />
    ),
  }}
/>;

二、navigation属性及方法(Actions)

App里的每个screen组件都能通过props接收到一个navigation属性,它包含了各种调度导航动作的便利功能/方法。不同的导航器接收到的navigation能执行的方法是有区别的:
这边只展开讲了部分方法,每个蓝色Action都加了API直达链接。

比如我们有个导航屏幕:
<Stack.Screen key="Profile-1" name="Profile" component={component} />

// 要先获取特定的导航动作创造器
import { CommonActions } from '@react-navigation/native';

navigation.dispatch(
  // 再去触发方法
  CommonActions.navigate({
    name: 'Profile',
    params: {
      user: 'jane',
    },
  })
);

三、导航状态Navigation state

navigation state是React Navigation存储应用程序的路由结构和历史记录的对象。
比如,在主屏幕嵌套了一个标签导航器的堆栈导航器,可能具有如下导航状态:

const state = {
  type: 'stack',
  key: 'stack-1',
  routeNames: ['Home', 'Profile', 'Settings'],
  routes: [
    {
      key: 'home-1',
      name: 'Home',
      state: {
        key: 'tab-1',
        routeNames: ['Feed', 'Library', 'Favorites'],
        routes: [
          { key: 'feed-1', name: 'Feed', params: { sortBy: 'latest' } },
          { key: 'library-1', name: 'Library' },
          { key: 'favorites-1', name: 'Favorites' },
        ],
        index: 0,
      },
    },
    { key: 'settings-1', name: 'Settings' },
  ],
  index: 1,
};

每个导航状态对象中包含的属性:

routes数组中的每个路由对象(route)都可以包含以下属性:

注:每个screen组件的props.route,就是上面说的routes数组中的路由对象,内容为这个屏幕的路由数据。

四、设计合理的导航结构

嵌套导航器就是在一个Navigator的一个Screen里渲染的Navigator,作为一个组件元素赋给Screen的component属性。
一个应用通常都拥有底部选项卡(tabbar),一般是主页的标准配置。同时应用中的部分页面(比如登录页等)不需要tabbar。要实现这一点,能从导航结构入手就不要去动态设置隐藏/显示tabbar。最简单的方法是将选项卡导航器嵌套在堆栈导航器的第一个屏幕中,将不需要tabbar的Screen放在这个屏幕后面。
像下面的例子:一个tab导航器就被嵌套在stack导航器里。

首页为底部tab栏的典型嵌套结构 (下面五、(3)还会用这个例子举证)
function HomeTabs() { 
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={Home} />
      <Tab.Screen name="Feed" component={Feed} />
    </Tab.Navigator>
  );
}
function App() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeTabs} />
      <Stack.Screen name="Profile" component={Profile} />
      <Stack.Screen name="Settings" component={Settings} />
    </Stack.Navigator>
  );
}

另外一种应用中常见的导航模式,把stack导航器嵌套在drawer导航器的每个屏幕中

function Root() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Profile" component={Profile} />
      <Stack.Screen name="Settings" component={Settings} />
    </Stack.Navigator>
  );
}
function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator>
        <Drawer.Screen name="Home" component={Home} />
        <Drawer.Screen name="Root" component={Root} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}

如果我们想从Home导航到Root,这样操作:navigation.navigate('Root'); Root的初始屏幕即Profile就会展示。如果你想展示的是Settings这一屏,就需要这样做:navigation.navigate('Root', { screen: 'Settings' }); 如果你要带参数跳转,现在就得这样做:

navigation.navigate('Root', {
  screen: 'Settings',
  params: { user: 'jane' },
});

五、保持嵌套导航器定义的初始路由不变(initialRouteName的值)

当你指定导航到嵌套导航器的某一屏时(navigation.navigate('Root', { screen: 'Settings' });),导航器定义初始路由就会被替换成这一屏,也就是说,下次直接导航这个嵌套导航器的时候(navigation.navigate('Root'); ),会默认显示这个Screen(Settings)。
如果不想初始路由被改变,我们就要在跳转的时候加一个initial: false,,如下:

navigation.navigate('Root', {
  screen: 'Settings',
  initial: false,
});

六、尽量避免深度嵌套

在能实现需求的基础上,请尽可能地少嵌套导航栈,建议层数最多不要超过两层。因为这会有很多副作用。
比如会引起低端设备的内存和性能问题。
影响代码可读性,过于冗余复杂变得难以维护。
Tab里再放Tab,Drawer里再放Drawer,会带来不好的用户体验。
如果你为了代码逻辑更清晰,想为Navigtor下的Screen分类,可以考虑像这样做:

// Define multiple groups of screens in objects like this
const commonScreens = {
  Help: HelpScreen,
};

const authScreens = {
  SignIn: SignInScreen,
  SignUp: SignUpScreen,
};

const userScreens = {
  Home: HomeScreen,
  Profile: ProfileScreen,
};

// Then use them in your components by looping over the object and creating screen configs
// You could extract this logic to a utility function and reuse it to simplify your code
<Stack.Navigator>
  {Object.entries({
    // Use the screens normally
    ...commonScreens,
    // Use some screens conditionally based on some condition
    ...(isLoggedIn ? userScreens : authScreens),
  }).map(([name, component]) => (
    <Stack.Screen name={name} component={component} />
  ))}
</Stack.Navigator>;

七、使用嵌套导航器,其他要注意并弄清的点

(1) 每个导航器保管它自己的导航历史

比如,当你在一个被嵌套在Screen里的堆栈导航器上点击返回按钮的时候,它会返回到本导航器(就是被嵌套的stack导航器)导航历史中的上一页,而不是返回到上级导航器中。

(2) 每个导航器中的屏幕有它自己的参数

比如,传递给嵌套导航器中的screen的任何参数都在该屏幕的route prop中,且不能被它的父或子导航器中的屏幕访问。
如果要从子屏幕访问父屏幕的参数,可以使用React Context将参数暴露给子屏幕。

(3) 导航action会优先由当前导航器处理,如果当前导航器不能处理则通过冒泡的方式由上一级导航器处理

比如,你在一个被嵌套的导航器的screen中调用navigation.goBack(),那么只有当你在该导航器的首页时你才会返回到父导航器中。其他的action像navigate工作原理相同。也就是说,只有当被嵌套的导航器不能处理这个action时,父导航器才会试图去处理它。
在上面的例子(首页为底部tab栏的典型嵌套结构)中,当你在Feed页调用navigate('Messages'),嵌套的tab导航器会处理这个action,但当你在这里调用navigate('Settings'),就会由它的父导航器来处理了。

(4) 导航器的一些特定方法可以在子导航器中使用

比如,如果一个stack导航器嵌套在drawer导航器中,那么drawer导航器的openDrawercloseDrawertoggleDrawer等方法在被嵌套的stack导航器传递给屏幕的navigation属性中依然是可用的。但是如果stack导航器是drawer的父导航器,那么它里面的screen是不能访问这些方法的,因为它没有被嵌套在drawer导航器里。
同样,如果一个tab导航器被嵌套在stack导航器中,那么tab导航器screen中的navigation属性会新得到pushreplace这两个方法。

如果你想从父导航器中分派动作给嵌套的子导航器,可以使用 navigation.dispatch
具体语句:navigation.dispatch(DrawerActions.toggleDrawer());

(5) 被嵌套的导航器不会响应父级导航器的事件

比如说,你有一个嵌套在tab导航器中的stack导航器,那么stack导航器的screen在用navigation.addListener绑定监听事件时,不能接收到由父tab导航器触发出的事件,比如tabPress。为了能够响应父导航器的事件,你可以用navigation.dangerouslyGetParent().addListener来显式地监听父级导航器事件。

useEffect(() => {
  const unsubscribe = navigation
    .dangerouslyGetParent()
    .addListener('tabPress', (e) => {
      // ...
  });
  return unsubscribe;
}, [navigation]);
(6) 父级导航器的UI先于子导航器被渲染

例如,将stack导航器嵌套在drawer导航器内部时,你会看到drawer显示在stack导航器标题的上方。但是如果将drawer导航器嵌套在stack导航器中,则drawer将出现在stack标题下方。这是在决定如何嵌套导航器时要考虑的一个要点。

在开发应用时,你可能会根据需求来选用下面这些模式:

上一篇下一篇

猜你喜欢

热点阅读