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

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

本篇梳理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 (
     : undefined
      }
      style={options.headerStyle}
    />
  );
};

(2) headerTitle配置示例

function LogoTitle(props) {
  return (
    <>
      
      {props.props.title}
    
  );
}
function StackScreen() {
  const title = '自定义标题' ;
  // 这边标题一般都是我们通过获取路由参数再经过方法判断确定的,这里写死为了方便演示如何把title额外传给自定义组件。
  return (
    
        }}
      />
    
  );
}

(3) headerLeft示例

import { HeaderBackButton } from '@react-navigation/stack';
 (
       {
          // Do something
        }}
      />
    ),
  }}
/>;

二、navigation属性及方法(Actions)

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

比如我们有个导航屏幕:

  • 通用方法

    • navigate - 导航到特定路由,参数必须包含name或key属性,可选参数params(合并到目标路由的参数)。通俗理解就是转到这个name/key对应的路由。如果正在当前路由屏幕组件页面,那么是不会有任何反应的。
      格式:navigation.navigate(name: string, [params: object])
    navigation.navigate( 'Profile',  { user: 'jane' })
    
    • goBack - 返回导航堆栈历史记录的上一个屏幕。navigation.goBack()
    • reset - 可以让我们用新的导航状态替换当前的导航状态,即移除现有的已经入栈的屏幕和历史记录,设置新的入栈屏幕们。如果希望在更改状态时保留现有的屏幕,可以使用CommonActions.set结合navigation.dispatch。像这样:
    import { CommonActions } from '@react-navigation/native';
    navigation.dispatch(
      CommonActions.reset({
        index: 1,
        routes: [
          { name: 'Home' },
          {
            name: 'Profile',
            params: { user: 'jane' },
          },
        ],
      })
    );
    
    • setParams - 更新当前特定路由的参数。 作用就像React的setState,传入的参数和旧的params合并对象而非覆盖。
      如果要为特定路由更新参数,则可添加source属性,值为该路由的key。
    import { CommonActions } from '@react-navigation/native';
    
    navigation.dispatch({
      ...CommonActions.setParams({ user: 'Wojtek' }),
      source: route.key,
    });
    
    • setOptions 可以在屏幕组件内部,根据它的props、state、context,来定制我们的屏幕组件选项(screen options),比如title等。
  • 堆栈导航器

    • replace - 用新路由替换导航状态navigation state中当前或指定的路由。以下是替换state中特定路由的示例:
    import { StackActions } from '@react-navigation/native';
    navigation.dispatch({
      ...StackActions.replace('Profile', { // 要新替换上的路由name
        user: 'jane',
      }),
      source: route.key, // 要被替换的路由的key
      target: navigation.dangerouslyGetState().key, // 要新替换上的路由的key
    });  
    
    • push - 添加一条新路由到导航堆栈顶部。格式同navigate
      和调用navigate的区别是,navigate会先尝试查找具有目标name的现有路由并跳转过去,并且仅在堆栈中还没有这个路由时才推送新路由。而push在当目标路由已经存在于导航堆栈时,仍然会推送新路由,因此一个路由可以多次访问(形成多条历史记录)。
    • pop - 默认回到导航栈历史记录的上一步。有一个可选参数(count),允许你指定弹出多少个屏幕。navigation.pop(count: number)
    • popToTop - 返回堆栈中的第一个屏幕,关闭所有其他屏幕。navigation.popToTop()
  • tab选项卡导航器

    • jumpTo - 跳转至tab导航器中的现有路由。
      格式:navigation.jumpTo(name: string, [params: object])
  • drawer抽屉导航器

    • jumpTo - 跳转至drawer导航器中的现有路由。格式同tab导航器的jumpTo
    • openDrawer - 打开drawer导航器面板。格式:navigation.openDrawer()
    • closeDrawer - 关闭drawer面板。格式:navigation.closeDrawer()
    • toggleDrawer - 切换drawer面板开关状态。格式:navigation.toggleDrawer()
  • 高级API参考

    • dispatch - dispatch方法允许我们发送一个导航动作对象(包含用于生成特定基于某类型导航器的操作方法),来确定导航状态如何更新。除非实在无法直接通过navigate,goBack等方法完成我们所需的操作。不然应该避免使用它。尽量都通过navigation.[普通方法]属性来导航。
      dispatch可调度的对象除了CommonActions,还有StackActions 、还有DrawerActions 、还有TabActions 。这仨都扩展于CommonActions。
// 要先获取特定的导航动作创造器
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,
};

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

  • type-这个导航状态归属的导航器的类型,例如stacktabdrawer
  • key -识别导航器的唯一键。
  • routeName-包含所属导航器的每个屏幕name(字符串)的数组。
  • routes-在导航器中呈现的路由对象(屏幕)的列表。它还在堆栈导航器中表示历史记录。此数组中至少应存在一项。
  • index-正获得焦点的路由对象在routes数组中的索引。
  • history-访问过的项目列表。这是一个可选属性,并非在所有导航器中都存在。比如它仅存在于核心的tab和抽屉导航器中。history数组中的项目可以根据导航器而变化。此数组中至少应存在一项。
  • stale-除非显式设置了stale属性,否则值默认是false。也就表示导航状态对象需要“自动补齐”。

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

  • key-屏幕的唯一键。会自动创建或在导航到此屏幕时添加。
  • name-屏幕名称。在导航器组件层次结构中定义。
  • params-可选,一个包含参数的对象,有导航动作时定义,例如navigate('Home', { sortBy: 'latest' })
  • state -可选,嵌套在此屏幕内的子导航器的导航状态对象。只会在导航事件发生后才挂到路由对象上。

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

四、设计合理的导航结构

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

首页为底部tab栏的典型嵌套结构 (下面五、(3)还会用这个例子举证)
function HomeTabs() { 
  return (
    
      
      
    
  );
}
function App() {
  return (
    
      
      
      
    
  );
}

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

function Root() {
  return (
    
      
      
    
  );
}
function App() {
  return (
    
      
        
        
      
    
  );
}

如果我们想从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

  {Object.entries({
    // Use the screens normally
    ...commonScreens,
    // Use some screens conditionally based on some condition
    ...(isLoggedIn ? userScreens : authScreens),
  }).map(([name, component]) => (
    
  ))}
;

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

(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标题下方。这是在决定如何嵌套导航器时要考虑的一个要点。

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

  • 在根stack导航器的首屏嵌套tab导航器——当你通过push跳转页面的时候,新的页面会覆盖掉标签栏。
  • 在drawer导航器的每个页面嵌套stack导航器----即先渲染抽屉效果再渲染stack导航器的头部
  • tab导航器的每个页面都嵌套stack导航器----tab导航器的标签栏仍然可见。常见的就是点击tab将stack置顶。
    详细官方说明:Nesting navigators

你可能感兴趣的:(React Navigation 5.x(一)常用知识点梳理)