React_Navigation中文文档

React Navigation

基础知识

Getting started

本文档的基础部分将介绍React Navigation最重要的方面。它应该涵盖足够多的内容,让您知道如何构建典型的小型移动应用程序,并为您提供深入了解React Navigation更高级部分所需的背景知识。

预备知识

如果您已经熟悉JavaScript、React和React Native,那么您将能够快速使用React Navigation!如果没有,我们强烈建议您先获得一些基本知识,完成后再来这里。

最低要求
  • react-native >= 0.63.0
  • expo >= 41 (if you use Expo)
  • typescript >= 4.1.0 (if you use TypeScript)
Hello React Navigation

在web浏览器中,您可以使用锚点()标记链接到不同的页面。当用户点击链接时,URL会被推送到浏览器历史堆栈中。当用户按下后退按钮时,浏览器会从历史堆栈的顶部弹出该项目,因此活动页面现在是以前访问过的页面。React Native不像web浏览器那样内置了全局历史堆栈的概念——这就是React Navigation进入故事的地方。
React Navigation的原生堆栈导航器为您的应用程序提供了一种在屏幕之间转换和管理导航历史的方式。如果你的应用程序只使用一个堆栈导航器,那么它在概念上类似于web浏览器处理导航状态的方式——当用户与应用程序交互时,你的应用会从导航堆栈中推送和弹出项目,这会导致用户看到不同的屏幕。这在web浏览器和React Navigation中的工作方式之间的一个关键区别是,React Navi导航的原生堆栈导航器提供了在堆栈中的路线之间导航时在Android和iOS上所期望的手势和动画。

让我们首先演示最常见的导航器createNativeStackNavigator

Installing the native stack navigator library

到目前为止,我们安装的库是导航器的构建块和共享基础,React Navigation中的每个导航器都位于自己的库中。要使用本机堆栈导航器,我们需要安装@react navigation/native stack:

npm install @react-navigation/native-stack

@react导航/本机堆栈取决于react本机屏幕和我们在入门中安装的其他库。如果您还没有安装这些,请转到该页面并按照安装说明进行操作。

Creating a native stack navigator

createNativeStackNavigator是一个函数,它返回一个包含两个属性的对象:Screen和Navigator。它们都是用于配置导航器的React组件。导航器应包含Screen元素作为其子元素,以定义路线的配置。
NavigationContainer是一个管理导航树并包含导航状态的组件。此组件必须包装所有导航器结构。通常,我们会在应用程序的根目录下呈现这个组件,通常是从app.js导出的组件。

// In App.js in a new project

import * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

function HomeScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
    </View>
  );
}

const Stack = createNativeStackNavigator();

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

export default App;

如果您运行此代码,您将看到一个屏幕,其中有一个空的导航栏和一个包含HomeScreen组件的灰色内容区域(如上所示)。您看到的导航栏和内容区域的样式是堆栈导航器的默认配置,我们稍后将了解如何配置这些样式。

Configuring the navigator

所有的路线配置都被指定为导航器的属性。我们没有向导航器传递任何属性,所以它只使用默认配置。
让我们在本机堆栈导航器中添加第二个屏幕,并将主屏幕配置为首先渲染:

function DetailsScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
    </View>
  );
}

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

现在,我们的堆栈有两个路由,一个Home路由和一个Details路由。可以使用屏幕组件指定路线。Screen组件接受一个与我们将用于导航的路线名称相对应的名称道具,以及一个与它将渲染的组件相对应的组件道具。

这里,Home路由对应于HomeScreen组件,Details路由对应于DetailsScreen组件。堆栈的初始路由是主路由。尝试将其更改为Details并重新加载应用程序(React Native的Fast Refresh不会像您所期望的那样更新initialRouteName中的更改),请注意,您现在将看到Details屏幕。然后将其更改回“主页”并再次重新加载。

指定选项

导航器中的每个屏幕都可以为导航器指定一些选项,例如要在标题中呈现的标题。这些选项可以在每个屏幕组件的选项道具中传递:

<Stack.Screen
  name="Home"
  component={HomeScreen}
  options={{ title: 'Overview' }}
/>

有时,我们会希望为导航器中的所有屏幕指定相同的选项。为此,我们可以将screenOptions道具传递给导航器。

传递附加属性

有时我们可能想把额外的属性传给屏幕。我们可以通过两种方法做到这一点:
使用React上下文并使用上下文提供程序包装导航器以将数据传递到屏幕(推荐)。
对屏幕使用渲染回调,而不是指定组件属性:

<Stack.Screen name="Home">
  {(props) => <HomeScreen {...props} extraData={someData} />}
</Stack.Screen>
总结

React Native没有像web浏览器那样内置用于导航的API。React Navigation为您提供了这一功能,以及iOS和Android在屏幕之间转换的手势和动画。
堆栈Navigator是一个组件,它将路由配置作为其子级,并为配置提供额外的道具,并呈现我们的内容。
每个堆栈。Screen组件采用一个名称道具,该名称道具指的是路由的名称,而组件道具则指定要为路由渲染的组件。这是两个必需的道具。
要指定堆栈中的初始路由,请提供initialRouteName作为导航器的道具。
要指定特定于屏幕的选项,我们可以将选项道具传递给Stack。Screen,对于常见选项,我们可以将screenOptions传递给Stack。Navigator

在屏幕之间移动

在上一节“Hello React Navigation”中,我们定义了一个具有两条路线(Home和Details)的堆栈导航器,但我们没有学会如何让用户从Home导航到Details(尽管我们确实学会了如何更改代码中的初始路线,但强迫用户克隆我们的存储库并更改我们代码中的路线以查看另一个屏幕可以说是人们能想象到的最糟糕的用户体验之一)。
如果这是一个网络浏览器,我们可以写这样的东西:

<a href="details.html">Go to Details</a>

另一种写法是

<a
  onClick={() => {
    window.location.href = 'details.html';
  }}
>
  Go to Details
</a>

我们将执行与后者类似的操作,但我们将使用传递到屏幕组件的导航道具,而不是使用window.location全局

导航到新屏幕
import * as React from 'react';
import { Button, View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

// ... other code from the previous section
将参数传递到路由

还记得我说过“以后我们谈论params时会有更多的内容!”吗?好吧,时间到了

既然我们知道了如何创建一个包含一些路由的堆栈导航器并在这些路由之间导航,那么让我们看看如何在导航到路由时将数据传递给它们

这个有两部分

将params作为navigation.navigation函数的第二个参数放入对象中,将其传递给路线:navigation.navigation(“RouteName”,{/params go here/})
读取屏幕组件中的参数:route.params。

我们建议您传递的参数是JSON可序列化的。这样,您将能够使用状态持久性,并且您的屏幕组件将具有实现深度链接的正确契约

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => {
          /* 1. Navigate to the Details route with params */
          navigation.navigate('Details', {
            itemId: 86,
            otherParam: 'anything you want here',
          });
        }}
      />
    </View>
  );
}

function DetailsScreen({ route, navigation }) {
  /* 2. Get the param */
  const { itemId, otherParam } = route.params;
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
      <Text>itemId: {JSON.stringify(itemId)}</Text>
      <Text>otherParam: {JSON.stringify(otherParam)}</Text>
      <Button
        title="Go to Details... again"
        onPress={() =>
          navigation.push('Details', {
            itemId: Math.floor(Math.random() * 100),
          })
        }
      />
      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      <Button title="Go back" onPress={() => navigation.goBack()} />
    </View>
  );
}
配置标题栏

我们已经看到了如何配置标题,但在转到其他选项之前,让我们再看一遍——重复是学习的关键!

设置标题

Screen组件接受选项属性,该属性是一个对象或返回一个对象的函数,该对象包含各种配置选项。我们用于标题的标题是title,如下面的示例所示。

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ title: 'My home' }}
      />
    </Stack.Navigator>
  );
}
在标题中使用参数

为了在标题中使用params,我们需要使屏幕的选项属性成为返回配置对象的函数。尝试在选项中使用this.props可能很诱人,但因为它是在组件呈现之前定义的,所以它不引用组件的实例,因此没有可用的道具。相反,如果我们将选项作为一个函数,那么React Navigation将用一个包含{Navigation,route}的对象来调用它——在这种情况下,我们所关心的只是路由,它与路由道具传递给屏幕道具的对象相同。您可能还记得,我们可以通过route.params获取params,因此我们在下面这样做是为了提取一个param并将其用作标题

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ title: 'My home' }}
      />
      <Stack.Screen
        name="Profile"
        component={ProfileScreen}
        options={({ route }) => ({ title: route.params.name })}
      />
    </Stack.Navigator>
  );
}

传递给options函数的参数是一个具有以下属性的对象

navigation-屏幕的导航属性。
route-屏幕的路由属性

我们只需要上面例子中的路由属性,但在某些情况下,您可能也想使用导航

使用setOptions更新选项

通常需要从安装的屏幕组件本身更新活动屏幕的选项配置。我们可以使用navigation.setOptions来完成此操作

/* Inside of render() of React class */
<Button
  title="Update the title"
  onPress={() => navigation.setOptions({ title: 'Updated!' })}
/>
调整页眉样式

自定义页眉样式时,有三个关键属性可供使用:headerStyle、headerIntColor和headerTitleStyle

headerStyle:将应用于包装页眉的视图的样式对象。如果你在上面设置backgroundColor,那将是你的标题的颜色。
headerIntColor:后退按钮和标题都使用此属性作为它们的颜色。在下面的示例中,我们将色调设置为白色(#fff),因此后退按钮和标题将为白色。
headerTitleStyle:如果我们想自定义标题的fontFamily、fontWeight和其他文本样式属性,我们可以使用它。

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{
          title: 'My home',
          headerStyle: {
            backgroundColor: '#f4511e',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
        }}
      />
    </Stack.Navigator>
  );
}

这里有几件事需要注意

  • 在iOS上,状态栏文本和图标是黑色的,在深色背景下看起来不太好。我们不会在这里讨论它,但您应该确保配置状态栏以适应您的屏幕颜色,如状态栏指南中所述。
  • 我们设置的配置仅适用于主屏幕;当我们导航到详细信息屏幕时,默认样式又回来了。我们现在将研究如何在屏幕之间共享选项
跨屏幕共享常用选项

在许多屏幕上,以类似的方式配置标题是很常见的。例如,您的公司品牌颜色可能是红色,因此您希望页眉背景颜色是红色,色调颜色是白色。方便的是,这些是我们在运行示例中使用的颜色,您会注意到,当您导航到DetailsScreen时,颜色会返回到默认值。如果我们必须将选项标题样式的属性从HomeScreen复制到DetailsScreen,以及我们在应用程序中使用的每个屏幕组件,这不是很糟糕吗?谢天谢地,我们没有。相反,我们可以将配置移动到道具屏幕Options下的本机堆栈导航器

function StackScreen() {
  return (
    <Stack.Navigator
      screenOptions={{
        headerStyle: {
          backgroundColor: '#f4511e',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
          fontWeight: 'bold',
        },
      }}
    >
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ title: 'My home' }}
      />
    </Stack.Navigator>
  );
}

现在,任何属于堆栈的屏幕。Navigator将拥有我们出色的品牌风格。当然,如果我们需要的话,必须有一种方法来推翻这些选择

用自定义组件替换标题

有时,您需要更多的控制权,而不仅仅是更改标题的文本和样式——例如,您可能想渲染一个图像来代替标题,或者将标题制作成一个按钮。在这些情况下,您可以完全覆盖用于标题的组件,并提供自己的

function LogoTitle() {
  return (
    <Image
      style={{ width: 50, height: 50 }}
      source={require('@expo/snack-static/react-native-logo.png')}
    />
  );
}

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ headerTitle: (props) => <LogoTitle {...props} /> }}
      />
    </Stack.Navigator>
  );
}

页眉自定义标题

您可能想知道,当我们提供组件而不是标题时,为什么要像以前那样使用headerTitle?原因是headerTitle是一个特定于页眉的属性,而title也将用于选项卡栏、抽屉等。headerTitle默认为显示标题的Text组件

附加配置

您可以在createNativeStackNavigator参考中阅读本机堆栈导航器内部屏幕的可用选项的完整列表

总结

您可以在屏幕组件的选项属性中自定义标题。阅读API参考资料中的完整选项列表。
选项属性可以是一个对象或一个函数。当它是一个函数时,它会被提供一个带有导航和路线道具的对象。
初始化堆栈导航器配置时,您也可以在该配置中指定共享屏幕选项。道具优先于该配置。

页眉按钮

既然我们知道了如何自定义我们的头的外观,让我们让它们有感知力!事实上,也许这是雄心勃勃的,让我们让他们能够以非常明确的方式对我们的触摸做出反应

向页眉添加按钮

与标题交互的最常见方式是点击标题左侧或右侧的按钮。让我们在标题的右侧添加一个按钮(根据手指和手机的大小,这是整个屏幕上最难触摸的地方之一,也是放置按钮的正常位置)。

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{
          headerTitle: (props) => <LogoTitle {...props} />,
          headerRight: () => (
            <Button
              onPress={() => alert('This is a button!')}
              title="Info"
              color="#fff"
            />
          ),
        }}
      />
    </Stack.Navigator>
  );
}

当我们以这种方式定义按钮时,选项中的this变量不是HomeScreen实例,因此您不能在其上调用setState或任何实例方法。这一点非常重要,因为希望页眉中的按钮与页眉所属的屏幕交互是非常常见的。因此,我们接下来将研究如何做到这一点

标头与其屏幕组件的交互

在某些情况下,标头中的组件需要与屏幕组件交互。对于这个用例,我们需要使用navigation.setOptions来更新我们的选项。通过在屏幕组件中使用navigation.setOptions,我们可以访问屏幕的道具、状态、上下文等

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={({ navigation, route }) => ({
          headerTitle: (props) => <LogoTitle {...props} />,
          // Add a placeholder button without the `onPress` to avoid flicker
          headerRight: () => <Button title="Update count" />,
        })}
      />
    </Stack.Navigator>
  );
}

function HomeScreen({ navigation }) {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    // Use `setOptions` to update the button that we previously specified
    // Now the button includes an `onPress` handler to update the count
    navigation.setOptions({
      headerRight: () => (
        <Button onPress={() => setCount((c) => c + 1)} title="Update count" />
      ),
    });
  }, [navigation]);

  return <Text>Count: {count}</Text>;
}

在这里,我们使用带有onPress处理程序的按钮更新headerRight,该处理程序可以访问组件的状态并可以更新它。

自定义后退按钮

createNativeStackNavigator为后退按钮提供了特定于平台的默认值。在iOS上,这包括按钮旁边的标签,当标题适合可用空间时,它会显示上一个屏幕的标题,否则会显示“返回”。
您可以使用headerBackTitle更改标签行为,并使用headerBackTitleStyle设置标签样式(阅读更多信息)。
要自定义后退按钮图像,可以使用headerBackImageSource(阅读更多)。

<Stack.Navigator>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen
    name="Details"
    component={DetailsScreen}
    options={{
      headerBackTitle: 'Custom Back',
      headerBackTitleStyle: { fontSize: 30 },
    }}
  />
</Stack.Navigator>
覆盖后退按钮

只要用户可以从当前屏幕返回,返回按钮就会在堆栈导航器中自动呈现——换句话说,只要堆栈中有多个屏幕,返回按钮就将呈现。
一般来说,这就是你想要的。但在某些情况下,您可能希望自定义后退按钮,而不是通过上述选项进行自定义,在这种情况下,可以将headerLeft选项设置为将被渲染的React元素,就像我们对headerRight所做的那样。或者,headerLeft选项也接受React Component,例如,它可以用于覆盖后退按钮的onPress行为。在api参考中阅读更多关于这方面的信息。

总结

您可以通过选项中的headerLeft和headerRight属性设置标题中的按钮。
返回按钮可通过headerLeft完全自定义,但如果您只想更改标题或图像,还有其他选项可供选择——headerBackTitle、headerBackTitleStyle和headerBackImageSource。
您可以使用选项道具的回调来访问导航和路线对象

嵌套导航器

例如,嵌套导航器意味着在另一个导航器的屏幕内呈现导航器

function Home() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={Feed} />
      <Tab.Screen name="Messages" component={Messages} />
    </Tab.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Home"
          component={Home}
          options={{ headerShown: false }}
        />
        <Stack.Screen name="Profile" component={Profile} />
        <Stack.Screen name="Settings" component={Settings} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

在上面的示例中,Home组件包含一个选项卡导航器。Home组件也用于应用程序组件内堆栈导航器中的Home屏幕。因此,在这里,选项卡导航器嵌套在堆栈导航器中

Stack.Navigator

Home (Tab.Navigator)

Feed (Screen)

Messages (Screen)

Profile (Screen)

Settings (Screen)

嵌套导航器的工作方式与嵌套常规组件非常相似。为了实现您想要的行为,通常需要嵌套多个导航器

嵌套导航器如何影响行为

当给航海家筑巢时,有一些事情需要记住

每个导航器都有自己的导航历史记录

例如,当您在嵌套堆栈导航器的屏幕内按下后退按钮时,即使有另一个导航器作为父导航器,它也会返回到嵌套堆栈内的上一个屏幕

每个导航器都有自己的选项

例如,在嵌套在子导航器中的屏幕中指定标题选项不会影响父导航器中显示的标题

如果要实现此行为,请参阅指南中的嵌套导航器屏幕选项。如果您正在堆栈导航器中呈现选项卡导航器,并且希望在堆栈导航器的标头中显示选项卡导航器中活动屏幕的标题,则这可能非常有用

导航器中的每个屏幕都有自己的参数

例如,在嵌套导航器中传递给屏幕的任何参数都在该屏幕的路由属性中,并且不能从父导航器或子导航器中的屏幕访问。
如果您需要从子屏幕访问父屏幕的params,可以使用React Context将params公开给子屏幕

导航操作由当前导航器处理,如果无法处理则弹出

例如,如果您在嵌套屏幕中调用navigation.goBack(),则只有当您已经在导航器的第一个屏幕上时,它才会返回到父导航器中。其他操作(如导航)的工作原理类似,即导航将发生在嵌套导航器中,如果嵌套导航器无法处理它,则父导航器将尝试处理它。在上面的示例中,当在Feed屏幕内调用navigation(‘Messages’)时,嵌套选项卡导航器将处理它,但如果您调用navigate(‘Settings’),则父堆栈导航器将进行处理。

嵌套在中的导航器中提供了特定于导航器的方法

例如,如果您在抽屉导航器中有一个堆栈,则抽屉的openDrawer、closeDrawer、toggleDrawer方法等也将在堆栈导航器中屏幕的导航道具上可用。但是假设您有一个堆栈导航器作为抽屉的父级,那么堆栈导航器中的屏幕将无法访问这些方法,因为它们没有嵌套在抽屉中

类似地,如果您在堆栈导航器中有一个选项卡导航器,则选项卡导航器中的屏幕将在其导航道具中获得堆栈的推送和替换方法

如果需要从父导航器向嵌套的子导航器分派操作,可以使用navigation.dispatch

navigation.dispatch(DrawerActions.toggleDrawer());

嵌套导航器不接收父级的事件

例如,如果在选项卡导航器中嵌套了堆栈导航器,则在使用navigation.addListener时,堆栈导航器中的屏幕将不会接收到父选项卡导航器发出的事件,例如(tabPress)

要从父导航器接收事件,可以使用navigation.getParent显式侦听父导航器的事件

const unsubscribe = navigation
  .getParent('MyTabs')
  .addListener('tabPress', (e) => {
    // Do something
  });

这里的“MyTabs”是指您在要侦听其事件的父Tab.Navigator的id属性中传递的值

父导航器的UI呈现在子导航器之上

例如,当您将堆栈导航器嵌套在抽屉导航器中时,您将看到抽屉显示在堆栈导航器的标头上方。但是,如果将抽屉导航器嵌套在堆栈中,则抽屉将显示在堆栈的标头下方。这是在决定如何安置领航员时需要考虑的一个重要问题。
在你的应用程序中,你可能会根据你想要的行为使用这些模式

选项卡导航器嵌套在堆栈导航器的初始屏幕中——当您按下新屏幕时,它们会覆盖选项卡栏。
抽屉导航器嵌套在堆栈导航器的初始屏幕内,初始屏幕的堆栈标头隐藏-抽屉只能从堆栈的第一个屏幕打开。
嵌套在抽屉导航器的每个屏幕内的堆栈导航器-抽屉显示在堆栈的页眉上方。
堆叠嵌套在选项卡导航器每个屏幕内的导航器-选项卡栏始终可见。通常再次按下选项卡也会将堆栈弹出到顶部

在嵌套导航器中导航到屏幕
function Root() {
  return (
    <Drawer.Navigator>
      <Drawer.Screen name="Home" component={Home} />
      <Drawer.Screen name="Profile" component={Profile} />
      <Stack.Screen name="Settings" component={Settings} />
    </Drawer.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Root"
          component={Root}
          options={{ headerShown: false }}
        />
        <Stack.Screen name="Feed" component={Feed} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

在这里,您可能需要从Feed组件导航到Root屏幕

navigation.navigate('Root');

它工作正常,并且显示根组件内部的初始屏幕,即主页。但有时您可能想要控制导航时应该显示的屏幕。为了实现这一点,您可以在params中传递屏幕名称

navigation.navigate('Root', { screen: 'Profile' });

现在,导航时将呈现“配置文件”屏幕,而不是“主页”。
这看起来可能与以前使用嵌套屏幕进行导航的方式大不相同。不同之处在于,在以前的版本中,所有配置都是静态的,因此React Navigation可以通过递归到嵌套配置中,静态地找到所有导航器及其屏幕的列表。但使用动态配置,React Navigation不知道哪些屏幕可用,以及在哪里可用,直到包含屏幕的导航器渲染为止。通常,在导航到屏幕之前,屏幕不会渲染其内容,因此尚未渲染的导航器的配置尚不可用。这使得有必要指定您要导航到的层次结构。这也是为什么您应该尽可能少地嵌套导航器以使代码更简单的原因

将参数传递到嵌套导航器中的屏幕

您也可以通过指定params键来传递params:

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

如果导航器已经呈现,则在堆栈导航器的情况下,导航到另一个屏幕将推送一个新屏幕。
对于深度嵌套的屏幕,可以采用类似的方法。请注意,在这里导航的第二个参数只是params,因此您可以执行以下操作

navigation.navigate('Root', {
  screen: 'Settings',
  params: {
    screen: 'Sound',
    params: {
      screen: 'Media',
    },
  },
});

在上述情况下,您将导航到媒体屏幕,该屏幕位于嵌套在声音屏幕内的导航器中,该导航器位于嵌套在设置屏幕内的导航器中

呈现导航器中定义的初始路线

默认情况下,当您在嵌套导航器中导航屏幕时,指定的屏幕将用作初始屏幕,导航器上的初始路线属性将被忽略。这种行为与React Navigation 4不同

如果需要渲染导航器中指定的初始路线,可以通过设置initial:false来禁用将指定屏幕用作初始屏幕的行为:

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

这会影响按下后退按钮时发生的情况。当出现初始屏幕时,后退按钮会将用户带到那里

嵌套多个导航器

嵌套多个导航器(如堆栈、抽屉或选项卡)有时很有用

当嵌套多个堆栈、抽屉或底部选项卡导航器时,将显示子导航器和父导航器的标题。然而,通常更希望在子导航器中显示标题,并在父导航器的屏幕中隐藏标题

为了实现这一点,您可以使用headerShown:false选项将标题隐藏在包含导航器的屏幕中

function Home() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Profile" component={Profile} />
      <Tab.Screen name="Settings" component={Settings} />
    </Tab.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Home"
          component={Home}
          options={{ headerShown: false }}
        />
        <Stack.Screen name="EditPost" component={EditPost} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

在这些示例中,我们使用了一个直接嵌套在另一个堆栈导航器中的底部选项卡导航器,但当中间有其他导航器时,同样的原理也适用,例如:位于另一个栈导航器内的选项卡导航器内,位于抽屉导航器内等

如果您不希望在任何导航程序中显示标题,可以在所有导航程序中指定headerShown:false

function Home() {
  return (
    <Tab.Navigator screenOptions={{ headerShown: false }}>
      <Tab.Screen name="Profile" component={Profile} />
      <Tab.Screen name="Settings" component={Settings} />
    </Tab.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{ headerShown: false }}>
        <Stack.Screen name="Home" component={Home} />
        <Stack.Screen name="EditPost" component={EditPost} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
嵌套时的最佳实践

我们建议将筑巢的导航仪减少到最低限度。尽可能少地嵌套以实现您想要的行为。嵌套有很多缺点

它会导致深度嵌套的视图层次结构,这可能会导致低端设备的内存和性能问题
嵌套相同类型的导航器(例如,选项卡内的选项卡、抽屉内的抽屉等)可能会导致用户体验混乱
由于嵌套过多,在导航到嵌套屏幕、配置深度链接等时,代码变得难以遵循

将嵌套导航器视为实现所需UI的一种方式,而不是组织代码的一种方法。如果要为组织创建单独的屏幕组,而不是使用单独的导航器,可以使用“组”组件

<Stack.Navigator>
  {isLoggedIn ? (
    // Screens for logged in users
    <Stack.Group>
      <Stack.Screen name="Home" component={Home} />
      <Stack.Screen name="Profile" component={Profile} />
    </Stack.Group>
  ) : (
    // Auth screens
    <Stack.Group screenOptions={{ headerShown: false }}>
      <Stack.Screen name="SignIn" component={SignIn} />
      <Stack.Screen name="SignUp" component={SignUp} />
    </Stack.Group>
  )}
  {/* Common modal screens */}
  <Stack.Group screenOptions={{ presentation: 'modal' }}>
    <Stack.Screen name="Help" component={Help} />
    <Stack.Screen name="Invite" component={Invite} />
  </Stack.Group>
</Stack.Navigator>

导航生命周期

在上一节中,我们使用了一个具有两个屏幕(主页和详细信息)的堆栈导航器,并学习了如何使用navigation.anavigation(“RouteName”)在路线之间导航

在这种情况下,一个重要的问题是:当我们离开首页时,或者当我们回到首页时,首页会发生什么?路由如何发现用户正在离开或返回

如果您是从web后台进行react导航的,您可能会认为,当用户从路径a导航到路径B时,a将卸载(调用其组件WillUnmount),当用户返回时a将再次装载。虽然这些react生命周期方法仍然有效,并在react导航中使用,但它们的用法与web不同。这是由更复杂的移动导航需求驱动的

示例场景

考虑一个具有屏幕A和B的堆栈导航器。导航到a后,调用其组件componentDidMount。当推送B时,它的componentDidMount也会被调用,但A仍然安装在堆栈上,因此它的componentWillUnmount不会被调用

当从B返回到A时,会调用B的componentWillUnmount,但不会调用A的componentDidMount,因为A一直保持挂载状态

与其他航海家也可以(结合)观察到相似的结果。考虑一个具有两个选项卡的选项卡导航器,其中每个选项卡都是堆栈导航器

function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="First">
          {() => (
            <SettingsStack.Navigator>
              <SettingsStack.Screen
                name="Settings"
                component={SettingsScreen}
              />
              <SettingsStack.Screen name="Profile" component={ProfileScreen} />
            </SettingsStack.Navigator>
          )}
        </Tab.Screen>
        <Tab.Screen name="Second">
          {() => (
            <HomeStack.Navigator>
              <HomeStack.Screen name="Home" component={HomeScreen} />
              <HomeStack.Screen name="Details" component={DetailsScreen} />
            </HomeStack.Navigator>
          )}
        </Tab.Screen>
      </Tab.Navigator>
    </NavigationContainer>
  );
}

我们从主屏幕开始,导航到细节屏幕。然后,我们使用选项卡栏切换到设置屏幕并导航到配置文件屏幕。完成此操作序列后,将安装所有4个屏幕!如果您使用选项卡栏切换回HomeStack,您会注意到您将看到DetailsScreen-HomeStack的导航状态已被保留

React导航生命周期事件

现在我们了解了React生命周期方法在React Navigation中的工作原理,让我们回答我们在开始时提出的问题:“我们如何发现用户正在离开(模糊)它或返回(焦点)它?”

React Navigation向订阅它们的组件发送事件。我们可以通过听聚焦和模糊事件来分别知道屏幕何时聚焦或失焦

function Profile({ navigation }) {
  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      // Screen was focused
      // Do something
    });

    return unsubscribe;
  }, [navigation]);

  return <ProfileContent />;
}

有关可用事件和API使用情况的更多详细信息,请参阅导航事件。
我们可以使用useFocusEffect挂钩来执行副作用,而不是手动添加事件侦听器。这就像React的useEffect挂钩,但它与导航生命周期有关。

import { useFocusEffect } from '@react-navigation/native';

function Profile() {
  useFocusEffect(
    React.useCallback(() => {
      // Do something when the screen is focused

      return () => {
        // Do something when the screen is unfocused
        // Useful for cleanup functions
      };
    }, [])
  );

  return <ProfileContent />;
}

如果你想根据屏幕是否聚焦来渲染不同的东西,你可以使用useIsFocused钩子,它返回一个布尔值,指示屏幕是否聚焦

总结

虽然React的生命周期方法仍然有效,但React Navigation添加了更多可以通过导航道具订阅的事件

您也可以使用useFocusEffect或useIsFocused挂钩

Next steps

现在,您已经熟悉了如何创建堆栈导航器、在屏幕组件上配置它、在路线之间导航和显示模式。堆栈导航器及其相关API将是React导航工具带中最常用的工具,但也有一些问题无法解决。例如,您不能使用堆栈导航器构建基于选项卡的导航——为此,您需要使用TabNavigator。
文档的其余部分是围绕特定用例组织的,因此您可以根据需要在“指南”下的各个部分之间跳转(但提前熟悉它们也不会对您造成伤害!)。
虽然大多数用户不需要这样做,但如果您很好奇并想了解更多关于React Navigation如何工作的信息,建议您浏览“构建自己的导航器”部分。
祝你好运

术语表

这是文档的新部分,缺少了很多术语!请提交一个撤回请求或一个你认为应该在这里解释的术语的问题

Navigator

Navigator是React组件,它决定如何渲染您定义的屏幕。它包含Screen元素作为其子元素,用于定义屏幕的配置

NavigationContainer是一个管理导航树并包含导航状态的组件。此组件必须包装所有导航器结构。通常,我们会在应用程序的根目录下呈现这个组件,它通常是从app.js导出的组件

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator> // <---- This is a Navigator
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
Router

路由器是决定如何处理导航器中的操作和状态更改的函数集合(类似于Redux应用程序中的减速器)。通常情况下,您永远不需要直接与路由器交互,除非您正在编写自定义导航器

Screen component

屏幕组件是我们在路线配置中使用的组件

const Stack = createNativeStackNavigator();

const StackNavigator = (
  <Stack.Navigator>
    <Stack.Screen
      name="Home"
      component={HomeScreen} // <----
    />
    <Stack.Screen
      name="Details"
      component={DetailsScreen} // <----
    />
  </Stack.Navigator>
);

组件名称中的后缀Screen完全是可选的,但却是一种常用的约定;我们可以称之为迈克尔,这也同样有效

我们之前看到,我们的屏幕组件提供了导航道具。需要注意的是,只有当React Navigation将屏幕渲染为路线时才会发生这种情况(例如,响应Navigation.Navigation)。例如,如果我们将DetailsScreen渲染为HomeScreen的子级,则DetailsScreet将不提供导航道具,并且当您按下主屏幕上的“Go to Details…again”按钮时,应用程序将抛出一个典型的JavaScript异常“undefined is not a object”

function HomeScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
      <DetailsScreen />
    </View>
  );
}

“导航道具参考”部分对此进行了更详细的介绍,介绍了解决方法,并提供了有关导航道具上其他可用属性的更多信息。

导航属性

这个属性将被传递到所有屏幕,它可以用于以下用途

dispatch : 调度将向路由器发送一个操作
navigate, goBack :等可用于以方便的方式调度操作

导航器也可以接受导航属性,如果有,他们应该从父导航器获得

有关更多详细信息,请参阅“导航道具文档”。

“路由道具参考”部分对此进行了更详细的介绍,介绍了解决方法,并提供了有关路由道具上其他可用属性的更多信息

Route Prop

此属性将传递到所有屏幕。包含有关当前路线的信息,即参数、密钥和名称

Navigation State

导航器的状态通常如下所示

{
  key: 'StackRouterRoot',
  index: 1,
  routes: [
    { key: 'A', name: 'Home' },
    { key: 'B', name: 'Profile' },
  ]
}

对于这种导航状态,有两条路线(可能是选项卡,也可能是堆栈中的卡片)。索引指示活动路由,该路由为“B”。

您可以在此处阅读有关导航状态的更多信息。

Route

每条路由都是一个对象,其中包含一个用于识别它的键和一个用于指定路线类型的“名称”。它也可以包含任意参数

{
  key: 'B',
  name: 'Profile',
  params: { id: '123' }
}
Header

也称为导航标题、导航栏、应用程序栏,可能还有许多其他东西。这是屏幕顶部的矩形,包含后退按钮和屏幕标题。在React Navigation中,整个矩形通常被称为标头

故障排除

本节试图概述用户在第一次习惯使用React Navigation时经常遇到的问题。这些问题可能与React Navigation本身有关,也可能与之无关。
在对问题进行故障排除之前,请确保已升级到软件包的最新可用版本。您可以通过重新安装软件包来安装最新版本(例如,npm安装软件包名称)。

I’m getting an error “Unable to resolve module” after updating to the latest version

这可能有三个原因:

Metro bundler的陈旧缓存

局限性

作为库的潜在用户,了解你能做什么和不能做什么很重要。有了这些知识,你可以选择采用不同的库,例如react native=navigation。我们在变桨和反变桨部分讨论了高级别的设计决策,在这里我们将介绍一些不受支持或难以实现的用例。如果以下任何限制对您的应用程序都是破坏交易的因素,React Navigation可能不适合您

有限的从右到左(RTL)布局支持

我们试图在React Navigation中正确处理RTL布局,但React Navigate的团队规模相当小,我们目前没有带宽或流程来测试RTL布局的所有更改。因此,您可能会遇到RTL布局的问题。
如果您喜欢React Navigation所提供的功能,但却被这一限制所拒绝,我们鼓励您参与进来并拥有RTL布局支持。请在推特上联系我们:@reactnavigation。

Guides 向导

Tab navigation 选项卡的导航

移动应用程序中最常见的导航方式可能是基于选项卡的导航。这可以是屏幕底部或页眉下方顶部的选项卡(甚至可以代替页眉)

本指南涵盖createBottomTabNavigator。您也可以使用createMaterialBottomTabNavigator和createMaterialTopTabNavigator将选项卡添加到应用程序中。

npm install @react-navigation/bottom-tabs
基于选项卡的导航的最小示例
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

function HomeScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Home!</Text>
    </View>
  );
}

function SettingsScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Settings!</Text>
    </View>
  );
}

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Settings" component={SettingsScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}
自定义外观

这与自定义堆栈导航器的方式类似——有些属性是在初始化选项卡导航器时设置的,有些属性可以在选项中按屏幕自定义。

// You can import Ionicons from @expo/vector-icons/Ionicons if you use Expo or
// react-native-vector-icons/Ionicons otherwise.
import Ionicons from 'react-native-vector-icons/Ionicons';

// (...)

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            let iconName;

            if (route.name === 'Home') {
              iconName = focused
                ? 'ios-information-circle'
                : 'ios-information-circle-outline';
            } else if (route.name === 'Settings') {
              iconName = focused ? 'ios-list' : 'ios-list-outline';
            }

            // You can return any component that you like here!
            return <Ionicons name={iconName} size={size} color={color} />;
          },
          tabBarActiveTintColor: 'tomato',
          tabBarInactiveTintColor: 'gray',
        })}
      >
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Settings" component={SettingsScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

让我们来剖析一下

tabBarIcon是底部选项卡导航器中支持的选项。所以我们知道我们可以在选项道具中的屏幕组件上使用它,但在这种情况下,为了方便起见,我们选择将它放在选项卡导航器的屏幕选项道具中,以集中图标配置。

tabBarIcon是一个给定聚焦状态、颜色和大小参数的函数。如果您在配置中进一步往下看,您将看到选项卡BarActiveTintColor和选项卡BarInactiveTintColor。这些默认值为iOS平台的默认值,但您可以在此处进行更改。传递到tabBarIcon的颜色是活动的还是非活动的,这取决于聚焦状态(聚焦即活动)。大小是选项卡栏所期望的图标的大小。

有关createBottomTabNavigator配置选项的更多信息,请阅读完整的API参考资料。

将徽章添加到图标

有时我们想在一些图标上添加徽章。您可以使用tabBarBadge选项来执行此操作

<Tab.Screen name="Home" component={HomeScreen} options={{ tabBarBadge: 3 }} />

从UI的角度来看,这个组件已经可以使用了,但您仍然需要找到一些方法来从其他地方正确地传递徽章计数,比如使用React Context、Redux、MobX或事件发射器

在选项卡之间跳转
function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Home!</Text>
      <Button
        title="Go to Settings"
        onPress={() => navigation.navigate('Settings')}
      />
    </View>
  );
}

function SettingsScreen({ navigation }) {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Settings!</Text>
      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
    </View>
  );
}
每个选项卡的堆栈导航器

通常,标签页不仅仅显示一个屏幕——例如,在你的推特订阅源上,你可以点击一条推文,它会将你带到标签页中的一个新屏幕,其中包含所有回复。您可以将其视为每个选项卡中都有单独的导航堆栈,这正是我们在React navigation中对其建模的方式

import * as React from 'react';
import { Button, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

function DetailsScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Details!</Text>
    </View>
  );
}

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Home screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

function SettingsScreen({ navigation }) {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Settings screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

const HomeStack = createNativeStackNavigator();

function HomeStackScreen() {
  return (
    <HomeStack.Navigator>
      <HomeStack.Screen name="Home" component={HomeScreen} />
      <HomeStack.Screen name="Details" component={DetailsScreen} />
    </HomeStack.Navigator>
  );
}

const SettingsStack = createNativeStackNavigator();

function SettingsStackScreen() {
  return (
    <SettingsStack.Navigator>
      <SettingsStack.Screen name="Settings" component={SettingsScreen} />
      <SettingsStack.Screen name="Details" component={DetailsScreen} />
    </SettingsStack.Navigator>
  );
}

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator screenOptions={{ headerShown: false }}>
        <Tab.Screen name="HomeStack" component={HomeStackScreen} />
        <Tab.Screen name="SettingsStack" component={SettingsStackScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}
为什么我们需要TabNavigator而不是TabBarIOS或其他组件?

尝试使用独立的选项卡栏组件而不将其集成到应用程序中使用的导航库中是很常见的。在某些情况下,这很有效!然而,应该提醒您,在这样做的时候,您可能会遇到一些令人沮丧的意外问题。
例如,React Navigation的选项卡导航器负责为您处理Android后退按钮,而独立组件通常不会。此外,如果你需要调用两个不同的API,你(作为开发人员)更难执行诸如“跳到这个选项卡,然后转到这个屏幕”之类的操作。最后,移动用户界面有许多小的设计细节,要求某些组件知道其他组件的布局或存在——例如,如果你有一个半透明的选项卡栏,内容应该在它下面滚动,滚动视图的底部应该有一个与选项卡栏高度相等的插图,这样你就可以看到所有内容。双击选项卡栏应使活动导航堆栈弹出到堆栈的顶部,再次单击应将堆栈中的活动滚动视图滚动到顶部。虽然并非所有这些行为都是通过React Navigation开箱即用实现的,但如果您使用独立的选项卡视图组件,它们将是开箱即用的,您将不会得到任何这些。

选项卡导航器包含一个堆栈,并且您希望在特定屏幕上隐藏选项卡栏

Drawer navigation 抽屉导航

导航中的常见模式是使用左侧(有时是右侧)的抽屉在屏幕之间导航

基于抽屉的导航的最小示例

要使用此抽屉导航器,请从@react navigation/dowler导入:(向右滑动打开)

import * as React from 'react';
import { Button, View } from 'react-native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { NavigationContainer } from '@react-navigation/native';

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button
        onPress={() => navigation.navigate('Notifications')}
        title="Go to notifications"
      />
    </View>
  );
}

function NotificationsScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button onPress={() => navigation.goBack()} title="Go back home" />
    </View>
  );
}

const Drawer = createDrawerNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator initialRouteName="Home">
        <Drawer.Screen name="Home" component={HomeScreen} />
        <Drawer.Screen name="Notifications" component={NotificationsScreen} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}
打开和关闭抽屉

要打开和关闭抽屉,请使用

navigation.openDrawer();
navigation.closeDrawer();

果你想切换抽屉,你可以调用以下命令

navigation.toggleDrawer();

在幕后,这些功能中的每一个都只是调度操作

navigation.dispatch(DrawerActions.openDrawer());
navigation.dispatch(DrawerActions.closeDrawer());
navigation.dispatch(DrawerActions.toggleDrawer());

若您想确定抽屉是打开的还是关闭的,您可以执行以下操作

import { useDrawerStatus } from '@react-navigation/drawer';

// ...

const isDrawerOpen = useDrawerStatus() === 'open';

Authentication flows 身份验证流

大多数应用程序都要求用户以某种方式进行身份验证,才能访问与用户或其他私人内容相关的数据。通常情况下,流程看起来是这样的

用户打开应用程序。
该应用程序从加密的永久存储(例如,SecureStore)加载一些身份验证状态。
加载状态后,将向用户显示身份验证屏幕或主应用程序,具体取决于是否加载了有效的身份验证状态。
当用户注销时,我们清除身份验证状态并将其发送回身份验证屏幕。

我们需要什么

这是我们希望从身份验证流中获得的行为:当用户登录时,我们希望丢弃身份验证流的状态并卸载所有与身份验证相关的屏幕,当我们按下硬件后退按钮时,我们预计无法返回到身份验证流

它将如何工作

我们可以根据某些条件定义不同的屏幕。例如,如果用户已登录,我们可以定义主页、配置文件、设置等。如果用户未登录,我们可定义登录和注册屏幕

isSignedIn ? (
  <>
    <Stack.Screen name="Home" component={HomeScreen} />
    <Stack.Screen name="Profile" component={ProfileScreen} />
    <Stack.Screen name="Settings" component={SettingsScreen} />
  </>
) : (
  <>
    <Stack.Screen name="SignIn" component={SignInScreen} />
    <Stack.Screen name="SignUp" component={SignUpScreen} />
  </>
);

当我们这样定义屏幕时,当isSignedIn为true时,React Navigation将只看到Home、Profile和Settings屏幕,当它为false时,ReactNavigation会看到SignIn和SignUp屏幕。这使得当用户未登录时无法导航到主页、配置文件和设置屏幕,而当用户登录时则无法导航到登录和注册屏幕。
这种模式已经被其他路由库(如React Router)使用了很长一段时间,通常被称为“受保护的路由”。在这里,我们需要用户登录的屏幕是“受保护的”,如果用户没有登录,则无法通过其他方式导航到。
当isSignedIn变量的值发生变化时,神奇的事情就发生了。假设最初isSignedIn为false。这意味着将显示“登录”或“注册”屏幕。用户登录后,isSignedIn的值将更改为true。React Navigation将看到SignIn和SignUp屏幕不再定义,因此将删除它们。然后它会自动显示主屏幕,因为这是当isSignedIn为true时定义的第一个屏幕。
该示例显示了堆栈导航器,但您可以对任何导航器使用相同的方法。
通过基于变量有条件地定义不同的屏幕,我们可以以一种简单的方式实现身份验证流,而不需要额外的逻辑来确保显示正确的屏幕。

有条件渲染屏幕时不要手动导航

需要注意的是,当使用这样的设置时,您不会通过调用navigation.navigation(“主页”)或任何其他方法手动导航到主屏幕。当isSignedIn更改时,React Navigation将自动导航到正确的屏幕-当isSigned In变为true时,主屏幕将自动导航,当isSignED In变为false时,导航到SignIn屏幕。如果您尝试手动导航,将会出现错误。

定义我们的屏幕

在我们的导航器中,我们可以有条件地定义适当的屏幕。对于我们的案例,假设我们有3个屏幕

SplashScreen-当我们恢复令牌时,这将显示一个启动或加载屏幕。
SignInScreen-如果用户尚未登录(我们找不到令牌),则会显示此屏幕。
HomeScreen-如果用户已经登录,我们会显示这个屏幕。

if (state.isLoading) {
  // We haven't finished checking for the token yet
  return <SplashScreen />;
}

return (
  <Stack.Navigator>
    {state.userToken == null ? (
      // No token found, user isn't signed in
      <Stack.Screen
        name="SignIn"
        component={SignInScreen}
        options={{
          title: 'Sign in',
          // When logging out, a pop animation feels intuitive
          // You can remove this if you want the default 'push' animation
          animationTypeForReplace: state.isSignout ? 'pop' : 'push',
        }}
      />
    ) : (
      // User is signed in
      <Stack.Screen name="Home" component={HomeScreen} />
    )}
  </Stack.Navigator>
);

在上面的片段中,isLoading意味着我们仍在检查是否有令牌。这通常可以通过检查SecureStore中是否有令牌并验证令牌来完成。在我们获得令牌后,如果它是有效的,我们需要设置userToken。我们还有另一个名为isSignout的状态,在注销时有不同的动画

主要需要注意的是,我们基于这些状态变量有条件地定义屏幕

  • SignIn screen is only defined if userToken is null (user is not signed in)
  • Home screen is only defined if userToken is non-null (user is signed in)

在这里,我们有条件地为每个案例定义一个屏幕。但您也可以定义多个屏幕。例如,当用户未登录时,您可能也想定义密码重置、注册等屏幕。同样,对于登录后可访问的屏幕,您可能有多个屏幕。我们可以使用React。定义多个屏幕的片段

state.userToken == null ? (
  <>
    <Stack.Screen name="SignIn" component={SignInScreen} />
    <Stack.Screen name="SignUp" component={SignUpScreen} />
    <Stack.Screen name="ResetPassword" component={ResetPassword} />
  </>
) : (
  <>
    <Stack.Screen name="Home" component={HomeScreen} />
    <Stack.Screen name="Profile" component={ProfileScreen} />
  </>
);
实现恢复令牌的逻辑

以下只是如何在应用程序中实现身份验证逻辑的示例。你不需要照原样行事

从前面的片段中,我们可以看到我们需要3个状态变量

isLoading-当我们试图检查是否已经在SecureStore中保存了令牌时,我们将其设置为true
isSignout-当用户注销时,我们将其设置为true,否则设置为false
userToken-用户的令牌。如果它不是null,我们假设用户已登录,否则不是。

因此,我们需要:

添加一些恢复令牌、登录和注销的逻辑

公开用于登录和注销到其他组件的方法

我们将在本指南中使用React.useReducer和React.use Context。但是,如果您使用的是状态管理库,如Redux或Mobx,则可以将它们用于此功能。事实上,在更大的应用程序中,全局状态管理库更适合存储身份验证令牌。您可以将相同的方法应用于您的州管理库

首先,我们需要为auth创建一个上下文,在这里我们可以公开必要的方法

import * as React from 'react';

const AuthContext = React.createContext();

所以我们的组件看起来是这样的

import * as React from 'react';
import * as SecureStore from 'expo-secure-store';

export default function App({ navigation }) {
  const [state, dispatch] = React.useReducer(
    (prevState, action) => {
      switch (action.type) {
        case 'RESTORE_TOKEN':
          return {
            ...prevState,
            userToken: action.token,
            isLoading: false,
          };
        case 'SIGN_IN':
          return {
            ...prevState,
            isSignout: false,
            userToken: action.token,
          };
        case 'SIGN_OUT':
          return {
            ...prevState,
            isSignout: true,
            userToken: null,
          };
      }
    },
    {
      isLoading: true,
      isSignout: false,
      userToken: null,
    }
  );

  React.useEffect(() => {
    // Fetch the token from storage then navigate to our appropriate place
    const bootstrapAsync = async () => {
      let userToken;

      try {
        userToken = await SecureStore.getItemAsync('userToken');
      } catch (e) {
        // Restoring token failed
      }

      // After restoring token, we may need to validate it in production apps

      // This will switch to the App screen or Auth screen and this loading
      // screen will be unmounted and thrown away.
      dispatch({ type: 'RESTORE_TOKEN', token: userToken });
    };

    bootstrapAsync();
  }, []);

  const authContext = React.useMemo(
    () => ({
      signIn: async (data) => {
        // In a production app, we need to send some data (usually username, password) to server and get a token
        // We will also need to handle errors if sign in failed
        // After getting token, we need to persist the token using `SecureStore`
        // In the example, we'll use a dummy token

        dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
      },
      signOut: () => dispatch({ type: 'SIGN_OUT' }),
      signUp: async (data) => {
        // In a production app, we need to send user data to server and get a token
        // We will also need to handle errors if sign up failed
        // After getting token, we need to persist the token using `SecureStore`
        // In the example, we'll use a dummy token

        dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
      },
    }),
    []
  );

  return (
    <AuthContext.Provider value={authContext}>
      <Stack.Navigator>
        {state.userToken == null ? (
          <Stack.Screen name="SignIn" component={SignInScreen} />
        ) : (
          <Stack.Screen name="Home" component={HomeScreen} />
        )}
      </Stack.Navigator>
    </AuthContext.Provider>
  );
}
填充其他组件

我们不会讨论如何实现身份验证屏幕的文本输入和按钮,这超出了导航的范围。我们只需要填写一些占位符内容

function SignInScreen() {
  const [username, setUsername] = React.useState('');
  const [password, setPassword] = React.useState('');

  const { signIn } = React.useContext(AuthContext);

  return (
    <View>
      <TextInput
        placeholder="Username"
        value={username}
        onChangeText={setUsername}
      />
      <TextInput
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      />
      <Button title="Sign in" onPress={() => signIn({ username, password })} />
    </View>
  );
}
身份验证状态更改时删除共享屏幕

考虑以下示例:

isSignedIn ? (
  <>
    <Stack.Screen name="Home" component={HomeScreen} />
    <Stack.Screen name="Profile" component={ProfileScreen} />
    <Stack.Screen name="Help" component={HelpScreen} />
  </>
) : (
  <>
    <Stack.Screen name="SignIn" component={SignInScreen} />
    <Stack.Screen name="SignUp" component={SignUpScreen} />
    <Stack.Screen name="Help" component={HelpScreen} />
  </>
);

这里我们有特定的屏幕,如登录、主页等,这些屏幕仅根据登录状态显示。但我们也有帮助屏幕,这两种情况都可以显示。这也意味着,如果用户在“帮助”屏幕中时登录状态发生更改,他们将留在“帮助”页面上。
这可能是一个问题,我们可能希望将用户带到登录屏幕或主屏幕,而不是将他们保留在帮助屏幕上。要实现此功能,我们可以使用navigationKey道具。当导航键更改时,React Navigation将删除所有屏幕。

所以我们更新的代码如下

<>
  {isSignedIn ? (
    <>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Profile" component={ProfileScreen} />
    </>
  ) : (
    <>
      <Stack.Screen name="SignIn" component={SignInScreen} />
      <Stack.Screen name="SignUp" component={SignUpScreen} />
    </>
  )}
  <Stack.Screen
    navigationKey={isSignedIn ? 'user' : 'guest'}
    name="Help"
    component={HelpScreen}
  />
</>

如果您有一堆共享屏幕,也可以将navigationKey与组一起使用以删除组中的所有屏幕。例如

<>
  {isSignedIn ? (
    <>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Profile" component={ProfileScreen} />
    </>
  ) : (
    <>
      <Stack.Screen name="SignIn" component={SignInScreen} />
      <Stack.Screen name="SignUp" component={SignUpScreen} />
    </>
  )}
  <Stack.Group navigationKey={isSignedIn ? 'user' : 'guest'}>
    <Stack.Screen name="Help" component={HelpScreen} />
    <Stack.Screen name="About" component={AboutScreen} />
  </Stack.Group>
</>

Supporting safe areas 支持安全区域

默认情况下,React Navigation会尝试确保导航器的元素在带有凹口的设备(如iPhone X)和可能与应用程序内容重叠的UI元素上正确显示。此类项目包括

物理缺口
状态栏覆盖
iOS上的家庭活动指示器
Android上的导航栏

未与此类物品重叠的区域称为“安全区域”。

我们试图在导航器的UI元素上应用适当的插入,以避免被这些项目重叠。目标是(a)最大限度地使用屏幕(b),而不隐藏内容或通过物理显示剪切或某些操作系统UI遮挡内容而使其难以交互。
虽然React Navigation默认情况下处理内置UI元素的安全区域,但您自己的内容可能也需要处理它,以确保内容不会被这些项目隐藏。
通过将整个应用程序包装在一个带有填充物的容器中以确保所有内容不会被遮挡,很容易解决(a)问题。但这样做会浪费屏幕上的大量空间,如下图所示。理想情况下,我们想要的是右边的图片。

虽然React Native导出了一个SafeAreaView组件,但该组件仅支持iOS 10+,不支持旧的iOS版本或Android。此外,它还存在一些问题,即如果包含安全区域的屏幕正在设置动画,则会导致跳跃行为。因此,我们建议使用react本地安全区域上下文库中的useSafeAreaInsets挂钩,以更可靠的方式处理安全区域。

本指南的其余部分提供了有关如何在React Navigation中支持安全区域的更多信息

隐藏/自定义标题或选项卡栏

React Navigation在默认标头中处理安全区域。但是,如果您使用的是自定义标头,那么确保您的UI位于安全区域内是很重要的

例如,如果不为标题或tabBar渲染任何内容,则不渲染任何内容

import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

function Demo() {
  return (
    <View
      style={{ flex: 1, justifyContent: 'space-between', alignItems: 'center' }}
    >
      <Text>This is top text.</Text>
      <Text>This is bottom text.</Text>
    </View>
  );
}
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{ headerShown: false }}
      >
        <Stack.Screen name="Home">
          {() => (
            <Tab.Navigator
              initialRouteName="Analitics"
              tabBar={() => null}
              screenOptions={{ headerShown: false }}
            >
              <Tab.Screen name="Analitics" component={Demo} />
              <Tab.Screen name="Profile" component={Demo} />
            </Tab.Navigator>
          )}
        </Stack.Screen>

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

Hiding tab bar in specific screens在特定屏幕中隐藏选项卡栏

有时,我们可能希望在嵌套在选项卡导航器中的堆栈导航器中隐藏特定屏幕中的选项卡栏。假设我们有5个屏幕:主页、订阅源、通知、个人资料和设置,你的导航结构如下

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

function App() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeStack} />
      <Tab.Screen name="Feed" component={Feed} />
      <Tab.Screen name="Notifications" component={Notifications} />
    </Tab.Navigator>
  );
}

使用此结构,当我们导航到“配置文件”或“设置”屏幕时,选项卡栏仍将在这些屏幕上可见

但是,如果我们只想在主页、订阅源和通知屏幕上显示标签栏,而不想在个人资料和设置屏幕上显示,我们需要更改导航结构。实现这一点的最简单方法是将选项卡导航器嵌套在堆栈的第一个屏幕内,而不是将堆栈嵌套在选项卡导航器内

function HomeTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={Home} />
      <Tab.Screen name="Feed" component={Feed} />
      <Tab.Screen name="Notifications" component={Notifications} />
    </Tab.Navigator>
  );
}

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

重新组织导航结构后,现在如果我们导航到“配置文件”或“设置”屏幕,标签栏将不再在屏幕上可见

Different status bar configuration based on route基于路由的不同状态栏配置

Opening a modal打开模态

模式显示暂时阻止与主视图交互的内容

模态就像一个弹出窗口——它通常有一个不同的过渡动画,旨在专注于一个特定的交互或内容

创建具有模态屏幕的堆栈
function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 30 }}>This is the home screen!</Text>
      <Button
        onPress={() => navigation.navigate('MyModal')}
        title="Open Modal"
      />
    </View>
  );
}

function DetailsScreen() {
  return (
    <View>
      <Text>Details</Text>
    </View>
  );
}

function ModalScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 30 }}>This is a modal!</Text>
      <Button onPress={() => navigation.goBack()} title="Dismiss" />
    </View>
  );
}

const RootStack = createStackNavigator();

function RootStackScreen() {
  return (
    <RootStack.Navigator>
      <RootStack.Group>
        <RootStack.Screen name="Home" component={HomeScreen} />
        <RootStack.Screen name="Details" component={DetailsScreen} />
      </RootStack.Group>
      <RootStack.Group screenOptions={{ presentation: 'modal' }}>
        <RootStack.Screen name="MyModal" component={ModalScreen} />
      </RootStack.Group>
    </RootStack.Navigator>
  );
}

在这里,我们使用RootStack.Group创建2组屏幕。。第一组用于常规屏幕,第二组用于模态屏幕。对于模态组,我们在screenOptions中指定了presentation:“模态”。这会将此选项应用于组内的所有屏幕。此选项将更改屏幕的动画,使其从下到上而不是从右到左设置动画。堆栈导航器的显示选项可以是卡(默认)或模式。模式行为将屏幕从底部滑入,并允许用户从顶部向下滑动以在iOS上消除它。
除了为组指定此选项外,还可以使用RootStack上的选项属性为单个屏幕指定此选项。

总结

要更改堆栈导航器上的转换类型,可以使用presentation选项。
当演示设置为模态时,屏幕的行为类似于模态,即它们具有从下到上的过渡,并且可能在后台显示前一屏幕的一部分。
设置演示:组上的“modal”使组中的所有屏幕都变为modals,因此为了在其他屏幕上使用非模态转换,我们添加了另一个具有默认配置的组

最佳实践

由于情态动词是在其他内容之上使用的,因此在使用情态动词时需要记住以下几点

  • 避免将它们嵌套在标签或抽屉等其他导航器中。模式屏幕应定义为根堆栈的一部分。

  • 模态屏幕应该是堆栈中的最后一个——避免将常规屏幕推到模态的顶部。

  • 堆栈中的第一个屏幕显示为常规屏幕,即使配置为模态屏幕,因为在它之前没有屏幕显示在后面。因此,一定要确保模式屏幕被推到常规屏幕或其他模式屏幕的顶部。

Multiple drawers 多个抽屉

有时我们想在同一个屏幕上有多个抽屉:一个在左边,一个在右边。这可以通过两种方式实现

直接使用rreact-native-drawer-layout原生抽屉布局(推荐)

通过嵌套2个抽屉导航器

使用react-native-drawer-layout

当我们有多个抽屉时,只有一个抽屉显示屏幕列表。第二个抽屉通常用于显示一些附加信息,如用户列表等

在这种情况下,我们可以直接使用react-native-drawer-layout 抽屉布局来渲染第二个抽屉。抽屉导航器将用于渲染第一个抽屉,并可以嵌套在第二个抽屉内

import * as React from 'react';
import { Button, View } from 'react-native';
import { Drawer } from 'react-native-drawer-layout';
import { createDrawerNavigator } from '@react-navigation/drawer';

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button onPress={() => navigation.openDrawer()} title="Open drawer" />
    </View>
  );
}

const LeftDrawer = createDrawerNavigator();

const LeftDrawerScreen = () => {
  return (
    <LeftDrawer.Navigator screenOptions={{ drawerPosition: 'left' }}>
      <LeftDrawer.Screen name="Home" component={HomeScreen} />
    </LeftDrawer.Navigator>
  );
};

function RightDrawerScreen() {
  const [rightDrawerOpen, setRightDrawerOpen] = React.useState(false);

  return (
    <Drawer
      open={rightDrawerOpen}
      onOpen={() => setRightDrawerOpen(true)}
      onClose={() => setRightDrawerOpen(false)}
      drawerPosition="right"
      renderDrawerContent={() => <>{/* Right drawer content */}</>}
    >
      <LeftDrawerScreen />
    </Drawer>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <RightDrawerScreen />
    </NavigationContainer>
  );
}

但有一个问题。当我们在主屏幕中调用navigation.openDrawer()时,它总是打开左侧的抽屉。我们无法通过导航属性访问正确的抽屉,因为它不是导航器

为了解决这个问题,我们需要使用上下文API传递一个函数来控制右边的抽屉

import * as React from 'react';
import { Button, View } from 'react-native';
import { Drawer } from 'react-native-drawer-layout';
import { createDrawerNavigator } from '@react-navigation/drawer';

const RightDrawerContext = React.createContext();

function HomeScreen({ navigation }) {
  const { openRightDrawer } = React.useContext(RightDrawerContext);

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button
        onPress={() => navigation.openDrawer()}
        title="Open left drawer"
      />
      <Button onPress={() => openRightDrawer()} title="Open right drawer" />
    </View>
  );
}

const LeftDrawer = createDrawerNavigator();

const LeftDrawerScreen = () => {
  return (
    <LeftDrawer.Navigator screenOptions={{ drawerPosition: 'left' }}>
      <LeftDrawer.Screen name="Home" component={HomeScreen} />
    </LeftDrawer.Navigator>
  );
};

function RightDrawerScreen() {
  const [rightDrawerOpen, setRightDrawerOpen] = React.useState(false);

  const value = React.useMemo(
    () => ({
      openRightDrawer: () => setRightDrawerOpen(true),
      closeRightDrawer: () => setRightDrawerOpen(false),
    }),
    []
  );

  return (
    <Drawer
      open={rightDrawerOpen}
      onOpen={() => setRightDrawerOpen(true)}
      onClose={() => setRightDrawerOpen(false)}
      drawerPosition="right"
      renderDrawerContent={() => <>{/* Right drawer content */}</>}
    >
      <RightDrawerContext.Provider value={value}>
        <LeftDrawerScreen />
      </RightDrawerContext.Provider>
    </Drawer>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <RightDrawerScreen />
    </NavigationContainer>
  );
}

在这里,我们使用RightDrawerContext将openRightDrawer函数传递给主屏幕。然后我们使用openRightDrawer打开正确的抽屉

嵌套2个抽屉导航器

另一种方法是将2个抽屉导航器相互嵌套。不建议这样做,因为它需要创建一个额外的屏幕和更多的嵌套,这会使导航和类型检查更加冗长。但如果两个导航器都包含多个屏幕,这可能会很有用

这里我们有两个相互嵌套的抽屉导航器,一个位于左侧,另一个位于右侧

import * as React from 'react';
import { Button, View } from 'react-native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { NavigationContainer } from '@react-navigation/native';

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button onPress={() => navigation.openDrawer()} title="Open drawer" />
    </View>
  );
}

const LeftDrawer = createDrawerNavigator();

const LeftDrawerScreen = () => {
  return (
    <LeftDrawer.Navigator screenOptions={{ drawerPosition: 'left' }}>
      <LeftDrawer.Screen name="Home" component={HomeScreen} />
    </LeftDrawer.Navigator>
  );
};

const RightDrawer = createDrawerNavigator();

const RightDrawerScreen = () => {
  return (
    <RightDrawer.Navigator
      screenOptions={{ drawerPosition: 'right', headerShown: false }}
    >
      <RightDrawer.Screen name="HomeDrawer" component={LeftDrawerScreen} />
    </RightDrawer.Navigator>
  );
};

export default function App() {
  return (
    <NavigationContainer>
      <RightDrawerScreen />
    </NavigationContainer>
  );
}

但有一个问题。当我们在主屏幕中调用navigation.openDrawer()时,它总是打开左侧抽屉,因为它是屏幕的直接父级

为了解决这个问题,我们需要使用navigation.getParent来引用右侧抽屉,该抽屉是左侧抽屉的父抽屉。所以我们的代码看起来像

<Button onPress={() => navigation.openDrawer()} title="Open left drawer" />
<Button onPress={() => navigation.getParent().openDrawer()} title="Open right drawer" />

然而,这意味着我们的按钮需要知道父导航器,这并不理想。如果我们的按钮进一步嵌套在其他导航器中,它需要多个

getParent()调用。为了解决这个问题,我们可以使用id prop来标识父导航器。

要自定义抽屉的内容,我们可以使用drawerContent属性传递一个呈现自定义组件的函数

最终代码看起来像这样

import * as React from 'react';
import { Button, Text, View } from 'react-native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { NavigationContainer } from '@react-navigation/native';

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button
        onPress={() => navigation.getParent('LeftDrawer').openDrawer()}
        title="Open left drawer"
      />
      <Button
        onPress={() => navigation.getParent('RightDrawer').openDrawer()}
        title="Open right drawer"
      />
    </View>
  );
}

function RightDrawerContent() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>This is the right drawer</Text>
    </View>
  );
}

const LeftDrawer = createDrawerNavigator();

function LeftDrawerScreen() {
  return (
    <LeftDrawer.Navigator
      id="LeftDrawer"
      screenOptions={{ drawerPosition: 'left' }}
    >
      <LeftDrawer.Screen name="Home" component={HomeScreen} />
    </LeftDrawer.Navigator>
  );
}

const RightDrawer = createDrawerNavigator();

function RightDrawerScreen() {
  return (
    <RightDrawer.Navigator
      id="RightDrawer"
      drawerContent={(props) => <RightDrawerContent {...props} />}
      screenOptions={{
        drawerPosition: 'right',
        headerShown: false,
      }}
    >
      <RightDrawer.Screen name="HomeDrawer" component={LeftDrawerScreen} />
    </RightDrawer.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <RightDrawerScreen />
    </NavigationContainer>
  );
}

在这里,我们在抽屉导航器的id属性中传递“LeftDraw”和“RightDrawer”字符串(您可以在这里使用任何字符串)。然后我们使用

navigation.getParent(‘LeftDrawer’).openDrawer() 打开左侧的抽屉

navigation.getParent(‘RightDrawer’).openDrawer() 打开右侧的抽屉

摘要
  • 要有多个抽屉,您可以直接结合抽屉导航器使用react原生抽屉布局
  • 抽屉定位支柱可用于将抽屉定位在右侧
  • 当使用reat-native-drawer-layout时,可以使用上下文API传递控制抽屉的方法
  • 嵌套多个导航器时,可以将navigation.getParent与id prop结合使用,以引用所需的抽屉

Screen options with nested navigators带有嵌套导航器的屏幕选项

在本文档中,我们将解释当有多个导航器时屏幕选项是如何工作的。了解这一点很重要,这样你才能把你的选项放在正确的位置,并正确配置你的导航器。如果你把它们放错了地方,往好了说什么都不会发生,往坏了说,会发生一些令人困惑和意想不到的事情

您只能从导航器的屏幕组件之一修改导航器的导航选项。这同样适用于嵌套为屏幕的导航器

让我们以一个选项卡导航器为例,它在每个选项卡中都包含一个本机堆栈。如果我们在堆栈内的屏幕上设置选项,会发生什么

const Tab = createTabNavigator();
const HomeStack = createNativeStackNavigator();
const SettingsStack = createNativeStackNavigator();

function HomeStackScreen() {
  return (
    <HomeStack.Navigator>
      <HomeStack.Screen
        name="A"
        component={A}
        options={{ tabBarLabel: 'Home!' }}
      />
    </HomeStack.Navigator>
  );
}

function SettingsStackScreen() {
  return (
    <SettingsStack.Navigator>
      <SettingsStack.Screen
        name="B"
        component={B}
        options={{ tabBarLabel: 'Settings!' }}
      />
    </SettingsStack.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeStackScreen} />
        <Tab.Screen name="Settings" component={SettingsStackScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

正如我们之前提到的,您只能从导航器的屏幕组件之一修改导航选项。上面的A和B分别是HomeStack和SettingsStack中的屏幕组件,而不是选项卡导航器中的组件。因此,结果将是tabBarLabel属性不应用于选项卡导航器。不过,我们可以解决这个问题

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen
          name="Home"
          component={HomeStackScreen}
          options={{ tabBarLabel: 'Home!' }}
        />
        <Tab.Screen
          name="Settings"
          component={SettingsStackScreen}
          options={{ tabBarLabel: 'Settings!' }}
        />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

当我们直接在包含HomeStack和SettingsStack组件的屏幕组件上设置选项时,它允许我们在其父导航器用作屏幕组件时控制其选项。在这种情况下,堆栈组件上的选项配置了呈现堆栈的选项卡导航器中的标签

根据子导航器的状态设置父屏幕选项

想象一下以下配置

const Tab = createBottomTabNavigator();

function HomeTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={FeedScreen} />
      <Tab.Screen name="Profile" component={ProfileScreen} />
      <Tab.Screen name="Account" component={AccountScreen} />
    </Tab.Navigator>
  );
}

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeTabs} />
        <Stack.Screen name="Settings" component={SettingsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

如果我们用FeedScreen的选项设置headerTitle,这将不起作用。这是因为应用程序堆栈只会查看其直接子项进行配置:HomeTabs和SettingsScreen

但是,我们可以使用getFocusedRouteNameFromRoute帮助程序根据选项卡导航器的导航状态来确定headerTitle选项。让我们先创建一个函数来获取标题

import { getFocusedRouteNameFromRoute } from '@react-navigation/native';

function getHeaderTitle(route) {
  // If the focused route is not found, we need to assume it's the initial screen
  // This can happen during if there hasn't been any navigation inside the screen
  // In our case, it's "Feed" as that's the first screen inside the navigator
  const routeName = getFocusedRouteNameFromRoute(route) ?? 'Feed';

  switch (routeName) {
    case 'Feed':
      return 'News feed';
    case 'Profile':
      return 'My profile';
    case 'Account':
      return 'My account';
  }
}

然后,我们可以将此功能与屏幕上的选项属性一起使用

<Stack.Screen
  name="Home"
  component={HomeTabs}
  options={({ route }) => ({
    headerTitle: getHeaderTitle(route),
  })}
/>

那么,这里发生了什么?使用getFocusedRouteNameFromRoute辅助程序,我们可以从子导航器(在这种情况下是选项卡导航器,因为这是我们正在呈现的)获取当前活动的路由名称,并为标头设置适当的标题

只要您想根据子导航器的状态为父导航器设置选项,就可以使用这种方法。常见用例包括

  • 在堆栈标题中显示选项卡标题:堆栈包含选项卡导航器,您希望在堆栈标题上设置标题(上例)
  • 显示没有标签栏的屏幕:标签导航器包含一个堆栈,您想在特定屏幕上隐藏标签栏(不建议,请参阅在特定屏幕中隐藏标签栏)
  • 在某些屏幕上锁定抽屉:抽屉里有一堆东西,你想在某些屏幕上将抽屉锁定

在许多情况下,通过重组我们的导航器可以实现类似的行为。如果适合您的用例,我们通常会推荐此选项

例如,对于上述用例,我们可以在每个选项卡内添加一个堆栈导航器,而不是在堆栈导航器中添加一个选项卡导航器

const FeedStack = createNativeStackNavigator();

function FeedStackScreen() {
  return (
    <FeedStack.Navigator>
      <FeedStack.Screen name="Feed" component={FeedScreen} />
      {/* other screens */}
    </FeedStack.Navigator>
  );
}

const ProfileStack = createNativeStackNavigator();

function ProfileStackScreen() {
  return (
    <ProfileStack.Navigator>
      <ProfileStack.Screen name="Profile" component={ProfileScreen} />
      {/* other screens */}
    </ProfileStack.Navigator>
  );
}

const Tab = createBottomTabNavigator();

function HomeTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={FeedStackScreen} />
      <Tab.Screen name="Profile" component={ProfileStackScreen} />
    </Tab.Navigator>
  );
}

const RootStack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <RootStack.Navigator>
        <RootStack.Screen name="Home" component={HomeTabs} />
        <RootStack.Screen name="Settings" component={SettingsScreen} />
      </RootStack.Navigator>
    </NavigationContainer>
  );
}

此外,这允许您将新屏幕推送到提要和配置文件堆栈,而无需通过向这些堆栈添加更多路线来隐藏标签栏

如果你想将屏幕推到标签栏的顶部(即不显示标签栏),那么你可以将它们添加到应用程序堆栈中,而不是添加到标签导航器内的屏幕中。

Custom Android back button behavior自定义Android后退按钮行为

默认情况下,当用户按下Android硬件后退按钮时,react导航将弹出屏幕,如果没有屏幕可弹出,则退出应用程序。这是一种合理的默认行为,但在某些情况下可能需要实现自定义处理

例如,考虑一个屏幕,用户正在列表中选择项目,并且“选择模式”处于活动状态。在按下后退按钮时,您首先希望“选择模式”被禁用,并且屏幕应该只在第二次按下后退按钮后弹出。下面的代码片段演示了这种情况。我们使用react native附带的BackHandler,以及useFocusEffect钩子来添加我们的自定义硬件BackPress侦听器

从onBackPress返回true表示我们已经处理了该事件,并且react导航的侦听器将不会被调用,因此不会弹出屏幕。返回false将导致事件冒泡并作出反应导航的侦听器将弹出屏幕

function ScreenWithCustomBackBehavior() {
  // ...

  useFocusEffect(
    React.useCallback(() => {
      const onBackPress = () => {
        if (isSelectionModeEnabled()) {
          disableSelectionMode();
          return true;
        } else {
          return false;
        }
      };

      const subscription = BackHandler.addEventListener(
        'hardwareBackPress',
        onBackPress
      );

      return () => subscription.remove();
    }, [isSelectionModeEnabled, disableSelectionMode])
  );

  // ...
}

所提出的方法适用于StackNavigator中显示的屏幕。目前可能不支持在其他情况下自定义后退按钮处理(例如,当您想在打开的抽屉中处理后退按钮按下时,这种方法不起作用。欢迎此类用例的PR

如果您不想覆盖系统返回按钮,而是想阻止从屏幕返回,请参阅防止返回的文档

为什么不使用组件生命周期方法

首先,您可能倾向于使用componentDidMount订阅封底事件,使用componentWillUnmount取消订阅,或使用useEffect添加监听器。这种方法行不通——在导航生命周期中了解更多信息

Animating elements between screens在屏幕之间设置元素动画

本指南介绍如何在屏幕之间设置元素动画。此功能被称为共享元素转换,它是在@reactnavigation/native堆栈中使用react native Reanimated实现的

预备知识

在继续本指南之前,请确保您的应用程序符合这些标准

  • 您正在使用@reactnavigation/native堆栈。基于JS的@reactnavigation/stack不支持共享元素转换功能
  • 您已安装并配置了react native reanimated v3.0.0或更高版本
最小示例

创建共享转换

  1. 使用从react-native-reanimated导入的动画组件
  2. 将相同的sharedTransitionTag分配给不同屏幕上的元素
  3. 在屏幕之间导航。转换将自动开始
import * as React from 'react';
import { View, Button, StyleSheet } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import Animated from 'react-native-reanimated';

const Stack = createNativeStackNavigator();

function HomeScreen({ navigation }) {
  return (
    <View style={styles.container}>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
      <Animated.Image
        source={{ uri: 'https://picsum.photos/id/39/200' }}
        style={{ width: 300, height: 300 }}
        sharedTransitionTag="tag"
      />
    </View>
  );
}

function DetailsScreen({ navigation }) {
  return (
    <View style={styles.container}>
      <Button title="Go back" onPress={() => navigation.goBack()} />
      <Animated.Image
        source={{ uri: 'https://picsum.photos/id/39/200' }}
        style={{ width: 100, height: 100 }}
        sharedTransitionTag="tag"
      />
    </View>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
  },
});

sharedTransitionTag是一个字符串,在单个屏幕的上下文中必须是唯一的,但必须匹配屏幕之间的元素。该属性允许Reanimated识别和动画元素,类似于key属性,该属性告诉React列表中的哪个元素是

自定义转换

默认情况下,过渡使用withTiming对宽度、高度、originX、originY和变换属性进行动画设置,持续时间为500毫秒。您可以轻松自定义宽度、高度、originX和originY属性。自定义转换也是可能的,但这远远超出了本指南的范围

要自定义转换,您需要传递除转换之外的所有属性

import { SharedTransition } from 'react-native-reanimated';

const customTransition = SharedTransition.custom((values) => {
  'worklet';
  return {
    height: withSpring(values.targetHeight),
    width: withSpring(values.targetWidth),
    originX: withSpring(values.targetOriginX),
    originY: withSpring(values.targetOriginY),
  };
});

function HomeScreen() {
  return (
    <Animated.Image
      style={{ width: 300, height: 300 }}
      sharedTransitionTag="tag"
      sharedTransitionStyle={customTransition} // add this to both elements on both screens
    />
  );
}
参考资料

您可以在React Native Reanimated文档中找到完整的共享元素转换参考

https://docs.swmansion.com/react-native-reanimated/docs/shared-element-transitions/overview/

其他选择

或者,您可以使用带有react Navigation绑定的react原生共享元素库,该绑定在基于JS的@react Navigation/stack导航器中实现共享元素转换。然而,这个解决方案并没有得到积极的维护

Preventing going back防止回头

有时您可能希望阻止用户离开屏幕,例如,如果有未保存的更改,您可能希望显示一个确认对话框。您可以使用beforeRemove事件来实现它

事件侦听器接收触发它的操作。您可以在确认后再次调度此操作,或者检查操作对象以确定要执行的操作。

function EditText({ navigation }) {
  const [text, setText] = React.useState('');
  const hasUnsavedChanges = Boolean(text);

  React.useEffect(
    () =>
      navigation.addListener('beforeRemove', (e) => {
        if (!hasUnsavedChanges) {
          // If we don't have unsaved changes, then we don't need to do anything
          return;
        }

        // Prevent default behavior of leaving the screen
        e.preventDefault();

        // Prompt the user before leaving the screen
        Alert.alert(
          'Discard changes?',
          'You have unsaved changes. Are you sure to discard them and leave the screen?',
          [
            { text: "Don't leave", style: 'cancel', onPress: () => {} },
            {
              text: 'Discard',
              style: 'destructive',
              // If the user confirmed, then we dispatch the action we blocked earlier
              // This will continue the action that had triggered the removal of the screen
              onPress: () => navigation.dispatch(e.data.action),
            },
          ]
        );
      }),
    [navigation, hasUnsavedChanges]
  );

  return (
    <TextInput
      value={text}
      placeholder="Type something…"
      onChangeText={setText}
    />
  );
}

以前,这样做的方法是

  • 覆盖页眉中的后退按钮

  • 禁用向后滑动手势

  • 覆盖Android上的系统后退按钮/手势

然而,这种方法除了代码较少之外,还有许多重要的区别

  • 它没有耦合到任何特定的按钮,从自定义按钮返回也会触发它

  • 它不与任何特定操作耦合,任何将路由从状态中删除的操作都将触发它

  • 它适用于嵌套导航器,例如,如果由于父导航器中的操作而删除了屏幕

  • 用户仍然可以在堆栈导航器中向后滑动,但是,如果事件被阻止,则滑动将被取消

  • 可以继续触发事件的相同操作

局限性

使用beforeRemove事件时需要注意几个限制。只有当屏幕因导航状态更改而被删除时,才会触发该事件。例如

  • 用户按下堆栈中屏幕上的后退按钮。
  • 用户执行了向后滑动手势。
  • 调度了一些操作,如弹出或重置,将屏幕从状态中删除。

当屏幕未聚焦但未移除时,不会触发此事件。例如

  • 用户在屏幕顶部推送一个新屏幕,监听器在堆栈中。
  • 用户从一个选项卡/抽屉屏幕导航到另一个选项卡或抽屉屏幕

当用户由于不受导航状态控制的操作而退出屏幕时,也不会触发该事件

  • 用户关闭应用程序(例如,按下主屏幕上的后退按钮、关闭浏览器中的选项卡、从应用程序切换器关闭应用程序等)。您还可以在Android上使用hardwareBackPress事件,在Web上使用beforeunload事件等来处理其中的一些情况。
  • 屏幕由于条件渲染或父组件被卸载而被卸载。
  • 由于使用了带有@react navigation/底部选项卡、@react navigation/抽屉等的unmountOnBlur选项,屏幕将被卸载。

除了上述场景外,此功能也不能与@react navigation/nature stack一起正常工作。要使其发挥作用,您需要

  • 禁用屏幕的滑动手势(手势启用:false)。
  • 使用自定义后退按钮覆盖标头中的本机后退按钮(headerLeft:(props)=><CustomBackButton{…props}/>)。

Call a function when focused screen changes当焦点屏幕发生变化时调用函数

在本指南中,我们将调用一个函数或在屏幕上渲染一些东西。当用户重新访问选项卡导航器中的特定屏幕时,这有助于进行额外的API调用,或在用户点击我们的应用程序时跟踪用户事件

我们有多种方法可供选择

监听’focus’事件

使用react-navigation提供的useFocusEffect钩子

使用react-navigation提供的useIsFocused钩子

监听’focus’事件

我们还可以与事件监听器一起收听“焦点”事件。设置事件侦听器后,我们还必须在卸载屏幕时停止侦听事件

通过这种方法,我们只能在屏幕聚焦时调用操作。这对于执行诸如记录屏幕视图以进行分析等操作非常有用

import * as React from 'react';
import { View } from 'react-native';

function ProfileScreen({ navigation }) {
  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      // The screen is focused
      // Call any action
    });

    // Return the function to unsubscribe from the event so it gets removed on unmount
    return unsubscribe;
  }, [navigation]);

  return <View />;
}

有关事件侦听器API的更多详细信息,请参阅导航事件指南

在大多数情况下,建议使用useFocusEffect钩子,而不是手动添加监听器。详见下文

使用react-navigation提供的useFocusEffect钩子

React Navigation提供了一个钩子,当屏幕聚焦时运行效果,当屏幕失焦时将其清理干净。这对于添加事件侦听器、在屏幕聚焦时使用API调用获取数据或在屏幕进入视图后需要执行的任何其他操作等情况都很有用

当我们试图在页面未聚焦时停止某些操作时,这尤其方便,比如停止播放视频或音频文件,或者停止跟踪用户的位置

import { useFocusEffect } from '@react-navigation/native';

function Profile({ userId }) {
  const [user, setUser] = React.useState(null);

  useFocusEffect(
    React.useCallback(() => {
      const unsubscribe = API.subscribe(userId, (user) => setUser(data));

      return () => unsubscribe();
    }, [userId])
  );

  return <ProfileContent user={user} />;
}
使用useIsFocused钩子重新渲染屏幕

React Navigation提供了一个钩子,返回一个布尔值,指示屏幕是否聚焦

当屏幕聚焦时,钩子将返回true,当我们的组件不再聚焦时,它将返回false。这使我们能够根据用户是否在屏幕上有条件地呈现内容

当我们聚焦和取消聚焦屏幕时,useIsFocused钩子将导致我们的组件重新渲染。使用此挂钩组件可能会在屏幕进入和离开焦点时引入不必要的组件重新渲染。这可能会导致问题,具体取决于我们呼吁关注的行动类型。因此,我们建议仅在需要触发重新渲染时使用此钩子。对于订阅事件或获取数据等副作用,请使用上述方法

import * as React from 'react';
import { Text } from 'react-native';
import { useIsFocused } from '@react-navigation/native';

function Profile() {
  // This hook returns `true` if the screen is focused, `false` otherwise
  const isFocused = useIsFocused();

  return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}

Access the navigation prop from any component从任何组件访问导航属性

useNavigation是一个钩子,用于访问导航对象。当您无法将导航属性直接传递到组件中,或者不想在嵌套较深的子组件中传递导航属性时,它会很有用

不是屏幕组件的普通组件不会自动接收导航属性。例如,在此GoToButton组件中

import * as React from 'react';
import { Button } from 'react-native';

function GoToButton({ navigation, screenName }) {
  return (
    <Button
      title={`Go to ${screenName}`}
      onPress={() => navigation.navigate(screenName)}
    />
  );
}

要解决此异常,您可以在从屏幕渲染导航属性时将其传递给GoToButton,如下所示:<GoToButton navigation={props.navigation}/>。

或者,您可以使用useNavigation自动提供导航属性(如果您感兴趣,可以通过React上下文)。

import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '@react-navigation/native';

function GoToButton({ screenName }) {
  const navigation = useNavigation();

  return (
    <Button
      title={`Go to ${screenName}`}
      onPress={() => navigation.navigate(screenName)}
    />
  );
}

使用这种方法,您可以在应用程序中的任何位置渲染GoToButton,而无需显式传递导航属性,它将按预期工作

Navigating without the navigation prop在没有导航属性的情况下导航

有时,您需要从无法访问导航属性的地方触发导航操作,例如Redux中间件。对于这种情况,可以使用导航容器上的ref来调度导航操作

如果出现以下情况,请不要使用ref:

  • 您需要从组件内部进行导航,而无需向下传递导航属性,请参阅useNavigation。ref的行为不同,并且许多特定于屏幕的辅助方法不可用。
  • 您需要处理深层链接或通用链接。对裁判这样做有很多边缘情况。有关处理深度链接的更多信息,请参阅配置链接。
  • 您需要与第三方库集成,如推送通知、分支等。请参阅第三方集成以获取深度链接

使用ref的场景

  • 您使用状态管理库,如Redux,需要从中间件中调度导航操作

​ 请注意,通常最好从用户操作(如按钮按下)触发导航,而不是从Redux中间件触发导航。导航用户动作使应用程序感觉更灵敏,并提供更好的用户体验。因此,在使用ref进行导航之前,请考虑这一点。ref是无法使用现有API处理的场景的转义符,只应在极少数情况下使用

用法

您可以通过ref访问根导航对象,并将其传递给我们稍后将用于导航的RootNavigation

// App.js

import { NavigationContainer } from '@react-navigation/native';
import { navigationRef } from './RootNavigation';

export default function App() {
  return (
    <NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
  );
}

下一步,我们定义RootNavigation,它是一个简单的模块,具有调度用户定义的导航操作的功能

// RootNavigation.js

import { createNavigationContainerRef } from '@react-navigation/native';

export const navigationRef = createNavigationContainerRef();

export function navigate(name, params) {
  if (navigationRef.isReady()) {
    navigationRef.navigate(name, params);
  }
}

// add other navigation functions that you need and export them

然后,在您的任何javascript模块中,导入RootNavigation并调用您从中导出的函数。您可以在React组件之外使用这种方法,事实上,在它们内部使用时也能很好地工作

// any js module
import * as RootNavigation from './path/to/RootNavigation.js';

// ...

RootNavigation.navigate('ChatScreen', { userName: 'Lucy' });

除了导航,您还可以添加其他导航操作

import { StackActions } from '@react-navigation/native';

// ...

export function push(...args) {
  if (navigationRef.isReady()) {
    navigationRef.dispatch(StackActions.push(...args));
  }
}

请注意,需要呈现堆栈导航器来处理此操作。您可能需要检查文档是否嵌套以了解更多详细信息

在编写测试时,您可以模拟导航函数,并断言是否使用正确的参数调用了正确的函数。

处理初始化

使用此模式时,您需要牢记以下几点,以避免导航在应用程序中失败

ref仅在导航容器渲染后设置,这在处理深度链接时可以是异步的
需要呈现导航器才能处理操作,如果没有导航器,ref就无法准备好

如果您尝试在不呈现导航器的情况下进行导航,或者在导航器完成安装之前进行导航,它将打印一个错误,并且什么也不做。因此,您需要添加一个额外的检查,以决定在安装应用程序之前要做什么

例如,考虑以下场景,您在应用程序中的某个位置有一个屏幕,该屏幕在useEffect/componentDidMount上调度一个redux操作。您正在中间件中侦听此操作,并在获得该操作时尝试执行导航。这将引发一个错误,因为此时,父导航器还没有完成安装,也没有准备好。父级的useEffect/componentDidMount总是在子级的useEffect/component之后调用

为了避免这种情况,您可以使用ref上可用的isReady()方法,如上面的示例所示

// RootNavigation.js

import * as React from 'react';

export const navigationRef = createNavigationContainerRef();

export function navigate(name, params) {
  if (navigationRef.isReady()) {
    // Perform navigation if the react navigation is ready to handle actions
    navigationRef.navigate(name, params);
  } else {
    // You can decide what to do if react navigation is not ready
    // You can ignore this, or add these actions to a queue you can call later
  }
}

如果您不确定是否呈现了导航器,可以调用

navigationRef.current.getRootState(),如果渲染了任何导航器,它将返回一个有效的状态对象,否则它将返回undefined

Deep linking深度链接

本指南将介绍如何配置您的应用程序以处理各种平台上的深度链接。要处理传入链接,您需要处理两种情况

  • 如果应用程序以前没有打开,则深度链接需要设置初始状态
  • 如果应用程序已经打开,则深度链接需要更新状态以反映传入链接

React Native提供了一个Linking来获得传入链接的通知。React Navigation可以与Linking模块集成,自动处理深度链接。在Web上,React Navigation可以与浏览器的历史API集成,在客户端处理URL。请参阅配置链接以查看有关如何在React Navigation中配置链接的更多详细信息

虽然您不需要使用React Navigation中的链接属性,并且可以通过使用linking API并从那里导航来处理深层链接,但这将比使用为您处理许多边缘情况的链接道具复杂得多。因此,我们不建议您自己实现它

下面,我们将介绍所需的配置,以便深度链接集成工作

示例

首先,您需要为应用程序指定一个URL方案。这对应于URL中://之前的字符串,因此如果您的方案是mychat,则指向您的应用程序的链接将是mychat://。您可以在app.json中注册一个scheme,方法是在scheme键下添加一个字符串

{
  "expo": {
    "scheme": "mychat"
  }
}

接下来,安装expo链接,我们需要获得深度链接前缀

npx expo install expo-linking

然后,让我们将React Navigation配置为使用解析传入深度链接的方案

import * as Linking from 'expo-linking';

const prefix = Linking.createURL('/');

function App() {
  const linking = {
    prefixes: [prefix],
  };

  return (
    <NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
      {/* content */}
    </NavigationContainer>
  );
}

使用Linking.createURL的必要原因是,方案会因您是在客户端应用程序中还是在独立应用程序中而有所不同

app.json中指定的方案仅适用于独立应用程序。在世博会客户端应用程序中,您可以使用exp://ADDRESS:PORT/–/其中ADDRESS通常为127.0.0.1,PORT通常为19000-URL是在运行expo-start时打印的。Linking.createURL函数将其抽象出来,这样您就不需要手动指定它们

如果您使用通用链接,还需要将您的域添加到前缀中

const linking = {
  prefixes: [Linking.createURL('/'), 'https://app.example.com'],
};
使用裸React Native项目设置
Setup on Android

要在Android中配置外部链接,您可以在清单中创建一个新的意图。
最简单的方法是使用uri scheme包:npx uri scheme add mychat–android

如果要手动添加,请打开

SimpleApp/android/app/src/main/AndroidManifest.xml

并进行以下调整

将MainActivity的launchMode设置为singleTask,以便接收对现有MainActivity的意图(这是默认设置,因此您可能不需要实际更改任何内容)。

在具有VIEW类型操作的MainActivity条目中添加新的意向过滤器

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="mychat" />
    </intent-filter>
</activity>

与iOS上的通用链接类似,您也可以通过验证Android应用程序链接,使用域将应用程序与Android上的网站相关联。首先,您需要配置

AndroidManifest.xml

将android:autoVerify=“true”添加到<intent filter>条目中。
在中的新条目中添加域的方案和主机

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="mychat" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="http" />
        <data android:scheme="https" />
        <data android:host="www.example.com" />
    </intent-filter>
</activity>

然后,您需要通过托管数字资产链接JSON文件来声明网站和意向过滤器之间的关联

测试深层链接

在测试深度链接之前,请确保在模拟器/模拟器/设备中重建并安装应用程序

npx react-native run-android

adb命令可用于测试与Android模拟器或连接设备的深度链接,如下所示

adb shell am start -W -a android.intent.action.VIEW -d [your deep link] [your android package name]

比如

adb shell am start -W -a android.intent.action.VIEW -d "mychat://chat/jane" com.simpleapp
第三方集成

react Native的链接并不是处理深度链接的唯一方法。您可能还想集成其他服务,如Firebase动态链接、分支等,这些服务提供自己的API,以获得传入链接的通知

为了实现这一点,你需要重写React Navigation订阅传入链接的方式。为此,您可以提供自己的getInitialURL和订阅函数

const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],

  // Custom function to get the URL which was used to open the app
  async getInitialURL() {
    // First, you would need to get the initial URL from your third-party integration
    // The exact usage depend on the third-party SDK you use
    // For example, to get the initial URL for Firebase Dynamic Links:
    const { isAvailable } = utils().playServicesAvailability;

    if (isAvailable) {
      const initialLink = await dynamicLinks().getInitialLink();

      if (initialLink) {
        return initialLink.url;
      }
    }

    // As a fallback, you may want to do the default deep link handling
    const url = await Linking.getInitialURL();

    return url;
  },

  // Custom function to subscribe to incoming links
  subscribe(listener) {
    // Listen to incoming links from Firebase Dynamic Links
    const unsubscribeFirebase = dynamicLinks().onLink(({ url }) => {
      listener(url);
    });

    // Listen to incoming links from deep linking
    const linkingSubscription = Linking.addEventListener('url', ({ url }) => {
      listener(url);
    });

    return () => {
      // Clean up the event listeners
      unsubscribeFirebase();
      linkingSubscription.remove();
    };
  },

  config: {
    // Deep link configuration
  },
};

与上面的示例类似,您可以集成任何API,该API提供了获取初始URL的方法,并使用getInitialURL和订阅选项订阅新的传入UR

Configuring links 配置链接

在本指南中,我们将配置React Navigation来处理外部链接。如果你想,这是必要的

在Android和iOS上的React Native应用程序中处理深度链接

在web上使用时,在浏览器中启用URL集成

使用或使用LinkTo使用路径导航

继续之前,请确保您已在应用程序中配置了深度链接。如果您有Android或iOS应用程序,请记住指定前缀选项

NavigationContainer接受一个链接属性,使处理传入链接变得更加容易。您可以在链接属性中指定的两个最重要的属性

前缀和配置

import { NavigationContainer } from '@react-navigation/native';

const linking = {
  prefixes: [
    /* your linking prefixes */
  ],
  config: {
    /* configuration for matching screens with paths */
  },
};

function App() {
  return (
    <NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
      {/* content */}
    </NavigationContainer>
  );
}

当您指定链接属性时,React Navigation将自动处理传入的链接。在Android和iOS上,它将使用React Native的链接模块来处理传入链接,无论是在使用链接打开应用程序时,还是在应用程序打开时收到新链接时。在Web上,它将使用历史API将URL与浏览器同步

您还可以向NavigationContainer传递一个回调,该属性控制React Navigation尝试解析初始深度链接URL时显示的内容

Prefixes 前缀

前缀选项可用于指定自定义方案(例如mychat://)以及主机名和域名(例如。https://mychat.com)如果您已配置通用链接或Android应用程序链接

const linking = {
  prefixes: ['mychat://', 'https://mychat.com'],
};

请注意,Web不支持前缀选项。主机名和域名将从浏览器中的网站URL自动确定。如果您的应用程序仅在Web上运行,则可以从配置中省略此选项

Multiple subdomains 多个子域名

要匹配关联域的所有子域,可以通过前缀指定通配符。在特定域开始之前。请注意,由于星号后的句点,.mychat.com的条目与mychat.com不匹配。要同时启用*.mychat.com和mychat.com的匹配,您需要为每个条目提供单独的前缀条目

const linking = {
  prefixes: ['mychat://', 'https://mychat.com', 'https://*.mychat.com'],
};
Filtering certain paths 过滤某些路径

有时我们可能不想处理所有传入的链接。例如,我们可能希望过滤掉用于身份验证(例如expoauth会话)或其他目的的链接,而不是导航到特定屏幕

To achieve this, you can use the filter option

const linking = {
  prefixes: ['mychat://', 'https://mychat.com'],
  filter: (url) => !url.includes('+expo-auth-session'),
};

这在Web上不受支持,因为我们总是需要处理页面的URL

Mapping path to route names 将路由映射到路线名称

要处理链接,您需要将其转换为有效的导航状态,反之亦然。例如,路径/房间/聊天?user=jane可以被翻译成这样的状态对象

const state = {
  routes: [
    {
      name: 'rooms',
      state: {
        routes: [
          {
            name: 'chat',
            params: { user: 'jane' },
          },
        ],
      },
    },
  ],
};

默认情况下,React Navigation在解析URL时将使用路径段作为路由名称。但直接将路径段转换为路由名称可能不是预期的行为

例如,您可能希望将路径/feed/latest解析为类似以下内容

const state = {
  routes: [
    {
      name: 'Chat',
      params: {
        sort: 'latest',
      },
    },
  ];
}

您可以在链接中指定配置选项,以控制如何解析深度链接以满足您的需求

const config = {
  screens: {
    Chat: 'feed/:sort',
    Profile: 'user',
  },
};

这里,Chat是处理URL/提要的屏幕名称,Profile处理URL/用户

然后,可以在链接属性中将config选项传递给容器

import { NavigationContainer } from '@react-navigation/native';

const config = {
  screens: {
    Chat: 'feed/:sort',
    Profile: 'user',
  },
};

const linking = {
  prefixes: ['https://mychat.com', 'mychat://'],
  config,
};

function App() {
  return (
    <NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
      {/* content */}
    </NavigationContainer>
  );
}

配置对象必须与应用程序的导航结构匹配。例如,如果您在根目录的导航器中有聊天和个人资料屏幕,则可以进行上述配置

function App() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Chat" component={ChatScreen} />
      <Stack.Screen name="Profile" component={ProfileScreen} />
    </Stack.Navigator>
  );
}

如果您的聊天屏幕位于嵌套导航器内,我们需要对此进行说明。例如,考虑以下结构,其中您的个人资料屏幕位于根目录,但聊天屏幕嵌套在主页中

function App() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Profile" component={ProfileScreen} />
    </Stack.Navigator>
  );
}

function HomeScreen() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Chat" component={ChatScreen} />
    </Tab.Navigator>
  );
}

对于上述结构,我们的配置如下

const config = {
  screens: {
    Home: {
      screens: {
        Chat: 'feed/:sort',
      },
    },
    Profile: 'user',
  },
};

同样,任何嵌套都需要反映在配置中。有关更多详细信息,请参阅处理嵌套导航器

传递参数

一个常见的用例是将参数传递给屏幕以传递一些数据。例如,您可能希望配置文件屏幕有一个id参数来知道它是哪个用户的配置文件。在处理深度链接时,可以通过URL将参数传递给屏幕

默认情况下,会解析查询参数以获取屏幕的参数。例如,在上面的例子中,URL/userid=wojciech会将id参数传递到配置文件屏幕

您还可以自定义如何从URL解析参数。假设您希望URL看起来像/user/wojiech,其中id参数是wojciech,而不是查询参数中的id。您可以通过为路径指定user/:id来实现这一点。当路径段以:开头时,它将被视为参数。例如,URL/user/wojiech将解析为配置文件屏幕,字符串wojciech作为id参数的值,并将在配置文件屏幕的route.params.id中可用

默认情况下,所有参数都被视为字符串。您还可以通过在parse属性中指定一个函数来解析参数,并在stringify属性中指定另一个函数将其转换回字符串,从而自定义如何解析它们

如果你想解析/user/wojiech/settings得到 { id: 'user-wojciech' section: 'settings' },你可以将Profile的配置设置为如下

const config = {
  screens: {
    Profile: {
      path: 'user/:id/:section',
      parse: {
        id: (id) => `user-${id}`,
      },
      stringify: {
        id: (id) => id.replace(/^user-/, ''),
      },
    },
  },
};

这将导致类似的结果

const state = {
  routes: [
    {
      name: 'Profile',
      params: { id: 'user-wojciech', section: 'settings' },
    },
  ],
};
将参数标记为可选

有时,根据特定条件,参数可能会也可能不会出现在URL中。例如,在上述场景中,URL中可能并不总是有section参数,即/user/wojiech/settings和/user/wejiech都应该转到Profile屏幕,但section参数(在这种情况下为值设置)可能存在也可能不存在

在这种情况下,您需要将节参数标记为可选。你可以通过添加?参数名称后的后缀

const config = {
  screens: {
    Profile: {
      path: 'user/:id/:section?',
      parse: {
        id: (id) => `user-${id}`,
      },
      stringify: {
        id: (id) => id.replace(/^user-/, ''),
      },
    },
  },
};

使用URL/users/wojiech,这将导致

const state = {
  routes: [
    {
      name: 'Profile',
      params: { id: 'user-wojciech' },
    },
  ],
};
处理嵌套导航器

有时,您会将目标导航器嵌套在不属于深度链接的其他导航器中。例如,假设您的导航结构如下

function Home() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Profile" component={Profile} />
      <Tab.Screen name="Feed" component={Feed} />
    </Tab.Navigator>
  );
}

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

在根目录中有一个堆栈导航器,在根堆栈的主屏幕中,有一个带有各种屏幕的选项卡导航器。使用这种结构,假设您希望路径/users/:id转到配置文件屏幕。你可以这样表达嵌套配置

const config = {
  screens: {
    Home: {
      screens: {
        Profile: 'users/:id',
      },
    },
  },
};

在此配置中,您指定应为users/:id模式解析配置文件屏幕,并将其嵌套在主屏幕中。然后解析users/jane将得到以下状态对象

const state = {
  routes: [
    {
      name: 'Home',
      state: {
        routes: [
          {
            name: 'Profile',
            params: { id: 'jane' },
          },
        ],
      },
    },
  ],
};

值得注意的是,状态对象必须与嵌套导航器的层次结构相匹配。否则,该状态将被丢弃

处理不匹配的路由或404

如果你的应用程序是用无效的URL打开的,大多数时候你都想显示一个包含一些信息的错误页面。在网络上,这通常被称为404错误或页面未找到错误

要处理此问题,您需要定义一个包罗万象的路线,如果没有其他路线与该路径匹配,则将呈现该路线。您可以通过为路径匹配模式指定*来实现。

const config = {
  screens: {
    Home: {
      initialRouteName: 'Feed',
      screens: {
        Profile: 'users/:id',
        Settings: 'settings',
      },
    },
    NotFound: '*',
  },
};

在这里,我们定义了一个名为NotFound的路由,并将其设置为匹配*aka所有内容。如果路径与user/:id或设置不匹配,则将通过此路由进行匹配

因此,像/library或/settings/notification这样的路径将解析为以下状态对象

const state = {
  routes: [{ name: 'NotFound' }],
};

你甚至可以更具体地说,例如,如果你想在/settings下显示无效路径的不同屏幕,你可以在settings下指定这样的模式

const config = {
  screens: {
    Home: {
      initialRouteName: 'Feed',
      screens: {
        Profile: 'users/:id',
        Settings: {
          path: 'settings',
          screens: {
            InvalidSettings: '*',
          },
        },
      },
    },
    NotFound: '*',
  },
};

使用此配置,路径/设置/通知将解析为以下状态对象

const state = {
  routes: [
    {
      name: 'Home',
      state: {
        index: 1,
        routes: [
          { name: 'Feed' },
          {
            name: 'Settings',
            state: {
              routes: [
                { name: 'InvalidSettings', path: '/settings/notification' },
              ],
            },
          },
        ],
      },
    },
  ],
};

传递到NotFound屏幕的路由将包含一个与打开页面的路径相对应的路径属性。如果需要,您可以使用此属性自定义此屏幕中显示的内容,例如在WebView中加载页面

function NotFoundScreen({ route }) {
  if (route.path) {
    return <WebView source={{ uri: `https://mywebsite.com/${route.path}` }} />;
  }

  return <Text>This screen doesn't exist!</Text>;
}

在进行服务器渲染时,您还希望为404错误返回正确的状态代码。有关如何处理它的指南,请参阅服务器渲染文档

渲染初始路由

有时,您希望确保某个屏幕始终作为导航器状态中的第一个屏幕出现。您可以使用initialRouteName属性指定用于初始屏幕的屏幕

在上面的例子中,如果你想让Feed屏幕成为主页下导航器中的初始路线,你的配置如下

const config = {
  screens: {
    Home: {
      initialRouteName: 'Feed',
      screens: {
        Profile: 'users/:id',
        Settings: 'settings',
      },
    },
  },
};

然后,路径/users/42将解析为以下状态对象

const state = {
  routes: [
    {
      name: 'Home',
      state: {
        index: 1,
        routes: [
          { name: 'Feed' },
          {
            name: 'Profile',
            params: { id: '42' },
          },
        ],
      },
    },
  ],
};

另一件要记住的事情是,不可能通过URL将参数传递给初始屏幕。因此,请确保您的初始路由不需要任何参数,或者在屏幕配置中指定initialParams来传递所需的参数

在这种情况下,URL中的任何参数都只会传递给与路径模式users/:id匹配的Profile屏幕,而Feed屏幕不会接收任何参数。如果你想在Feed屏幕中有相同的参数,你可以指定一个自定义的getStateFromPath函数并复制这些参数

同样,如果你想从子屏幕访问父屏幕的参数,你可以使用React Context来公开它们

匹配精确路径

默认情况下,为每个屏幕定义的路径与相对于其父屏幕路径的URL相匹配。考虑以下配置

const config = {
  screens: {
    Home: {
      path: 'feed',
      screens: {
        Profile: 'users/:id',
      },
    },
  },
};

在这里,您为主屏幕以及子配置文件屏幕定义了一个路径属性。配置文件屏幕指定了路径users/:id,但由于它嵌套在带有路径提要的屏幕内,它将尝试匹配模式提要/users/:id

这将导致URL/feed导航到主屏幕,/feed/users/cal导航到配置文件屏幕

在这种情况下,使用/users/cal而不是/feed/users/car这样的URL导航到配置文件屏幕更有意义。为了实现这一点,您可以将相对匹配行为覆盖为精确匹配

const config = {
  screens: {
    Home: {
      path: 'feed',
      screens: {
        Profile: {
          path: 'users/:id',
          exact: true,
        },
      },
    },
  },
};

将精确属性设置为true后,Profile将忽略父屏幕的路径配置,您将能够使用users/cal等URL导航到Profile

从路由中省略屏幕

有时,您可能不希望在路径中包含屏幕的路由名称。例如,假设您有一个主屏幕,我们的导航状态如下

const state = {
  routes: [{ name: 'Home' }],
};

当此状态序列化为具有以下配置的路径时,您将获得/home

const config = {
  screens: {
    Home: {
      path: 'home',
      screens: {
        Profile: 'users/:id',
      },
    },
  },
};

但如果URL只是/在访问主屏幕时显示会更好。你可以指定一个空字符串作为路径,也可以根本不指定路径,React Navigation不会将屏幕添加到路径中(可以把它想象成向路径添加空字符串,这不会改变任何事情)

const config = {
  screens: {
    Home: {
      path: '',
      screens: {
        Profile: 'users/:id',
      },
    },
  },
};
序列化和解析参数

由于URL是字符串,因此在构建路径时,您为路由设置的任何参数也会转换为字符串

例如,假设您有以下状态

const state = {
  routes: [
    {
      name: 'Chat',
      params: { at: 1589842744264 },
    },
  ];
}

使用以下配置将其转换为chat/1589842744264

const config = {
  screens: {
    Chat: 'chat/:date',
  },
};

解析此路径时,您将得到以下状态

const state = {
  routes: [
    {
      name: 'Chat',
      params: { date: '1589842744264' },
    },
  ];
}

在这里,日期参数被解析为字符串,因为React Navigation不知道它应该是时间戳,因此也不知道它是数字。您可以通过提供用于解析的自定义函数来对其进行自定义

const config = {
  screens: {
    Chat: {
      path: 'chat/:date',
      parse: {
        date: Number,
      },
    },
  },
};

您还可以提供一个自定义函数来序列化参数。例如,假设您想在路径中使用DD-MM-YYYY格式,而不是时间戳

const config = {
  screens: {
    Chat: {
      path: 'chat/:date',
      parse: {
        date: (date) => new Date(date).getTime(),
      },
      stringify: {
        date: (date) => {
          const d = new Date(date);

          return d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate();
        },
      },
    },
  },
};

根据您的要求,您可以使用此功能来解析和字符串化更复杂的数据

高级案例

对于某些高级情况,指定映射可能还不够。为了处理这种情况,您可以指定一个自定义函数将URL解析为状态对象

(getStateFromPath)和一个将状态对象序列化为URL的自定义函数(getPathFromState)。

const linking = {
  prefixes: ['https://mychat.com', 'mychat://'],
  config: {
    screens: {
      Chat: 'feed/:sort',
    },
  },
  getStateFromPath: (path, options) => {
    // Return a state object here
    // You can also reuse the default logic by importing `getStateFromPath` from `@react-navigation/native`
  },
  getPathFromState(state, config) {
    // Return a path string here
    // You can also reuse the default logic by importing `getPathFromState` from `@react-navigation/native`
  },
};
正在更新的配置

旧版本的React Navigation的链接配置格式略有不同。旧配置允许对象中有一个简单的键值对,而不管是否嵌套导航器

const config = {
  Home: 'home',
  Feed: 'feed',
  Profile: 'profile',
  Settings: 'settings',
};

假设您的Feed和Profile屏幕嵌套在Home中。即使您没有上述配置的嵌套,只要URL是/home/profile,它就可以工作。此外,它还将对路径段和路由名称进行相同的处理,这意味着您可以深度链接到配置中未指定的屏幕。例如,如果您在主页中有一个相册屏幕,则深度链接/Home/Albums将导航到该屏幕。虽然在某些情况下这可能是可取的,但无法阻止访问特定屏幕。这种方法也使得不可能有类似404屏幕的东西,因为任何路由名称都是有效的路径

最新版本的React Navigation使用不同的配置格式,在这方面更严格

  • 配置的形状必须与导航结构中的嵌套形状相匹配
  • 只有配置中定义的屏幕才有资格进行深度链接

因此,您可以将上述配置重构为以下格式

const config = {
  screens: {
    Home: {
      path: 'home',
      screens: {
        Feed: 'feed',
        Profile: 'profile',
      },
    },
    Settings: 'settings',
  },
};

这里,配置对象有一个新的screens属性,Feed和Profile配置现在嵌套在Home下以匹配导航结构

如果您使用旧格式,它将继续工作而不做任何更改。但是,您将无法指定通配符模式来处理不匹配的屏幕或防止屏幕被深度链接。旧格式将在下一个主要版本中删除。因此,我们建议您在可能的情况下迁移到新格式

Web上的React导航

React Navigation的web支持目前需要使用React Native for web。这种方法使我们能够在React Native和Web上重用相同的代码

目前,有以下功能可用

  • 浏览器中的URL集成
  • 可访问的链接
  • 服务器渲染

重要的是使用链接作为主要的导航方式,而不是导航操作,如navigation.navigation。它将确保您的链接在网络上正确使用

一些导航器在网络上的配置也不同,或者提供其他特定于网络的功能

  • 抽屉和底部标签导航器分别在抽屉侧边栏和标签栏中显示超链接
  • 在网络上使用时,抽屉和堆栈导航器上没有滑动手势
  • 默认情况下,堆栈导航器禁用页面过渡动画,但可以通过指定animationEnabled:true重新启用它

Server rendering服务器渲染

本指南将介绍如何使用React Native for Web和React Navigation服务器渲染您的React Nation应用程序。我们将涵盖以下情况

浏览器中的URL集成

基于聚焦屏幕设置适当的页面元数据

预备知识

在遵循指南之前,请确保您的应用程序在服务器上已经呈现良好。为此,您需要确保以下几点

  • 你使用的所有依赖项都是在发布到npm之前编译的,这样你就不会在Node上遇到语法错误
  • 节点配置为能够需要图像和字体等资产文件。你可以尝试使用webpack同构工具来实现这一点。
  • react native被别名为react-native-web。你可以用babel插件模块解析器来实现
渲染应用程序

首先,让我们来看一个例子,说明如何在不涉及React Navigation的情况下使用React Native Web进行服务器渲染

import { AppRegistry } from 'react-native-web';
import ReactDOMServer from 'react-dom/server';
import App from './src/App';

const { element, getStyleElement } = AppRegistry.getApplication('App');

const html = ReactDOMServer.renderToString(element);
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());

const document = `
  <!DOCTYPE html>
  <html style="height: 100%">
  <meta charset="utf-8">
  <meta httpEquiv="X-UA-Compatible" content="IE=edge">
  <meta
    name="viewport"
    content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
  >
  ${css}
  <body style="min-height: 100%">
  <div id="root" style="display: flex; min-height: 100vh">
  ${html}
  </div>
`;

这里/src/App是您拥有AppRegistry.registerComponent(‘App’,()=>App)的文件

如果你在应用程序中使用React Navigation,这将呈现主页呈现的屏幕。但是,如果您在应用程序中配置了链接,则希望在服务器上为请求URL呈现正确的屏幕,以便它与客户端上呈现的内容相匹配

我们可以使用ServerContainer通过在位置道具中传递此信息来实现这一点。例如,使用Koa,您可以使用上下文参数中的路径和搜索属性

app.use(async (ctx) => {
  const location = new URL(ctx.url, 'https://example.org/');

  const { element, getStyleElement } = AppRegistry.getApplication('App');

  const html = ReactDOMServer.renderToString(
    <ServerContainer location={location}>{element}</ServerContainer>
  );

  const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());

  const document = `
    <!DOCTYPE html>
    <html style="height: 100%">
    <meta charset="utf-8">
    <meta httpEquiv="X-UA-Compatible" content="IE=edge">
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
    >
    ${css}
    <body style="min-height: 100%">
    <div id="root" style="display: flex; min-height: 100vh">
    ${html}
    </div>
`;

  ctx.body = document;
});

您可能还想为搜索引擎、打开图形等设置正确的文档标题和描述。为此,您可以向容器传递一个ref,它将为您提供当前屏幕的选项

app.use(async (ctx) => {
  const location = new URL(ctx.url, 'https://example.org/');

  const { element, getStyleElement } = AppRegistry.getApplication('App');

  const ref = React.createRef<ServerContainerRef>();

  const html = ReactDOMServer.renderToString(
    <ServerContainer
      ref={ref}
      location={location}
    >
      {element}
    </ServerContainer>
  );

  const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());

  const options = ref.current?.getCurrentOptions();

  const document = `
    <!DOCTYPE html>
    <html style="height: 100%">
    <meta charset="utf-8">
    <meta httpEquiv="X-UA-Compatible" content="IE=edge">
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
    >
    ${css}
    <title>${options.title}</title>
    <body style="min-height: 100%">
    <div id="root" style="display: flex; min-height: 100vh">
    ${html}
    </div>
`;

  ctx.body = document;
});

确保您在屏幕上指定了标题选项

<Stack.Screen
  name="Profile"
  component={ProfileScreen}
  options={{ title: 'My profile' }}
/>
处理404或其他状态代码

当渲染无效URL的屏幕时,我们还应该从服务器返回404状态代码

首先,我们需要创建一个上下文,在其中附加状态代码。为此,将以下代码放在一个单独的文件中,我们将在服务器和客户端上导入该文件

import * as React from 'react';

const StatusCodeContext = React.createContext();

export default StatusCodeContext;

然后,我们需要在NotFound屏幕中使用上下文。在这里,我们添加了一个值为404的代码属性,以表示找不到屏幕

function NotFound() {
  const status = React.useContext(StatusCodeContext);

  if (status) {
    staus.code = 404;
  }

  return (
    <View>
      <Text>Oops! This URL doesn't exist.</Text>
    </View>
  );
}

然后,我们需要在NotFound屏幕中使用上下文。在这里,我们添加了一个值为404的代码属性,以表示找不到屏幕

接下来,我们需要创建一个状态对象,以便在服务器上传递上下文。默认情况下,我们将代码设置为200。然后在StatusCodeContext中传递该对象。应使用ServerContainer包装元素的提供程序

// Create a status object
const status = { code: 200 };

const html = ReactDOMServer.renderToString(
  // Pass the status object via context
  <StatusCodeContext.Provider value={status}>
    <ServerContainer ref={ref} location={location}>
      {element}
    </ServerContainer>
  </StatusCodeContext.Provider>
);

// After rendering, get the status code and use it for server's response
ctx.status = status.code;

在我们使用ReactDOMServer.renderToString渲染应用程序后,如果渲染了NotFound屏幕,状态对象的代码属性将更新为404

您也可以对其他状态代码采用类似的方法,例如,401表示未经授权等

摘要
  • 使用ServerContainer上的位置属性根据传入的请求呈现正确的屏幕
  • 为当前屏幕的ServerContainer获取选项附加一个引用。
  • 使用上下文附加更多信息,如状态代码

Screen tracking for analytics用于分析的屏幕跟踪

要跟踪当前活动的屏幕,我们需要

  • 添加回调以获取状态更改的通知
  • 获取根导航器状态并查找活动路由名称

为了获得状态更改的通知,我们可以在NavigationContainer上使用onStateChange属性。要获取根导航器状态,我们可以在容器的引用上使用getRootState方法。请注意,onStateChange在初始呈现时不会被调用,因此您必须单独设置初始屏幕

此示例显示了该方法如何适用于任何移动分析SDK

import {
  NavigationContainer,
  useNavigationContainerRef,
} from '@react-navigation/native';

export default () => {
  const navigationRef = useNavigationContainerRef();
  const routeNameRef = useRef();

  return (
    <NavigationContainer
      ref={navigationRef}
      onReady={() => {
        routeNameRef.current = navigationRef.getCurrentRoute().name;
      }}
      onStateChange={async () => {
        const previousRouteName = routeNameRef.current;
        const currentRouteName = navigationRef.getCurrentRoute().name;
        const trackScreenView = () => {
          // Your implementation of analytics goes here!
        };

        if (previousRouteName !== currentRouteName) {
          // Save the current route name for later comparison
          routeNameRef.current = currentRouteName;

          // Replace the line below to add the tracker from a mobile analytics SDK
          await trackScreenView(currentRouteName);
        }
      }}
    >
      {/* ... */}
    </NavigationContainer>
  );
};

Themes主题

主题允许您更改React Navigation提供的各种组件的颜色。您可以使用主题

  • 定制与您的品牌相匹配的颜色
  • 根据一天中的时间或用户偏好提供明暗主题
基本用法
import * as React from 'react';
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';

const MyTheme = {
  ...DefaultTheme,
  colors: {
    ...DefaultTheme.colors,
    primary: 'rgb(255, 45, 85)',
  },
};

export default function App() {
  return (
    <NavigationContainer theme={MyTheme}>{/* content */}</NavigationContainer>
  );
}

您可以动态更改主题属性,所有组件都将自动更新以反映新主题。如果您没有提供主题属性,将使用默认主题

主题是一个JS对象,包含要使用的颜色列表。它包含以下属性

  • dark(boolean):这是深色主题还是浅色主题
  • colors (object)::react导航组件使用的各种颜色

primary (string): 应用程序的原色,用于为各种元素着色。通常你会想使用你的品牌颜色。

background (string):各种背景的颜色,例如屏幕的背景颜色。
card (string):卡片状元素的背景色,如标题、标签条等。
text (string): 各种元素的文本颜色。
border (string): 边框的颜色,例如页眉边框、选项卡栏边框等。
notification (string): 选项卡导航器徽章的颜色

创建自定义主题时,需要提供所有这些属性

const MyTheme = {
  dark: false,
  colors: {
    primary: 'rgb(255, 45, 85)',
    background: 'rgb(242, 242, 242)',
    card: 'rgb(255, 255, 255)',
    text: 'rgb(28, 28, 30)',
    border: 'rgb(199, 199, 204)',
    notification: 'rgb(255, 69, 58)',
  },
};

提供一个主题将照顾到所有官方航海家的造型。React Navigation还提供了一些工具来帮助您自定义这些导航器,导航器中的屏幕也可以使用该主题。

内置主题

随着操作系统增加了对亮暗模式的内置支持,支持暗模式与其说是为了跟上趋势,不如说是为了符合普通用户对应用程序工作方式的期望。为了以与操作系统默认值合理一致的方式提供对亮暗模式的支持,这些主题内置于React Navigation中

你可以像这样导入默认和深色主题

import { DefaultTheme, DarkTheme } from '@react-navigation/native';
保持本机主题同步

如果您正在更改应用程序中的主题,原生UI元素(如警报、ActionSheet等)将不会反映新主题。您可以执行以下操作以保持本机主题同步

React.useEffect(() => {
  const colorScheme = theme.dark ? 'dark' : 'light';

  if (Platform.OS === 'web') {
    document.documentElement.style.colorScheme = colorScheme;
  } else {
    Appearance.setColorScheme(colorScheme);
  }
}, [theme.dark]);

或者,您可以使用useColorScheme挂钩获取当前的原生配色方案并相应地更新主题

使用操作系统首选项

在iOS 13+和Android 10+上,您可以使用(useColorScheme挂钩)获取用户偏好的配色方案(“暗”或“亮”)

import { useColorScheme } from 'react-native';
import {
  NavigationContainer,
  DefaultTheme,
  DarkTheme,
} from '@react-navigation/native';

export default () => {
  const scheme = useColorScheme();

  return (
    <NavigationContainer theme={scheme === 'dark' ? DarkTheme : DefaultTheme}>
      {/* content */}
    </NavigationContainer>
  );
};
在您自己的组件中使用当前主题

要访问导航容器内呈现的任何组件中的主题:,可以使用useTheme钩子。它返回主题对象

import * as React from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { useTheme } from '@react-navigation/native';

// Black background and white text in light theme, inverted on dark theme
function MyButton() {
  const { colors } = useTheme();

  return (
    <TouchableOpacity style={{ backgroundColor: colors.card }}>
      <Text style={{ color: colors.text }}>Button!</Text>
    </TouchableOpacity>
  );
}

State persistence状态持久性

您可能希望在应用程序中保存用户的位置,以便在应用程序重新启动后立即将其返回到同一位置

这在开发过程中尤其有价值,因为它允许开发人员在刷新应用程序时保持在同一屏幕上

为了能够保持导航状态,我们可以使用容器的onStateChange和initialState属性

  • onStateChange - 此属性会通知我们任何状态更改。我们可以在这个回调中保持状态
  • initialState - 这个属性允许我们传递一个初始状态用于导航状态。我们可以在这个道具中通过恢复状态
import * as React from 'react';
import { Linking, Platform } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { NavigationContainer } from '@react-navigation/native';

const PERSISTENCE_KEY = 'NAVIGATION_STATE_V1';

export default function App() {
  const [isReady, setIsReady] = React.useState(Platform.OS === 'web'); // Don't persist state on web since it's based on URL
  const [initialState, setInitialState] = React.useState();

  React.useEffect(() => {
    const restoreState = async () => {
      try {
        const initialUrl = await Linking.getInitialURL();

        if (initialUrl == null) {
          // Only restore state if there's no deep link
          const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY);
          const state = savedStateString
            ? JSON.parse(savedStateString)
            : undefined;

          if (state !== undefined) {
            setInitialState(state);
          }
        }
      } finally {
        setIsReady(true);
      }
    };

    if (!isReady) {
      restoreState();
    }
  }, [isReady]);

  if (!isReady) {
    return null;
  }

  return (
    <NavigationContainer
      initialState={initialState}
      onStateChange={(state) =>
        AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state))
      }
    >
      {/* ... */}
    </NavigationContainer>
  );
}
开发模式

此功能在开发模式中特别有用。您可以使用以下方法选择性地启用它

const [isReady, setIsReady] = React.useState(__DEV__ ? false : true);

虽然它也可以用于生产,但要谨慎使用,因为如果应用程序在特定屏幕上崩溃,它可能会使应用程序无法使用,因为用户在重新启动后仍将在同一屏幕上

加载视图

因为状态是异步恢复的,所以在我们获得初始状态之前,应用程序必须先呈现一个空/加载视图。为了处理这个问题,我们可以在isReady为false时返回一个加载视图

if (!isReady) {
  return <ActivityIndicator />;
}
警告:可序列化状态

每个参数、路由和导航状态都必须是完全可序列化的,才能使此功能正常工作。通常,您会将状态序列化为JSON字符串。这意味着您的路由和参数不能包含函数、类实例或递归数据结构。React Navigation已经在开发过程中警告您,如果它遇到不可序列化的数据,所以如果您计划保持导航状态,请注意警告

您可以在将初始状态对象传递到容器之前修改它,但请注意,如果您的initialState不是有效的导航状态,React navigation可能无法优雅地处理这种情况

使用Jest进行测试

使用React Navigation测试代码可能需要一些设置,因为我们需要模拟导航器中使用的本机依赖关系。我们建议使用Jest编写单元测试

模拟本地模块

为了能够测试React Navigation组件,需要根据所使用的组件来模拟某些依赖关系。

如果你使用的是@react-navigation/drawer,你需要模拟

  • react-native-reanimated

  • react-native-gesture-handler

如果你使用@reactnavigation/stack,你只需要模拟

  • react-native-gesture-handler

    要添加模拟,请创建一个文件jest/setup.js(或您选择的任何其他文件名),并将以下代码粘贴到其中

// include this line for mocking react-native-gesture-handler
import 'react-native-gesture-handler/jestSetup';

// include this section and the NativeAnimatedHelper section for mocking react-native-reanimated
jest.mock('react-native-reanimated', () => {
  const Reanimated = require('react-native-reanimated/mock');

  // The mock for `call` immediately calls the callback which is incorrect
  // So we override it with a no-op
  Reanimated.default.call = () => {};

  return Reanimated;
});

// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');

然后,我们需要在jest配置中使用此设置文件。您可以在jest.config.js文件的setupFiles选项下或package.json中的jest键下添加它

{
  "preset": "react-native",
  "setupFiles": ["<rootDir>/jest/setup.js"]
}

确保setupFiles中文件的路径正确。Jest将在运行测试之前运行这些文件,因此它是放置全局模拟的最佳位置

如果你没有使用Jest,那么你需要根据你使用的测试框架来模拟这些模块

编写测试

我们建议使用React Native测试库和jest Native来编写测试

import * as React from 'react';
import { screen, render, fireEvent } from '@testing-library/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { RootNavigator } from './RootNavigator';

test('shows profile screen when View Profile is pressed', () => {
  render(
    <NavigationContainer>
      <RootNavigator />
    </NavigationContainer>
  );

  fireEvent.press(screen.getByText('View Profile'));

  expect(screen.getByText('My Profile')).toBeOnTheScreen();
});
最佳做法

在使用React Navigation编写组件测试时,有几件事需要记住

  • 避免嘲笑React导航。相反,在测试中使用真正的导航器
  • 不要检查导航操作。相反,请检查导航的结果,例如正在渲染的屏幕

使用TypeScript进行类型检查

React Navigation是用TypeScript编写的,并为TypeScript项目导出类型定义

对导航器进行类型检查

要类型检查我们的路由名称和参数,我们需要做的第一件事是创建一个对象类型,其中包含路由名称到路由参数的映射。例如,假设我们的根导航器中有一个名为Profile的路由,它应该有一个参数userId

type RootStackParamList = {
  Profile: { userId: string };
};

同样,我们需要对每条路由都做同样的事情

type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Feed: { sort: 'latest' | 'top' } | undefined;
};

指定undefined意味着路由没有参数。未定义的联合类型(例如SomeType|undefined)意味着参数是可选的

定义映射后,我们需要告诉导航器使用它。为此,我们可以将其作为泛型传递给createXNavigator函数

import { createStackNavigator } from '@react-navigation/stack';

const RootStack = createStackNavigator<RootStackParamList>();

然后我们可以使用它

<RootStack.Navigator initialRouteName="Home">
  <RootStack.Screen name="Home" component={Home} />
  <RootStack.Screen
    name="Profile"
    component={Profile}
    initialParams={{ userId: user.id }}
  />
  <RootStack.Screen name="Feed" component={Feed} />
</RootStack.Navigator>

这将为导航器和屏幕组件的属性提供类型检查和智能感知

类型检查屏幕

要键入检查我们的屏幕,我们需要注释屏幕接收到的导航属性和路由属性。React Navigation中的navigator包导出一个泛型类型,以定义来自相应navigator的导航和路线属性的类型

例如,您可以将NativeStackScreenProps用于本机堆栈导航器

import type { NativeStackScreenProps } from '@react-navigation/native-stack';

type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Feed: { sort: 'latest' | 'top' } | undefined;
};

type Props = NativeStackScreenProps<RootStackParamList, 'Profile'>;

该类型接受3个泛型

  • 我们之前定义的参数列表对象
  • 屏幕所属路由的名称
  • 导航器的ID(可选)

如果你有导航器的id属性,你可以

type Props = NativeStackScreenProps<RootStackParamList, 'Profile', 'MyStack'>;

这允许我们键入检查您使用导航、推送等导航的路由名称和参数。在route.params中键入检查参数以及调用setParams时,需要当前路由的名称

同样,您可以从@reactnavigation/stack导入StackScreenProps,从@react navigation/waught导入DrawerScreenProp,从@react navigation/bott选项卡导入BottomTabScreenProps等等

然后,您可以使用上面定义的Props类型来注释您的组件

function ProfileScreen({ route, navigation }: Props) {
  // ...
}
class ProfileScreen extends React.Component<Props> {
  render() {
    // ...
  }
}

您可以从Props类型中获取导航和路由的类型,如下所示

type ProfileScreenNavigationProp = Props['navigation'];

type ProfileScreenRouteProp = Props['route'];

或者,您也可以分别注释导航和路线属性

要获取导航属性的类型,我们需要从导航器中导入相应的类型。例如,NativeStackNavigationProp用于@reactnavigation/native堆栈

import type { NativeStackNavigationProp } from '@react-navigation/native-stack';

type ProfileScreenNavigationProp = NativeStackNavigationProp<
  RootStackParamList,
  'Profile'
>;

同样,您可以从@reactnavigation/stack导入StackNavigationProp,从@react navigation/laughter导入DrawerNavigationProp、从@react-navigation/bott选项卡导入BottomTabNavigationProp等

要获取路由属性的类型,我们需要使用@reactnavigation/native中的RouteProp类型

import type { RouteProp } from '@react-navigation/native';

type ProfileScreenRouteProp = RouteProp<RootStackParamList, 'Profile'>;

我们建议您创建一个单独的types.tsx文件,在其中保存类型并将其导入到组件文件中,而不是在每个文件中重复它们

嵌套导航器

嵌套导航器中的类型检查屏幕和参数

通过传递嵌套屏幕的屏幕和参数属性,您可以在嵌套导航器中导航到屏幕

navigation.navigate('Home', {
  screen: 'Feed',
  params: { sort: 'latest' },
});

为了能够进行类型检查,我们需要从包含嵌套导航器的屏幕中提取参数。这可以使用NavigatorScreenParams实用程序完成

import { NavigatorScreenParams } from '@react-navigation/native';

type TabParamList = {
  Home: NavigatorScreenParams<StackParamList>;
  Profile: { userId: string };
};
组合导航属性

当你嵌套导航器时,屏幕的导航属性是多个导航属性的组合。例如,如果我们在堆栈中有一个选项卡,导航属性将同时具有jumpTo(来自选项卡导航器)和push(来自堆栈导航器)。为了更容易组合来自多个导航器的类型,您可以使用CompositeScreen属性类型

import type { CompositeScreenProps } from '@react-navigation/native';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import type { StackScreenProps } from '@react-navigation/stack';

type ProfileScreenProps = CompositeScreenProps<
  BottomTabScreenProps<TabParamList, 'Profile'>,
  StackScreenProps<StackParamList>
>;

CompositeScreenProps类型有2个参数,第一个参数是主导航的属性类型(拥有此屏幕的导航器的类型,在我们的例子中是包含配置文件屏幕的标签导航器),第二个参数是辅助导航的属性的类型(父导航器的类型)。主类型应始终将屏幕的路线名称作为其第二个参数

对于多个父导航器,此次要类型应嵌套

type ProfileScreenProps = CompositeScreenProps<
  BottomTabScreenProps<TabParamList, 'Profile'>,
  CompositeScreenProps<
    StackScreenProps<StackParamList>,
    DrawerScreenProps<DrawerParamList>
  >
>;

如果单独注释导航属性,则可以使用CompositenavigationDrop。用法类似于CompositeScreen属性

import type { CompositeNavigationProp } from '@react-navigation/native';
import type { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import type { StackNavigationProp } from '@react-navigation/stack';

type ProfileScreenNavigationProp = CompositeNavigationProp<
  BottomTabNavigationProp<TabParamList, 'Profile'>,
  StackNavigationProp<StackParamList>
>;

useNavigation注解

要注释我们从useNavigation获得的导航属性,我们可以使用一个类型参数

const navigation = useNavigation<ProfileScreenNavigationProp>();
useRoute注解

要注释我们从useRoute获得的路由属性,我们可以使用一个类型参数:

const route = useRoute<ProfileScreenRouteProp>();

options and screenOptions注解

当您将选项传递给Screen或将screenOptions属性传递给Navigator组件时,它们已经过类型检查,您不需要做任何特殊操作。但是,有时您可能希望将选项提取到单独的对象中,并可能希望对其进行注释

要注释选项,我们需要从导航器中导入相应的类型

import type { StackNavigationOptions } from '@react-navigation/stack';

const options: StackNavigationOptions = {
  headerShown: false,
};

同样,您可以从@reactnavigation/sauder导入DrawerNavigationOptions,从@react navigation/bott选项卡导入BottomTabNavigationOptions等

使用选项和screenOptions的函数形式时,您可以使用与注释导航路由属性相同的类型来注释参数

Annotating ref on NavigationContainer

如果使用createNavigationContainerRef()方法创建引用,则可以对其进行注释以键入检查导航操作

import { createNavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef = createNavigationContainerRef<RootStackParamList>();

同样,对于使用NavigationContainerRef()

import { useNavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef = useNavigationContainerRef<RootStackParamList>();

如果使用常规ref对象,则可以将泛型传递给NavigationContainerRef类型

使用React.useRef钩子时的示例

import type { NavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef =
  React.useRef<NavigationContainerRef<RootStackParamList>>(null);

使用React.createRef时的示例

import type { NavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef =
  React.createRef<NavigationContainerRef<RootStackParamList>>();

指定默认使用useNavigation, Link, ref

您可以为根导航器指定一个全局类型作为默认类型,而不是手动注释这些API

为此,您可以将此代码段添加到代码库中的某个位置

declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}

RootParamList接口让React Navigation知道你的根导航器接受的参数。在这里,我们扩展了RootStackParamList类型,因为这是根目录下堆栈导航器的参数类型。这种类型的名称并不重要

如果您在应用程序中大量使用useNavigation、Link等,指定此类型非常重要,因为它将确保类型安全。它还将确保您在链接道具上有正确的嵌套

组织类型

在为React Navigation编写类型时,我们建议做几件事来保持内容的有序性

  1. 最好创建一个单独的文件(例如navigation/types.tsx),其中包含与React navigation相关的类型
  2. 与其直接在组件中使用CompositenavigationDrop,不如创建一个可以重用的帮助程序类型
  3. 为根导航器指定全局类型可以避免在许多地方进行手动注释

考虑到这些建议,包含这些类型的文件可能看起来像这样

import type {
  CompositeScreenProps,
  NavigatorScreenParams,
} from '@react-navigation/native';
import type { StackScreenProps } from '@react-navigation/stack';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';

export type RootStackParamList = {
  Home: NavigatorScreenParams<HomeTabParamList>;
  PostDetails: { id: string };
  NotFound: undefined;
};

export type RootStackScreenProps<T extends keyof RootStackParamList> =
  StackScreenProps<RootStackParamList, T>;

export type HomeTabParamList = {
  Popular: undefined;
  Latest: undefined;
};

export type HomeTabScreenProps<T extends keyof HomeTabParamList> =
  CompositeScreenProps<
    BottomTabScreenProps<HomeTabParamList, T>,
    RootStackScreenProps<keyof RootStackParamList>
  >;

declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}

现在,在为组件添加注释时,您可以编写

import type { HomeTabScreenProps } from './navigation/types';

function PopularScreen({ navigation, route }: HomeTabScreenProps<'Popular'>) {
  // ...
}

如果你使用useRoute等钩子,你可以写

import type { HomeTabScreenProps } from './navigation/types';

function PopularScreen() {
  const route = useRoute<HomeTabScreenProps<'Popular'>['route']>();

  // ...
}

Redux integration Redux集成

在带有React Navigation的应用程序中使用Redux非常容易。它基本上与没有React Navigation没有什么不同

import { Provider } from 'react-redux';
import { NavigationContainer } from '@react-navigation/native';

// Render the app container component with the provider around it
export default function App() {
  return (
    <Provider store={store}>
      <NavigationContainer>{/* Screen configuration */}</NavigationContainer>
    </Provider>
  );
}

请注意,我们将组件封装在Provider中,就像通常使用react redux一样。大大!现在可以在整个应用程序中使用connect

使用在选项中连接的组件

创建一个组件,将其连接到store,然后在title中使用该组件

function Counter({ value }) {
  return <Text>Count: {value}</Text>;
}

const CounterContainer = connect((state) => ({ value: state.count }))(Counter);
<Stack.Screen
  name="Test"
  component={TestScreen}
  options={{ title: () => <CounterContainer /> }}
/>
将您关心的状态作为参数传递到屏幕

如果该值预计不会更改,则可以将其作为参数从连接的组件传递到另一个屏幕

<Button
  title="Go to static counter screen"
  onPress={() =>
    props.navigation.navigate('StaticCounter', {
      count,
    })
  }
/>
function StaticCounter({ route }) {
  return (
    <View style={styles.container}>
      <Text style={styles.paragraph}>{route.params.count}</Text>
    </View>
  );
}

所以我们的组件看起来是这样的

<RootStack.Screen
  name="StaticCounter"
  component={StaticCounter}
  options={({ route }) => ({ title: route.params.count })}
/>

我也可以将导航状态存储在Redux中吗?

这是不可能的。我们不支持它,因为它太容易射中自己的脚并减慢/破坏您的应用程序

Navigation 导航

Stack Navigator 栈导航器

在这里插入图片描述

堆栈导航器为您的应用程序提供了一种在屏幕之间转换的方式,其中每个新屏幕都放置在堆栈的顶部

默认情况下,堆栈导航器配置为具有熟悉的iOS和Android外观:在iOS上,新屏幕从右侧滑入,在Android上使用OS默认动画。但动画可以根据您的需求进行定制

需要记住的一点是,虽然@react navigation/stack是非常可定制的,但它是用JavaScript实现的。虽然它使用本机运行动画和手势,但性能可能不如本机实现快。这对很多应用程序来说可能不是问题,但如果您在导航过程中遇到性能问题,请考虑使用@react navigation/nature stack,它使用本机导航原语

安装

要使用此导航器,请确保您有@react navigation/nature及其依赖项(请遵循本指南),然后安装@react navigation/stack:

npm install @react-navigation/stack

然后,您需要安装和配置堆栈导航器所需的库

npx expo install react-native-gesture-handler

要完成react原生手势处理程序的安装,请在输入文件的顶部添加以下内容(确保它在顶部,并且在此之前没有其他内容),如index.js或App.js

import 'react-native-gesture-handler';

也可以选择安装@react native masked view/masked view。如果要对标头使用UIKit样式的动画,则需要这样做

npx expo install @react-native-masked-view/masked-view
API定义

要使用此导航器,请从@react navigation/stack导入:

import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

function MyStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={Home} />
      <Stack.Screen name="Notifications" component={Notifications} />
      <Stack.Screen name="Profile" component={Profile} />
      <Stack.Screen name="Settings" component={Settings} />
    </Stack.Navigator>
  );
}
属性

堆栈导航器组件接受以下属性

id

导航器的可选唯一ID。这可以与navigation.getParent一起使用,以在子导航器中引用此导航器

initialRouteName

第一次加载导航器时要渲染的路由的名称

screenOptions

用于导航器中屏幕的默认选项

以下选项可用于配置导航器中的屏幕。这些可以在Stack.navigator的screenOptions属性或Stack的options属性下指定

属性说明
title可以用作headerTitle的字符串
cardShadowEnabled使用此属性可以在过渡过程中产生可见的阴影。默认为true
cardOverlayEnabled使用这个属性可以在转换过程中在卡片下方看到半透明的深色覆盖。在Android上默认为true,在iOS上默认为false。
cardOverlay函数,返回一个React元素以显示为卡的覆盖层。使用此选项时,请确保将cardOverlayEnabled设置为true
cardStyle堆叠中卡片的样式对象。您可以在此处提供要使用的自定义背景颜色,而不是默认背景
您还可以指定{backgroundColor:“transparent”}以使上一个屏幕在下面可见(对于透明模式)。这对于实现模式对话框之类的东西很有用。当使用透明背景时,您还应该在选项中指定presentation:“modal”,这样以前的屏幕就不会分离,并在下面保持可见。
在Web上,屏幕的高度不限于视口的高度。这是为了允许浏览器的地址栏在滚动时隐藏。如果这不是理想的行为,可以将cardStyle设置为{flex:1}以强制屏幕填充视口
presentation这是一个快捷方式选项,用于配置几个选项来配置渲染和转换的样式
1.card:使用iOS和Android屏幕转换的默认操作系统动画
2.modal:使用模态动画。这改变了一些事情:
将headerMode设置为屏幕的屏幕,除非另有指定。
更改屏幕动画以匹配模态的平台行为
3.transparentModal: 类似于模态。以下内容会发生变化
将headerMode设置为屏幕的屏幕,除非另有指定。
将屏幕的背景色设置为透明,以便上一个屏幕可见
调整detachPreviousScreen选项,使上一个屏幕保持渲染状态。
防止上一个屏幕从其最后一个位置设置动画。
将屏幕动画更改为垂直幻灯片动画
animationEnabled是否应在屏幕上启用过渡动画。如果将其设置为false,则在按下或弹出时屏幕将不会显示动画。在iOS和Android上默认为true,在Web上默认为false
animationTypeForReplace此屏幕替换另一个屏幕时要使用的动画类型。它采用以下值
1.push-将使用正在推送的新屏幕的动画
2.pop-将使用弹出屏幕的动画
gestureEnabled是否可以使用手势关闭此屏幕。在iOS上默认为true,在Android上默认为false
gestureResponseDistance用于覆盖从屏幕边缘开始触摸以识别手势的距离的数字
它将根据手势方向值配置水平或垂直距离
默认值为
50-当手势方向为水平或水平倒置时
135-当手势方向垂直或垂直倒置时
gestureVelocityImpact决定手势的速度相关性的数字。默认值为0.3。
gestureDirection手势的方向。有关详细信息,请参阅“动画”部分
transitionSpec屏幕转换的配置对象。有关详细信息,请参阅“动画”部分
cardStyleInterpolator卡片各个部分的插值样式。有关详细信息,请参阅“动画”部分
headerStyleInterpolator页眉各个部分的插值样式。有关详细信息,请参阅“动画”部分
keyboardHandlingEnabled如果为false,则从该屏幕导航到新屏幕时,键盘不会自动关闭。默认为true
detachPreviousScreen布尔值,用于指示是否从视图层次结构中分离前一个屏幕以节省内存。如果您需要通过活动屏幕查看前一个屏幕,请将其设置为false。仅适用于detachInactiveScreens未设置为false的情况
当将演示文稿用作transparentMode或modal时,会自动调整此选项,以保持所需屏幕可见。在其他情况下默认为true
freezeOnBlur布尔值,指示是否阻止非活动屏幕重新呈现。默认值为false。当在应用程序顶部运行react本机屏幕包中的enableFreeze()时,默认为true

Native Stack Navigator 本机堆栈导航器

Native Stack Navigator为您的应用程序提供了一种在屏幕之间转换的方式,其中每个新屏幕都放置在堆栈的顶部

他的导航器在iOS上使用原生API UINavigationController,在Android上使用Fragment,因此使用createNativeStackNavigator构建的导航将与在这些API之上原生构建的应用程序表现完全相同,并具有相同的性能特征。它还使用react原生Web提供基本的Web支持

需要记住的一点是,虽然@react navigation/nature stack提供了本机性能,并在iOS等上公开了大标题等本机功能,但根据您的需求,它可能不如@react navigation/stack那样可自定义。因此,如果您需要比该导航器更多的自定义,请考虑使用@react navigation/stack,这是一种更可自定义的基于JavaScript的实现

安装

要使用此导航器,请确保您有@react navigation/nature及其依赖项(请遵循本指南),然后安装@react navigation/natur堆栈

npm install @react-navigation/native-stack
API定义

如果您在使用createNativeStackNavigator时遇到任何错误,请在react原生屏幕而不是react导航存储库上打开问题

To use this navigator, import it from @react-navigation/native-stack:

import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

function MyStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={Home} />
      <Stack.Screen name="Notifications" component={Notifications} />
      <Stack.Screen name="Profile" component={Profile} />
      <Stack.Screen name="Settings" component={Settings} />
    </Stack.Navigator>
  );
}
属性

堆栈导航器组件接受以下属性

id

导航器的可选唯一ID。这可以与navigation.getParent一起使用,以在子导航器中引用此导航器

initialRouteName

第一次加载导航器时要渲染的路由的名称

screenOptions

用于导航器中屏幕的默认选项

Options

以下选项可用于配置导航器中的屏幕

属性名说明
title可以用作headerTitle的字符串
headerBackButtonMenuEnabled布尔值,指示是否在iOS的长按>=14后退按钮上显示菜单。默认为true。
headerBackVisible返回按钮在页眉中是否可见。如果已指定,则可以使用它在headerLeft旁边显示后退按钮
这对堆栈中的第一个屏幕没有影响
headerBackTitleiOS上后退按钮使用的标题字符串。默认为上一个场景的标题,如果空间不足,则为“后退”。使用headerBackTitleVisible:false隐藏它
Only supported on iOS
headerBackTitleVisible后退按钮标题是否可见
Only supported on iOS.
headerBackTitleStyle页眉后标题的样式对象。支持的属性
fontFamily fontSize
Only supported on iOS
headerBackImageSource要在页眉中显示为后退按钮中的图标的图像。默认为平台的背面图标图像
A chevron on iOS
An arrow on Android
headerLargeStyle显示大标题时标题的样式。如果headerLargeTitle为true,并且任何可滚动内容的边缘都达到标题的匹配边缘,则显示大标题
Supported properties backgroundColor
Only supported on iOS
headerLargeTitle是否启用滚动时折叠为常规标题的大标题标题标题
为了使大标题在滚动时折叠,屏幕的内容应该包装在可滚动的视图中,如ScrollView或FlatList。如果可滚动区域没有填满屏幕,则大标题不会在滚动时折叠。您还需要在ScrollView、FlatList等中指定contentInsetAdjustmentBehavior=“automatic”
Only supported on iOS
headerLargeTitleShadowVisible显示大标题时标题的阴影是否可见
headerLargeTitleStyle页眉中大标题的样式对象。支持的属性
fontFamily fontSize fontWeight color
Only supported on iOS
headerShown是否显示标题。默认情况下会显示标题。将其设置为false将隐藏标头
headerStyle页眉的样式对象。支持的属性
backgroundColor
headerShadowVisible是隐藏标题上的高程阴影(Android)还是隐藏底部边框(iOS)
headerTransparent布尔值,指示导航栏是否半透明
默认为false。将此设置为true将使标题处于绝对位置,从而使标题浮动在屏幕上,使其与下面的内容重叠,并将背景颜色更改为透明,除非在headerStyle中指定
如果要渲染半透明标头或模糊背景,则此选项非常有用
请注意,如果您不希望内容显示在标题下,则需要手动为内容添加上边距。React Navigation不会自动完成
要获得头部的高度,可以将HeaderHeightContext与React的Context API或useHeaderHeight一起使用
headerBlurEffect半透明页眉的模糊效果。headerTransparent选项需要设置为true才能工作
headerBackground函数,返回一个React元素作为标头的背景进行渲染。这对于使用背景(如图像或渐变)非常有用
headerTintColor页眉的色调。更改后退按钮和标题的颜色
headerLeft返回React Element以显示在页眉左侧的函数。这将取代后退按钮。请参见headerBackVisible以沿左侧元素显示后退按钮。
headerRight返回一个React Element以显示在页眉右侧的函数
headerTitle字符串或返回一个React元素以供标头使用的函数。默认为屏幕的标题或名称
headerTitleAlign如何对齐页眉标题。可能的值
left center
在iOS以外的平台上默认为左侧
headerTitleStyle标题的样式对象。支持的属性
fontFamily fontSize fontWeight color
headerSearchBarOptions在iOS上呈现本机搜索栏的选项。搜索栏很少是静态的,因此通常通过将对象传递到组件主体中的headerSearchBarOptions导航选项来控制它。您还需要在ScrollView、FlatList等中指定contentInsetAdjustmentBehavior=“automatic”。如果您没有ScrollView,请指定headerTransparent:false
autoCapitalize控制用户输入文本时是否自动将其大写。可能的值
none words sentences characters
autoFocus搜索栏显示时是否自动聚焦。默认为false
Only supported on Android
barTintColor搜索字段的背景颜色。默认情况下,条形图色调是半透明的
Only supported on iOS
tintColor光标插入符号和取消按钮文本的颜色
Only supported on iOS
cancelButtonText要使用的文本,而不是默认的“取消”按钮文本
Only supported on iOS
disableBackButtonOverride后退按钮是否应关闭搜索栏的文本输入。默认为false
Only supported on Android
hideNavigationBar布尔值,指示在搜索过程中是否隐藏导航栏。默认为true
Only supported on iOS
hideWhenScrolling布尔值,指示滚动时是否隐藏搜索栏。默认为true
Only supported on iOS
inputType输入的类型。默认为“text”
"text" "phone" "number" "email"
Only supported on Android
obscureBackground布尔值,指示是否使用半透明覆盖来隐藏基础内容。默认为true
placeholder搜索字段为空时显示的文本
textColor搜索字段中文本的颜色
hintTextColor搜索字段中提示文本的颜色
Only supported on Android
headerIconColor标题中显示的搜索和关闭图标的颜色
Only supported on Android
shouldShowHintSearchIcon聚焦搜索栏时是否显示搜索提示图标。默认为true
Only supported on Android
onBlur当搜索栏失去焦点时调用的回调
onCancelButtonPress按下取消按钮时调用的回调
onChangeText当文本更改时调用的回调。它接收搜索栏的当前文本值
header要使用的自定义标头,而不是默认标头
这接受一个函数,该函数返回一个React元素以显示为标头。函数接收一个包含以下属性的对象作为参数
statusBarAnimation设置状态栏动画(类似于StatusBar组件)。iOS上默认为淡入淡出,Android上默认为无
Supported values:
"fade" "none" "slide"
在Android上,设置淡入淡出或滑动将设置状态栏颜色的转换。在iOS上,此选项适用于状态栏的外观动画
statusBarHidden是否应在此屏幕上隐藏状态栏
页眉相关选项

您可以在此处找到与标头相关的选项列表。这些选项可以在Stack.navigator的screenOptions道具或Stack的options道具下指定。屏幕您不必直接使用@react navigation/elements来使用这些选项,它们只是在该页面中记录的。

除此之外,堆栈中还支持以下选项

header

要使用的自定义标头,而不是默认标头

这接受一个函数,该函数返回一个React元素以显示为标头。函数接收一个包含以下属性的对象作为参数

  • navigation-当前屏幕的导航对象。
  • route-当前屏幕的路由对象。
  • options-当前屏幕的选项
  • layout-屏幕的尺寸,包含高度和宽度属性。
  • progress表示动画进度的动画节点。
  • back-后退按钮的选项,包含一个具有用于后退按钮标签的title属性的对象。
  • styleInterpolator-返回表头中各种元素的插值样式的函数。

使用自定义标头时,请确保将headerMode设置为屏幕(有关更多详细信息,请参阅下文)

import { getHeaderTitle } from '@react-navigation/elements';

// ..

header: ({ navigation, route, options, back }) => {
  const title = getHeaderTitle(options, route.name);

  return (
    <MyHeader
      title={title}
      leftButton={
        back ? <MyBackButton onPress={navigation.goBack} /> : undefined
      }
      style={options.headerStyle}
    />
  );
};

要为导航器中的所有屏幕设置自定义标题,可以在导航器的screenOptions道具中指定此选项

使用自定义标头时,需要记住两件事

在页眉样式中指定高度以避免出现问题

如果您的收割台高度与默认收割台高度不同,那么您可能会注意到由于测量不同步而出现的故障。明确指定高度可以避免此类故障

headerStyle: {
  height: 80, // Specify the height of your custom header
};

请注意,默认情况下,此样式不会应用于页眉,因为您可以控制自定义页眉的样式。如果您还想将此样式应用于页眉,请使用属性中的headerStyle

detachInactiveScreens

用于指示是否应将非活动屏幕从视图层次结构中分离以节省内存的布尔值。这实现了与react原生屏幕的集成。默认为true。

如果您需要对特定屏幕禁用此优化(例如,您希望屏幕即使在未聚焦的情况下也能保持在视图中),请分离PreviousScreen选项。

Drawer Navigator 抽屉导航器

在这里插入图片描述

抽屉导航器在屏幕一侧显示一个导航抽屉,可以通过手势打开和关闭

这种包装反应了原生抽屉布局。如果你想在不集成React Navigation的情况下使用抽屉,可以直接使用库

Installation 安装

要使用此导航器,请确保您拥有@reactnavigation/native及其依赖项(请按照本指南进行操作),然后安装@reactnavigation/spauter:

npm install @react-navigation/drawer

您需要安装和配置抽屉导航器所需的库

首先,安装react原生手势处理程序,并重新激活react原生。
如果您有一个Expo管理的项目,请在项目目录中运行

npm install react-native-gesture-handler react-native-reanimated

抽屉支持Reanimated 1和最新版本的Reanimated。如果要使用最新版本的Reanimated,请确保按照安装指南进行配置。

为了完成react原生手势处理程序的安装,我们需要有条件地导入它。为此,创建2个文件

gesture-handler.native.js

import 'react-native-gesture-handler';

gesture-handler.js

// Don't import react-native-gesture-handler on web

现在,在入口文件的顶部添加以下内容(确保它在顶部,之前没有其他内容),例如index.js或App.js:

import './gesture-handler';

由于抽屉导航器在Web上不使用react原生手势处理程序,这避免了不必要地增加包大小

如果你在Mac上为iOS开发,你还需要安装Pod(通过Cocoapods)来完成链接

npx pod-install ios
API定义

要使用此抽屉导航器,请从@react-navigation/drawer导入:

import { createDrawerNavigator } from '@react-navigation/drawer';

const Drawer = createDrawerNavigator();

function MyDrawer() {
  return (
    <Drawer.Navigator>
      <Drawer.Screen name="Feed" component={Feed} />
      <Drawer.Screen name="Article" component={Article} />
    </Drawer.Navigator>
  );
}
Props 属性

抽屉导航器组件接受以下属性

id

导航器的可选唯一ID。这可以与navigation.getParent一起使用,以在子导航器中引用此导航器

initialRouteName

在导航器的第一次加载时渲染的路由名称

screenOptions

导航器中屏幕使用的默认选项。

backBehavior

这控制了在导航器中调用goBack时会发生什么。这包括在Android上按下设备的后退按钮或后退手势

它支持以下值

  • firstRoute-返回导航器中定义的第一个屏幕(默认)
  • initialRoute-返回initialRouteName道具中传递的初始屏幕,如果未传递,则默认为第一个屏幕
  • order-返回到聚焦屏幕之前定义的屏幕
  • history-返回导航器中上次访问的屏幕;如果多次访问同一屏幕,则旧条目将从历史记录中删除
  • none-不处理后退按钮
defaultStatus

抽屉的默认状态-默认情况下抽屉应保持打开还是关闭

当此设置为打开时,抽屉将从初始渲染开始打开。它可以通过手势或编程方式正常关闭。然而,当返回时,如果抽屉关闭,它将重新打开。这基本上与抽屉的默认行为相反,抽屉从关闭开始,后退按钮关闭打开的抽屉

detachInactiveScreens

布尔值,用于指示是否应将非活动屏幕从视图层次结构中分离以节省内存。这使得能够与react原生屏幕集成。默认为true。

useLegacyImplementation

是否使用基于Reanimated 1的遗留实现。基于Reanimated 2的新实现将表现更好,但您需要额外的配置,并且需要使用Hermes和Flipper进行调试

在以下情况下,默认为true:

  • 未配置Reanimated 2
  • 应用程序已连接到Chrome调试器(Reanimated 2不能与Chrome调试器一起使用)
  • 应用程序正在Web上运行

否则,它默认为false

drawerContent

返回React元素以呈现为抽屉内容的函数,例如导航项

默认情况下,内容组件接收以下属性

  • state-导航器的导航状态。
  • navigation-导航器的导航对象。
  • descriptors-一个包含抽屉屏幕选项的描述符对象。这些选项可以在描述符[route.key].options中访问

提供定制

抽屉的默认组件是可滚动的,只包含RouteConfig中路由的链接。您可以轻松覆盖默认组件,将页眉、页脚或其他内容添加到抽屉中。默认内容组件导出为DrawingContent。它在ScrollView中呈现DrawerItemList组件

默认情况下,抽屉是可滚动的,并支持带有凹口的设备。如果您自定义了内容,则可以使用DrawingContentScrollView自动处理此问题

import {
  DrawerContentScrollView,
  DrawerItemList,
} from '@react-navigation/drawer';

function CustomDrawerContent(props) {
  return (
    <DrawerContentScrollView {...props}>
      <DrawerItemList {...props} />
    </DrawerContentScrollView>
  );
}

要在抽屉中添加其他项目,可以使用DrawerItem组件

function CustomDrawerContent(props) {
  return (
    <DrawerContentScrollView {...props}>
      <DrawerItemList {...props} />
      <DrawerItem
        label="Help"
        onPress={() => Linking.openURL('https://mywebsite.com/help')}
      />
    </DrawerContentScrollView>
  );
}

DrawerItem组件接受以下属性

  • label(必填):项目的标签文本。可以是字符串,也可以是返回反应元素的函数。例如({聚焦,颜色})=><文本样式={颜色}>{聚焦?‘聚焦文本’:‘非聚焦文本’}。
  • icon:项目显示的图标。接受返回反应元素的函数。例如({聚焦,颜色,大小})=><图标颜色={颜色}大小={大小}名称={聚焦?‘心脏’:‘心脏轮廓’}/>。
  • focused:布尔值,指示是否将抽屉项目突出显示为活动状态。
  • onPress(必填):在印刷机上执行的功能。
  • activeTintColor:项目处于活动状态时图标和标签的颜色。
  • inactiveTintColor:项目处于非活动状态时图标和标签的颜色。
  • activeBackgroundColor:项目处于活动状态时的背景颜色。
  • inactiveBackgroundColor:项目处于非活动状态时的背景颜色。
  • labelStyle:标签Text的样式对象。
  • style:包装器视图的样式对象。

进度对象可用于在drawerCenter中制作有趣的动画,例如抽屉内容的视差运动

function CustomDrawerContent(props) {
  const progress = useDrawerProgress();

  // If you are on react-native-reanimated 1.x, use `Animated.interpolate` instead of `Animated.interpolateNode`
  const translateX = Animated.interpolateNode(progress, {
    inputRange: [0, 1],
    outputRange: [-100, 0],
  });

  return (
    <Animated.View style={{ transform: [{ translateX }] }}>
      {/* ... drawer contents */}
    </Animated.View>
  );
}

如果您使用的是Reanimated 1(请参阅useLegacyImplementation),则进度对象为Reanimated Node,否则为SharedValue。它表示抽屉的动画位置(0表示关闭;1表示打开)

请注意,由于useNavigation仅在屏幕内可用,因此您不能在drawerCenter内使用useNavigation挂钩。您可以为drawerCenter获得一个导航属性,您可以使用它

function CustomDrawerContent({ navigation }) {
  return (
    <Button
      title="Go somewhere"
      onPress={() => {
        // Navigate using the `navigation` prop that you received
        navigation.navigate('SomeScreen');
      }}
    />
  );
}

要使用自定义组件,我们需要将其传递到drawerContent属性中

<Drawer.Navigator drawerContent={(props) => <CustomDrawerContent {...props} />}>
  {/* screens */}
</Drawer.Navigator>
Options

以下选项可用于配置导航器中的屏幕。这些可以在Drawer.navigator的屏幕选项属性或Drawer的选项属性下指定。屏幕

title

一个通用标题,可以用作headerTitle和drawerLabel的后备

lazy

此屏幕是否应在首次访问时呈现。默认为true。如果要在初始渲染时渲染屏幕,请将其设置为false

drawerLabel

字符串或给定{focused:boolean,color:String}的函数返回React。节点,显示在抽屉侧边栏中。未定义时,使用场景标题

drawerIcon

给定{focused:boolean,color:string,size:number}的函数返回一个React。要在抽屉侧边栏中显示的节点。

drawerActiveTintColor

抽屉中活动项目的图标和标签的颜色

drawerActiveBackgroundColor

抽屉中活动项目的背景颜色

drawerInactiveTintColor

抽屉中非活动项目的图标和标签的颜色

drawerInactiveBackgroundColor

抽屉中未激活项目的背景颜色

drawerItemStyle

单个项目的样式对象,可以包含图标和/或标签

drawerLabelStyle

应用于呈现标签的内容部分内的文本样式的样式对象。

drawerContentContainerStyle

ScrollView中内容部分的样式对象

drawerContentStyle

包装器视图的样式对象。

drawerStyle

抽屉组件的样式对象。您可以在此处传递抽屉的自定义背景颜色或自定义宽度

<Drawer.Navigator
  screenOptions={{
    drawerStyle: {
      backgroundColor: '#c6cbef',
      width: 240,
    },
  }}
>
  {/* screens */}
</Drawer.Navigator>
drawerPosition

选项是左或右。LTR语言默认为左,RTL语言默认为右

drawerType

抽屉的类型。它决定了抽屉的外观和动画效果

  • front:传统的抽屉,用覆盖物覆盖屏幕。
  • back:滑动时抽屉会显示在屏幕后面。
  • slide:屏幕和抽屉在滑动时都会滑动,露出抽屉。
  • permanent:永久性抽屉显示为侧边栏。适用于在较大屏幕上始终可见的抽屉

默认情况下,在iOS上滑动,在其他平台上前置

您可以有条件地指定drawerType,以便在较大屏幕上显示永久抽屉,在较小屏幕上显示传统抽屉

import { useWindowDimensions } from 'react-native';
import { createDrawerNavigator } from '@react-navigation/drawer';

const Drawer = createDrawerNavigator();

function MyDrawer() {
  const dimensions = useWindowDimensions();

  return (
    <Drawer.Navigator
      screenOptions={{
        drawerType: dimensions.width >= 768 ? 'permanent' : 'front',
      }}
    >
      {/* Screens */}
    </Drawer.Navigator>
  );
}

您还可以根据屏幕大小指定其他属性,如drawerStyle,以自定义行为。例如,您可以将其与defaultStatus=“open”结合使用,以实现主细节布局

import { useWindowDimensions } from 'react-native';
import { createDrawerNavigator } from '@react-navigation/drawer';

const Drawer = createDrawerNavigator();

function MyDrawer() {
  const dimensions = useWindowDimensions();

  const isLargeScreen = dimensions.width >= 768;

  return (
    <Drawer.Navigator
      defaultStatus="open"
      screenOptions={{
        drawerType: isLargeScreen ? 'permanent' : 'back',
        drawerStyle: isLargeScreen ? null : { width: '100%' },
        overlayColor: 'transparent',
      }}
    >
      {/* Screens */}
    </Drawer.Navigator>
  );
}
drawerHideStatusBarOnOpen

当设置为true时,无论何时拉动抽屉或抽屉处于“打开”状态,Drawer都会隐藏操作系统状态栏

drawerStatusBarAnimation

隐藏状态栏时的动画。与hideStatusBar结合使用

支持的值

  • slide
  • fade
  • none

这仅在iOS上受支持。默认为滑动

overlayColor

抽屉打开时,内容视图顶部将显示颜色叠加。抽屉打开时,不透明度从0变为1

sceneContainerStyle

包装屏幕内容的组件的样式对象

gestureHandlerProps

传递给底层平移手势处理程序的属性。
Web不支持此功能

swipeEnabled

是否可以使用滑动手势打开或关闭抽屉。默认为true。
网络上不支持滑动手势

swipeEdgeWidth

允许定义滑动手势应激活的距离内容视图边缘的距离。
Web不支持此功能

swipeMinDistance

应激活打开抽屉的最小滑动距离阈值

keyboardDismissMode

滑动手势开始时是否应关闭键盘。默认为“拖动”。设置为“无”以禁用键盘处理

unmountOnBlur

离开此屏幕时是否应卸载此屏幕。卸载屏幕会重置屏幕中的任何本地状态以及屏幕中嵌套导航器的状态。默认为false

通常,我们不建议启用此属性,因为用户不希望在切换屏幕时丢失导航历史记录。如果您启用此属性,请考虑这是否真的会为用户提供更好的体验

freezeOnBlur

布尔值,指示是否阻止非活动屏幕重新渲染。默认为false。当在应用程序顶部运行react native screens包中的enableFreeze()时,默认为true

需要react原生屏幕版本>=3.16.0

仅支持iOS和Android

**Header related options **标题相关选项

您可以在此处找到与标题相关的选项列表。这些选项可以在Drawer.navigator的屏幕选项属性或Drawer的选项道具下指定。屏幕。您不必直接使用@reactnavigation/elements来使用这些选项,它们只是记录在该页面中

除此之外,抽屉还支持以下选项

header

使用自定义标头代替默认标头

这接受一个函数,该函数返回一个React元素作为头部显示。该函数接收一个包含以下属性的对象作为参数

  • navigation-当前屏幕的导航对象。
  • route-当前屏幕的路由对象。
  • options-当前屏幕的选项
  • layout-屏幕的尺寸,包含高度和宽度属性
import { getHeaderTitle } from '@react-navigation/elements';

// ..

header: ({ navigation, route, options }) => {
  const title = getHeaderTitle(options, route.name);

  return <MyHeader title={title} style={options.headerStyle} />;
};

要为导航器中的所有屏幕设置自定义标题,您可以在导航器的screenOptions道具中指定此选项

在headerStyle中指定高度

如果自定义标头的高度与默认标头高度不同,那么您可能会注意到由于测量异步而出现的故障。明确指定高度将避免此类故障

headerStyle: {
  height: 80, // Specify the height of your custom header
};

请注意,默认情况下,此样式不会应用于标题,因为您可以控制自定义标题的样式。如果您还想将此样式应用于页眉,请使用props中的options.headerStyle

headerShown

是否显示或隐藏屏幕的标题。默认情况下显示标题。将其设置为false会隐藏标题

Events 事件

导航器可以在某些操作上发出事件。支持的事件有

drawerItemPress

当用户按下抽屉中的屏幕按钮时,会触发此事件。默认情况下,抽屉物品按压器会做几件事

  • 如果屏幕未聚焦,抽屉项目按压将聚焦该屏幕
  • 如果屏幕已经聚焦,那么它将关闭抽屉

为了防止默认行为,您可以调用event.preventDefault

React.useEffect(() => {
  const unsubscribe = navigation.addListener('drawerItemPress', (e) => {
    // Prevent default behavior
    e.preventDefault();

    // Do something manually
    // ...
  });

  return unsubscribe;
}, [navigation]);

果您有自定义抽屉内容,请确保发出此事件

Helpers 助手

抽屉导航器将以下方法添加到导航属性中

openDrawer

打开抽屉窗格

navigation.openDrawer();
closeDrawer

关闭抽屉窗格

navigation.closeDrawer();
toggleDrawer

如果关闭,则打开抽屉窗格;如果打开,则关闭抽屉窗格

navigation.toggleDrawer();
jumpTo

在抽屉导航器中导航到现有屏幕。该方法接受以下参数

name - string 要跳转到的路由名称

params - object 传递到目的地路由的屏幕参数

navigation.jumpTo('Profile', { owner: 'Satya' });

示例

import { createDrawerNavigator } from '@react-navigation/drawer';

const Drawer = createDrawerNavigator();

function MyDrawer() {
  return (
    <Drawer.Navigator initialRouteName="Feed">
      <Drawer.Screen
        name="Feed"
        component={Feed}
        options={{ drawerLabel: 'Home' }}
      />
      <Drawer.Screen
        name="Notifications"
        component={Notifications}
        options={{ drawerLabel: 'Updates' }}
      />
      <Drawer.Screen
        name="Profile"
        component={Profile}
        options={{ drawerLabel: 'Profile' }}
      />
    </Drawer.Navigator>
  );
}
检查抽屉是否打开

您可以使用useDrawerStatus挂钩检查抽屉是否打开

import { useDrawerStatus } from '@react-navigation/drawer';

// ...

const isDrawerOpen = useDrawerStatus() === 'open';

如果你不能使用钩子,你也可以使用getDrawerStatusFromState帮助程序

import { getDrawerStatusFromState } from '@react-navigation/drawer';

// ...

const isDrawerOpen = getDrawerStatusFromState(navigation.getState()) === 'open';

对于类组件,您可以监听状态事件以检查抽屉是打开还是关闭的

class Profile extends React.Component {
  componentDidMount() {
    this._unsubscribe = navigation.addListener('state', () => {
      const isDrawerOpen =
        getDrawerStatusFromState(navigation.getState()) === 'open';

      // do something
    });
  }

  componentWillUnmount() {
    this._unsubscribe();
  }

  render() {
    // Content of the component
  }
}

将抽屉导航器嵌套在其他抽屉中

如果抽屉导航器嵌套在提供某些UI的另一个导航器中,例如选项卡导航器或堆栈导航器,那么抽屉将在这些导航器的UI下方呈现。抽屉将出现在标签栏下方和堆栈标题下方。您需要将抽屉导航器设置为任何导航器的父级,抽屉应在其UI之上呈现

Bottom Tabs Navigator 底部选项卡导航器

在这里插入图片描述

屏幕底部的一个简单标签栏,可让您在不同路由之间切换。路由是延迟初始化的——在首次聚焦之前,它们的屏幕组件不会被挂载

安装
npm install @react-navigation/bottom-tabs
API定义

要使用此选项卡导航器,请从@reactnavigation/底部选项卡导入

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Tab = createBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}
属性

Tab.Navigator组件接受以下属性

id

导航器的可选唯一ID。这可以与navigation.getParent一起使用,以在子导航器中引用此导航器

initialRouteName

首次加载导航器时要渲染的路由名称

screenOptions

导航器中屏幕使用的默认选项

backBehavior

这控制了在导航器中调用goBack时会发生什么。这包括在Android上按下设备的后退按钮或后退手势

它支持以下值

firstRoute - 返回导航器中定义的第一个屏幕(默认)

initialRoute - 返回initialRouteName prop中传递的初始屏幕,如果未传递,则默认为第一个屏幕

order - 返回到聚焦屏幕之前定义的屏幕

history - 返回导航器中上次访问的屏幕;如果多次访问同一屏幕,则旧条目将从历史记录中删除

none - 不要操作后退按钮

detachInactiveScreen

布尔值,用于指示是否应将非活动屏幕从视图层次结构中分离以节省内存。这使得能够与react原生屏幕集成。默认为true

sceneContainerStyle

包装屏幕内容的组件的样式对象

tabBar

返回一个React元素以显示为标签栏的函数

示例

import { View, Text, TouchableOpacity } from 'react-native';

function MyTabBar({ state, descriptors, navigation }) {
  return (
    <View style={{ flexDirection: 'row' }}>
      {state.routes.map((route, index) => {
        const { options } = descriptors[route.key];
        const label =
          options.tabBarLabel !== undefined
            ? options.tabBarLabel
            : options.title !== undefined
            ? options.title
            : route.name;

        const isFocused = state.index === index;

        const onPress = () => {
          const event = navigation.emit({
            type: 'tabPress',
            target: route.key,
            canPreventDefault: true,
          });

          if (!isFocused && !event.defaultPrevented) {
            navigation.navigate(route.name, route.params);
          }
        };

        const onLongPress = () => {
          navigation.emit({
            type: 'tabLongPress',
            target: route.key,
          });
        };

        return (
          <TouchableOpacity
            accessibilityRole="button"
            accessibilityState={isFocused ? { selected: true } : {}}
            accessibilityLabel={options.tabBarAccessibilityLabel}
            testID={options.tabBarTestID}
            onPress={onPress}
            onLongPress={onLongPress}
            style={{ flex: 1 }}
          >
            <Text style={{ color: isFocused ? '#673ab7' : '#222' }}>
              {label}
            </Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
}

// ...

<Tab.Navigator tabBar={props => <MyTabBar {...props} />}>
  {...}
</Tab.Navigator>

此示例将呈现一个带有标签的基本选项卡栏

请注意,您不能在tabBar中使用useNavigation钩子,因为useNavigation仅在屏幕内可用。你会得到一个tabBar的导航属性,你可以用它来代替

function MyTabBar({ navigation }) {
  return (
    <Button
      title="Go somewhere"
      onPress={() => {
        // Navigate using the `navigation` prop that you received
        navigation.navigate('SomeScreen');
      }}
    />
  );
}
选项

以下选项可用于配置导航器中的屏幕。这些可以在Tab.navigator的screenOptions属性或Tab.Screen的options属性下指定

title

可用作headerTitle和tabBarLabel回退的通用标题

tabBarLabel

选项卡栏中显示的选项卡的标题字符串或给定{focused:boolean,color:string}的函数返回React。节点,显示在选项卡栏中。如果未定义,则使用场景标题。要隐藏,请参见选项卡BarShowLabel

tabBarShowLabel

标签是否应可见。默认为true

tabBarLabelPosition

标签是显示在图标下方还是图标旁边

below-icon: 标签显示在图标下方(iPhone的典型标签)

beside-icon: 标签显示在图标旁边(iPad的典型标签)

默认情况下,位置会根据设备宽度自动选择

tabBarLabelStyle

选项卡标签的样式对象

tabBarIcon

给定{focused:boolean,color:string,size:number}的函数返回一个React。节点,显示在选项卡栏中。

tabBarIconStyle

选项卡图标的样式对象

tabBarBadge

在标签图标上的徽章中显示的文本。接受字符串或数字

tabBarBadgeStyle

标签图标上徽章的样式。您可以在此处指定背景颜色或文本颜色

tabBarAccessibilityLabel

选项卡按钮的辅助功能标签。当用户点击标签时,屏幕阅读器会读取此内容。如果您没有标签,建议设置此内容

tabBarTestID

在测试中定位此选项卡按钮的ID

tabBarButton

返回一个React元素以呈现为选项卡栏按钮的函数。它包裹图标和标签。默认情况下渲染为可按压

您可以在此处指定自定义实现

tabBarButton: (props) => <TouchableOpacity {...props} />;
tabBarActiveTintColor

活动选项卡中图标和标签的颜色

tabBarInactiveTintColor

非活动选项卡中图标和标签的颜色

tabBarActiveBackgroundColor

活动选项卡的背景颜色

tabBarInactiveBackgroundColor

非活动选项卡的背景颜色

tabBarHideOnKeyboard

键盘打开时标签栏是否隐藏。默认为false

tabBarItemStyle

选项卡项容器的样式对象

tabBarStyle

标签栏的样式对象。您可以在此处配置背景颜色等样式。
要在标签栏下显示屏幕,可以将位置样式设置为绝对

<Tab.Navigator
  screenOptions={{
    tabBarStyle: { position: 'absolute' },
  }}
>

如果你有一个绝对定位的标签栏,你可能还需要为你的内容添加一个底部边距。React Navigation不会自动执行此操作

要获得底部选项卡栏的高度,可以将BottomTabBarHeightContext与React的Context API一起使用,或者使用BottomTabbarHeight

import { BottomTabBarHeightContext } from '@react-navigation/bottom-tabs';

// ...

<BottomTabBarHeightContext.Consumer>
  {tabBarHeight => (
    /* render something */
  )}
</BottomTabBarHeightContext.Consumer>

or

import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';

// ...

const tabBarHeight = useBottomTabBarHeight();
tabBarBackground

返回一个React元素作为标签栏背景的函数。你可以渲染图像、渐变、模糊视图等

import { BlurView } from 'expo-blur';

// ...

<Tab.Navigator
  screenOptions={{
    tabBarStyle: { position: 'absolute' },
    tabBarBackground: () => (
      <BlurView tint="light" intensity={100} style={StyleSheet.absoluteFill} />
    ),
  }}
>

使用BlurView时,请确保在tabBarStyle中设置position:“absolute”。您还需要使用useBottomTabBarHeight()为您的内容添加底部填充

lazy

此屏幕是否应在首次访问时呈现。默认为true。如果要在初始渲染时渲染屏幕,请将其设置为false

unmountOnBlur

离开此屏幕时是否应卸载此屏幕。卸载屏幕会重置屏幕中的任何本地状态以及屏幕中嵌套导航器的状态。默认为false。

通常,我们不建议启用此属性,因为用户不希望在切换选项卡时丢失导航历史记录。如果您启用此属性,请考虑这是否真的会为用户提供更好的体验。

freezeOnBlur

布尔值,指示是否阻止非活动屏幕重新渲染。默认为false。当在应用程序顶部运行react native screens包中的enableFreeze()时,默认为true

react-native-screens 版本>=3.16.0

仅支持iOS和Android

标题相关选项

您可以在此处找到与标题相关的选项列表。这些选项可以在Tab.navigator的screenOptions属性或Tab.Screen的options属性下指定。您不必直接使用@reactnavigation/elements来使用这些选项,它们只是记录在该页面中

除此之外,底部选项卡还支持以下选项

header

使用自定义标头代替默认标头

这接受一个函数,该函数返回一个React元素作为头部显示。该函数接收一个包含以下属性的对象作为参数

navigation - 当前屏幕的导航对象

route - 当前屏幕的路由对象

options - 当前屏幕的选项

layout - 屏幕的尺寸,包含高度和宽度属性

例子:

import { getHeaderTitle } from '@react-navigation/elements';

// ..

header: ({ navigation, route, options }) => {
  const title = getHeaderTitle(options, route.name);

  return <MyHeader title={title} style={options.headerStyle} />;
};

要为导航器中的所有屏幕设置自定义标题,您可以在导航器的screenOptions属性中指定此选项

Specify a height in headerStyle

如果自定义标头的高度与默认标头高度不同,那么您可能会注意到由于测量异步而导致的故障。明确指定高度将避免此类故障

例子:

headerStyle: {
  height: 80, // Specify the height of your custom header
};

请注意,默认情况下,此样式不会应用于标题,因为您可以控制自定义标题的样式。如果您还想将此样式应用于页眉,请使用props中的options.headerStyle

headerShown

是否显示或隐藏屏幕的标题。默认情况下显示标题。将其设置为false会隐藏标题

事件

导航器可以在某些操作上发出事件。支持的活动有

tabPress

当用户按下选项卡栏中当前屏幕的选项卡按钮时,会触发此事件。默认情况下,按tab键可以做几件事

如果标签未聚焦,按下标签将聚焦该标签

如果选项卡已聚焦

  • 如果选项卡的屏幕呈现滚动视图,则可以使用ScrollToTop将其滚动到顶部
  • 如果选项卡的屏幕显示堆栈导航器,则对堆栈执行popToTop操作

要防止默认行为,您可以调用 event.preventDefault:

React.useEffect(() => {
  const unsubscribe = navigation.addListener('tabPress', (e) => {
    // Prevent default behavior
    e.preventDefault();

    // Do something manually
    // ...
  });

  return unsubscribe;
}, [navigation]);

如果您有自定义选项卡栏,请确保发出此事件

tabLongPress

当用户长时间按下标签栏中当前屏幕的标签按钮时,会触发此事件。如果您有自定义选项卡栏,请确保发出此事件

例子:

React.useEffect(() => {
  const unsubscribe = navigation.addListener('tabLongPress', (e) => {
    // Do something
  });

  return unsubscribe;
}, [navigation]);
帮助

选项卡导航器将以下方法添加到导航属性中

jumpTo

导航到选项卡导航器中的现有屏幕。该方法接受以下参数

name - string 要跳转到的路由名称

params - *object *用于目的地路由的屏幕参数

navigation.jumpTo('Profile', { owner: 'Michaś' });
例子
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';

const Tab = createBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator
      initialRouteName="Feed"
      screenOptions={{
        tabBarActiveTintColor: '#e91e63',
      }}
    >
      <Tab.Screen
        name="Feed"
        component={Feed}
        options={{
          tabBarLabel: 'Home',
          tabBarIcon: ({ color, size }) => (
            <MaterialCommunityIcons name="home" color={color} size={size} />
          ),
        }}
      />
      <Tab.Screen
        name="Notifications"
        component={Notifications}
        options={{
          tabBarLabel: 'Updates',
          tabBarIcon: ({ color, size }) => (
            <MaterialCommunityIcons name="bell" color={color} size={size} />
          ),
          tabBarBadge: 3,
        }}
      />
      <Tab.Screen
        name="Profile"
        component={Profile}
        options={{
          tabBarLabel: 'Profile',
          tabBarIcon: ({ color, size }) => (
            <MaterialCommunityIcons name="account" color={color} size={size} />
          ),
        }}
      />
    </Tab.Navigator>
  );
}

Material Bottom Tabs Navigator 物理底部选项卡导航器

屏幕底部的一个以材质设计为主题的标签栏,允许您通过动画在不同路线之间切换。路由被延迟初始化-在首次聚焦之前,它们的屏幕组件不会被挂载

安装
npm install @react-navigation/material-bottom-tabs react-native-paper react-native-vector-icons
API定义

如果您在使用createMaterialBottomTabNavigator时遇到任何错误,请在react原生论文而不是react导航存储库上打开问题

要使用此选项卡导航器,请从以下位置导入 @react-navigation/material-bottom-tabs

import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';

const Tab = createMaterialBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}
路由配置

route-configs对象是从路由名称到路由配置的映射

属性

Tab.Navigator组件接受以下属性

id

导航器的可选唯一ID。这可以与navigation.getParent一起使用,以在子导航器中引用此导航器。

initialRouteName

首次加载导航器时要渲染的路由名称

screenOptions

导航器中屏幕使用的默认选项

backBehavior

这控制了在导航器中调用goBack时会发生什么。这包括在Android上按下设备的后退按钮或后退手势

It supports the following values

firstRoute - 返回导航器中定义的第一个屏幕(默认)

initialRoute - 返回initialRouteName prop中传递的初始屏幕,如果未传递,则默认为第一个屏幕

order - 返回到聚焦屏幕之前定义的屏幕

history - 返回导航器中上次访问的屏幕;如果多次访问同一屏幕,则旧条目将从历史记录中删除

none - 不要操作后退按钮

shifting

无论是否使用移动样式,活动选项卡图标都会向上移动以显示标签,而非活动选项卡将没有标签

默认情况下,当您有3个以上的选项卡时,这是正确的。传递shift={false}以显式禁用此动画,或传递shift={true}以始终使用此动画

labeled

是否在选项卡中显示标签。如果为false,则只显示图标

activeColor

活动选项卡中图标和标签的自定义颜色

inactiveColor

非活动选项卡中图标和标签的自定义颜色

barStyle

底部导航栏的样式。您可以在此处传递自定义背景颜色

<Tab.Navigator
  initialRouteName="Home"
  activeColor="#f0edf6"
  inactiveColor="#3e2465"
  barStyle={{ backgroundColor: '#694fad' }}
>
  {/* ... */}
</Tab.Navigator>

如果你在Android上有一个半透明的导航栏,你也可以在这里设置底部填充

<Tab.Navigator
  initialRouteName="Home"
  activeColor="#f0edf6"
  inactiveColor="#3e2465"
  barStyle={{ paddingBottom: 48 }}
>
  {/* ... */}
</Tab.Navigator>
选项

以下选项可用于配置导航器中的屏幕

title

可用作headerTitle和tabBarLabel回退的通用标题

tabBarIcon

给定{focused:boolean,color:string}的函数返回一个React。节点,显示在选项卡栏中

tabBarLabel

选项卡栏中显示的选项卡的标题字符串。如果未定义,则使用场景标题。要隐藏,请参阅上一节中的标记选项

tabBarBadge

徽章显示在标签图标上,可以是true显示点、字符串或数字显示文本

tabBarAccessibilityLabel

选项卡按钮的辅助功能标签。当用户点击标签时,屏幕阅读器会读取此内容。如果您没有标签,建议设置此内容

tabBarTestID

在测试中定位此选项卡按钮的ID

事件

导航器可以在某些操作上发出事件。支持的活动有

tabPress

当用户按下选项卡栏中当前屏幕的选项卡按钮时,会触发此事件。默认情况下,按tab键可以做几件事

如果选项卡未聚焦,按下选项卡将聚焦该选项卡

如果选项卡已聚焦

  • 如果选项卡的屏幕呈现滚动视图,则可以使用ScrollToTop将其滚动到顶部
  • 如果选项卡的屏幕显示堆栈导航器,则对堆栈执行popToTop操作

要防止默认行为,您可以调用event.preventDefault

React.useEffect(() => {
  const unsubscribe = navigation.addListener('tabPress', (e) => {
    // Prevent default behavior

    e.preventDefault();
    // Do something manually
    // ...
  });

  return unsubscribe;
}, [navigation]);
帮助

选项卡导航器将以下方法添加到导航属性中

jumpTo

导航到选项卡导航器中的现有屏幕。该方法接受以下参数

name - string - 要跳转到的路由名称

params - object - 传递到目的地路线的屏幕参数

navigation.jumpTo('Profile', { name: 'Michaś' });
例子
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';

const Tab = createMaterialBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator
      initialRouteName="Feed"
      activeColor="#e91e63"
      barStyle={{ backgroundColor: 'tomato' }}
    >
      <Tab.Screen
        name="Feed"
        component={Feed}
        options={{
          tabBarLabel: 'Home',
          tabBarIcon: ({ color }) => (
            <MaterialCommunityIcons name="home" color={color} size={26} />
          ),
        }}
      />
      <Tab.Screen
        name="Notifications"
        component={Notifications}
        options={{
          tabBarLabel: 'Updates',
          tabBarIcon: ({ color }) => (
            <MaterialCommunityIcons name="bell" color={color} size={26} />
          ),
        }}
      />
      <Tab.Screen
        name="Profile"
        component={Profile}
        options={{
          tabBarLabel: 'Profile',
          tabBarIcon: ({ color }) => (
            <MaterialCommunityIcons name="account" color={color} size={26} />
          ),
        }}
      />
    </Tab.Navigator>
  );
}

Material Top Tabs Navigator 物理顶部选项卡导航器

在这里插入图片描述

屏幕顶部以材质设计为主题的标签栏,您可以通过点击标签或水平滑动在不同路线之间切换。默认情况下,过渡是动画化的。立即安装每条路线的屏幕组件

这封装了react原生选项卡视图。如果你想在不集成React Navigation的情况下使用选项卡视图,可以直接使用库

安装
npm install @react-navigation/material-top-tabs react-native-tab-view
API定义

要使用此选项卡导航器,请从以下位置导入 @react-navigation/material-top-tabs:

import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';

const Tab = createMaterialTopTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}
属性

Tab.Navigator组件接受以下属性

id

导航器的可选唯一ID。这可以与navigation.getParent一起使用,以在子导航器中引用此导航器

initialRouteName

首次加载导航器时要渲染的路由名称

screenOptions

导航器中屏幕使用的默认选项

backBehavior

这控制了在导航器中调用goBack时会发生什么。这包括在Android上按下设备的后退按钮或后退手势

它支持以下值

firstRoute - 返回导航器中定义的第一个屏幕(默认)

initialRoute - 返回initialRouteName prop中传递的初始屏幕,如果未传递,则默认为第一个屏幕

order - 返回到聚焦屏幕之前定义的屏幕

history - 返回导航器中上次访问的屏幕;如果多次访问同一屏幕,则旧条目将从历史记录中删除

none - 不要操作后退按钮

tabBarPosition

选项卡视图中选项卡栏的位置。可能的值是“top”和“bottom"。默认为“top”。

keyboardDismissMode

指示键盘是否因拖动手势而关闭的字符串。可能的值有

‘auto’ (默认):当索引更改时,键盘将被关闭

‘on-drag’ 当拖动开始时,键盘被关闭

‘none’ 拖动不会关闭键盘

initialLayout

包含屏幕初始高度和宽度的对象。传递此参数将提高初始渲染性能。对于大多数应用程序来说,这是一个很好的默认设置

{
  width: Dimensions.get('window').width;
}
sceneContainerStyle

应用于包裹每个屏幕的视图的样式。您可以传递此值以覆盖一些默认样式,如溢出剪裁

style

Style to apply to the tab view container

tabBar

返回一个React元素以显示为标签栏的函数

例子:

import { Animated, View, TouchableOpacity } from 'react-native';

function MyTabBar({ state, descriptors, navigation, position }) {
  return (
    <View style={{ flexDirection: 'row' }}>
      {state.routes.map((route, index) => {
        const { options } = descriptors[route.key];
        const label =
          options.tabBarLabel !== undefined
            ? options.tabBarLabel
            : options.title !== undefined
            ? options.title
            : route.name;

        const isFocused = state.index === index;

        const onPress = () => {
          const event = navigation.emit({
            type: 'tabPress',
            target: route.key,
            canPreventDefault: true,
          });

          if (!isFocused && !event.defaultPrevented) {
            navigation.navigate(route.name, route.params);
          }
        };

        const onLongPress = () => {
          navigation.emit({
            type: 'tabLongPress',
            target: route.key,
          });
        };

        const inputRange = state.routes.map((_, i) => i);
        const opacity = position.interpolate({
          inputRange,
          outputRange: inputRange.map(i => (i === index ? 1 : 0)),
        });

        return (
          <TouchableOpacity
            accessibilityRole="button"
            accessibilityState={isFocused ? { selected: true } : {}}
            accessibilityLabel={options.tabBarAccessibilityLabel}
            testID={options.tabBarTestID}
            onPress={onPress}
            onLongPress={onLongPress}
            style={{ flex: 1 }}
          >
            <Animated.Text style={{ opacity }}>
              {label}
            </Animated.Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
}

// ...

<Tab.Navigator tabBar={props => <MyTabBar {...props} />}>
  {...}
</Tab.Navigator>

此示例将呈现一个带有标签的基本选项卡栏

请注意,您不能在tabBar中使用useNavigation钩子,因为useNavigation仅在屏幕内可用。你会得到一个tabBar的导航属性,你可以用它来代

function MyTabBar({ navigation }) {
  return (
    <Button
      title="Go somewhere"
      onPress={() => {
        // Navigate using the `navigation` prop that you received
        navigation.navigate('SomeScreen');
      }}
    />
  );
}
选项

以下选项可用于配置导航器中的屏幕

<Tab.Navigator
  screenOptions={{
    tabBarLabelStyle: { fontSize: 12 },
    tabBarItemStyle: { width: 100 },
    tabBarStyle: { backgroundColor: 'powderblue' },
  }}
>
  {/* ... */}
</Tab.Navigator>
title

可用作headerTitle和tabBarLabel回退的通用标题

tabBarLabel

选项卡栏中显示的选项卡的标题字符串或给定{focused:boolean,color:string}的函数返回React。节点,显示在选项卡栏中。如果未定义,则使用场景标题。要隐藏,请参见tabBarShowLabel选项

tabBarAccessibilityLabel

选项卡按钮的辅助功能标签。当用户点击标签时,屏幕阅读器会读取此内容。如果您没有标签,建议设置此内容

tabBarAllowFontScaling

标签字体是否应缩放以符合“文本大小”辅助功能设置

tabBarShowLabel

标签是否应可见。默认为true

tabBarIcon

给定{focused:boolean,color:string}的函数返回一个React节点,显示在选项卡栏中

tabBarShowIcon

选项卡图标是否应可见。默认为false。

tabBarBadge

返回一个React元素作为标签的徽章的函数

tabBarIndicator

返回React元素作为标签栏指示器的函数

tabBarIndicatorStyle

标签栏指示器的样式对象

tabBarIndicatorContainerStyle

包含选项卡栏指示器的视图的样式对象。

tabBarTestID

在测试中定位此选项卡按钮的ID

tabBarActiveTintColor

活动选项卡中图标和标签的颜色

tabBarInactiveTintColor

非活动选项卡中图标和标签的颜色

tabBarGap

选项卡栏中选项卡项之间的间距

<Tab.Navigator
  //...
  screenOptions={{
    tabBarGap: 10,
  }}
></Tab.Navigator>
tabBarAndroidRipple

允许自定义安卓涟漪效果

<Tab.Navigator
  //...
  screenOptions={{
    tabBarAndroidRipple: { borderless: false },
  }}
></Tab.Navigator>
tabBarPressColor

材质波纹颜色

仅安卓支持

tabBarPressOpacity

按下标签的不透明度

仅在iOS上受支持

tabBarBounces

布尔值,指示标签栏在过度滚动时是否反弹

tabBarScrollEnabled

布尔值,指示是否使标签栏可滚动

如果将此设置为true,还应在tabBarItemStyle中指定宽度,以提高初始渲染的性能

tabBarIconStyle

选项卡图标容器的样式对象

tabBarLabelStyle

选项卡标签的样式对象

tabBarItemStyle

单个选项卡项的样式对象

tabBarContentContainerStyle

包含选项卡项的视图的样式对象

tabBarStyle

标签栏的样式对象

swipeEnabled

布尔值,指示是否启用滑动手势。默认情况下启用滑动手势。传递false将禁用滑动手势,但用户仍然可以通过按标签栏切换标签

lazy

是否应延迟渲染此屏幕。当此设置为true时,屏幕将在进入视口时呈现。默认情况下,所有屏幕都会渲染以提供更流畅的滑动体验。但是,您可能希望将屏幕的渲染推迟到视口之外,直到用户看到它们。要为此屏幕启用延迟渲染,请将lazy设置为true

当您启用懒惰时,懒惰加载的屏幕在进入视口时通常需要一些时间来渲染。您可以使用lazyPlaceholder属性来定制用户在这段短时间内看到的内容

lazyPreloadDistance

启用lazy后,您可以使用此属性指定应提前预加载多少个相邻屏幕。此值默认为0,这意味着延迟页面在进入视口时会被加载

lazyPlaceholder

如果此屏幕尚未渲染,则返回一个React元素进行渲染的函数。lazy选项也需要启用才能正常工作

此视图通常只显示一瞬间。保持轻便。

默认情况下,这将呈现null

事件

导航器可以在某些操作上发出事件。支持的活动包括

tabPress

当用户按下选项卡栏中当前屏幕的选项卡按钮时,会触发此事件。默认情况下,按tab键可以做几件事

如果选项卡未聚焦,按下选项卡将聚焦该选项卡

如果选项卡已聚焦

  • 如果选项卡的屏幕呈现滚动视图,则可以使用ScrollToTop将其滚动到顶部
  • 如果选项卡的屏幕显示堆栈导航器,则对堆栈执行popToTop操作

要防止默认行为,您可以调用 event.preventDefault:

React.useEffect(() => {
  const unsubscribe = navigation.addListener('tabPress', (e) => {
    // Prevent default behavior
    e.preventDefault();

    // Do something manually
    // ...
  });

  return unsubscribe;
}, [navigation]);
tabLongPress

当用户长时间按下标签栏中当前屏幕的标签按钮时,会触发此事件。

React.useEffect(() => {
  const unsubscribe = navigation.addListener('tabLongPress', (e) => {
    // Do something
  });

  return unsubscribe;
}, [navigation]);
帮助

选项卡导航器将以下方法添加到导航属性中

jumpTo

导航到选项卡导航器中的现有屏幕。该方法接受以下参数

name - string - 要跳转到的路由名称

params - object 传递到目的地路由的屏幕参数

navigation.jumpTo('Profile', { name: 'Michaś' });
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';

const Tab = createMaterialTopTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator
      initialRouteName="Feed"
      screenOptions={{
        tabBarActiveTintColor: '#e91e63',
        tabBarLabelStyle: { fontSize: 12 },
        tabBarStyle: { backgroundColor: 'powderblue' },
      }}
    >
      <Tab.Screen
        name="Feed"
        component={Feed}
        options={{ tabBarLabel: 'Home' }}
      />
      <Tab.Screen
        name="Notifications"
        component={Notifications}
        options={{ tabBarLabel: 'Updates' }}
      />
      <Tab.Screen
        name="Profile"
        component={Profile}
        options={{ tabBarLabel: 'Profile' }}
      />
    </Tab.Navigator>
  );
}

libraries 库

Developer tools 开发人员工具

使用React Navigation时使调试更容易的开发工具

npm install @react-navigation/devtools

此包中的钩子仅在开发期间有效,在生产中禁用。您不需要做任何特殊的事情就可以将它们从生产构建中删除

API定义

该包公开了以下API

useFlipper

此钩子为React Native应用程序提供了与Flipper的集成

要使用这个钩子,你需要

  • 如果尚未配置Flipper,请在React Native应用程序中进行配置
  • 在您的应用程序中安装react native flipper包
npm install --save-dev react-native-flipper
  • 在Flipper应用程序中安装反应导航插件
import * as React from 'react';
import {
  NavigationContainer,
  useNavigationContainerRef,
} from '@react-navigation/native';
import { useFlipper } from '@react-navigation/devtools';

export default function App() {
  const navigationRef = useNavigationContainerRef();

  useFlipper(navigationRef);

  return (
    <NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
  );
}

现在,只要您的设备连接到Flipper,您就可以在Flipper中使用React Navigation开发工具

API reference API参考

NavigationContainer 导航容器

NavigationContainer负责管理您的应用程序状态,并将您的顶级导航器链接到应用程序环境

容器负责特定于平台的集成,并提供各种有用的功能

  • 与链接属性的深度链接集成。
  • 通知屏幕跟踪、状态持久性等的状态更改。
  • 使用React Native的BackHandler API在Android上处理系统后退按钮。
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>{/* ... */}</Stack.Navigator>
    </NavigationContainer>
  );
}
Ref

还可以将ref附加到容器以访问各种辅助方法,例如调度导航操作。在极少数情况下,当您无法访问导航属性(如Redux中间件)时,应使用此选项

import {
  NavigationContainer,
  useNavigationContainerRef,
} from '@react-navigation/native';

function App() {
  const navigationRef = useNavigationContainerRef(); // You can also use a regular ref with `React.useRef()`

  return (
    <View style={{ flex: 1 }}>
      <Button onPress={() => navigationRef.navigate('Home')}>Go home</Button>
      <NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
    </View>
  );
}

如果您使用的是常规ref对象,请记住,在某些情况下(例如启用链接时),ref最初可能为null。为了确保ref已初始化,可以使用onReady回调在导航容器完成装载时获得通知

Methods on the ref

ref对象包括所有常见的导航方法,如navigation、goBack等。有关更多详细信息,请参阅CommonActions文档

navigationRef.navigate(name, params);

所有这些方法都将在当前聚焦的屏幕内调用。重要的是,必须有一个导航器来处理这些操作

除了这些方法,ref对象还包括以下特殊方法

isReady

isReady方法返回一个布尔值,指示导航树是否已就绪。当NavigationContainer至少包含一个导航器并且所有导航器都已完成安装时,导航树就准备好了

这可用于确定在不出错的情况下调度导航操作是否安全。有关更多详细信息,请参阅处理初始化

resetRoot

resetRoot方法允许您将导航树的状态重置为指定的状态对象

navigationRef.resetRoot({
  index: 0,
  routes: [{ name: 'Profile' }],
});

与重置方法不同,这作用于根导航器,而不是当前聚焦屏幕的导航器

getRootState

getRootState方法返回一个导航状态对象,其中包含导航树中所有导航器的导航状态

const state = navigationRef.getRootState();

请注意,如果当前没有呈现导航器,则返回的状态对象将是未定义的

getCurrentRoute

getCurrentRoute方法返回整个导航树中当前聚焦屏幕的路线对象

const route = navigationRef.getCurrentRoute();

请注意,如果当前没有呈现导航器,则返回的路由对象将是未定义的

getCurrentOptions

getCurrentOptions方法返回整个导航树中当前聚焦屏幕的选项

const options = navigationRef.getCurrentOptions();

请注意,如果当前没有呈现导航器,则返回的选项对象将是未定义的

addListener

addListener方法允许您监听以下事件

state

每当导航树中的任何导航器中的导航状态发生变化时,都会触发该事件

const unsubscribe = navigationRef.addListener('state', (e) => {
  // You can get the raw navigation state (partial state object of the root navigator)
  console.log(e.data.state);

  // Or get the full state object with `getRootState()`
  console.log(navigationRef.getRootState());
});

这类似于onStateChange方法。唯一的区别是e.data.state对象可能包含部分状态对象,这与onStateChange中的state参数不同,后者将始终包含完整状态对象

options

每当导航树中当前聚焦屏幕的选项发生变化时,就会触发该事件

const unsubscribe = navigationRef.addListener('options', (e) => {
  // You can get the new options for the currently focused screen
  console.log(e.data.options);
});
属性props
initialState

接受导航器初始状态的属性。这对于深度链接、状态持久性等情况非常有用

<NavigationContainer initialState={initialState}>
  {/* ... */}
</NavigationContainer>

提供自定义初始状态对象将覆盖通过链接配置或从浏览器URL获得的初始状态对象。如果你提供了一个初始状态对象,请确保你没有在网络上传递它,也没有需要处理的深度链接

onStateChange

每次导航状态更改时调用的函数。它接收新的导航状态作为参数

您可以使用它来跟踪聚焦屏幕、保持导航状态等

<NavigationContainer
  onStateChange={(state) => console.log('New state is', state)}
>
  {/* ... */}
</NavigationContainer>
onReady

在导航容器及其所有子容器首次完成安装后调用的函数。你可以用它

  • 确保ref可用。有关更多详细信息,请参阅有关ref初始化的文档。
    隐藏您的原生启动画面
<NavigationContainer
  onReady={() => console.log('Navigation container is ready')}
>
  {/* ... */}
</NavigationContainer>

ServerContainer 服务器容器

ServerContainer组件提供了在服务器上以正确的导航状态呈现应用程序的实用程序

// Ref which will be populated with the screen options
const ref = React.createRef();

// Location object containing the `pathname` and `search` fields of the current URL
const location = { pathname: '/profile', search: '?user=jane' };

// Get rendered HTML
const html = ReactDOMServer.renderToString(
  <ServerContainer ref={ref} location={location}>
    <App />
  </ServerContainer>
);

// Then you can access the options for the current screen in the ref
const options = ref.current.getCurrentOptions(); // { title: 'My Profile' }

erverContainer组件应在服务器渲染期间包装整个应用程序。请注意,您的应用程序中仍需要一个NavigationContainer,ServerContainer不会替换它。’

有关详细指南和示例,请参阅服务器渲染指南

Ref

如果将ref附加到容器,则可以在渲染应用程序后获得当前屏幕的选项。ref将包含一个名为getCurrentOptions的方法,该方法将返回一个对象,其中包含导航树中聚焦屏幕的选项

const options = ref.current.getCurrentOptions();

然后,您可以从该对象访问屏幕的选项,并将其放入HTML中

<title>{options.title}</title>
<meta name="description" content={options.description} />

请注意,如果您没有在初始渲染上渲染导航器,则选项对象可能未定义

属性
location

位置对象,包含用于服务器渲染输出的位置。您可以在浏览器中传递与位置对象匹配的路径名和搜索属性

<ServerContainer location={{ pathname: '/profile', search: '' }}>
  <App />
</ServerContainer>

通常,您会根据传入的请求构造此对象

Koa的基本示例(不要在生产中按原样使用)

app.use(async (ctx) => {
  const html = ReactDOMServer.renderToString(
    <ServerContainer location={{ pathname: ctx.path, search: ctx.search }}>
      <App />
    </ServerContainer>
  );

  ctx.body = html;
});

Group 组

组组件用于在导航器内对多个屏幕进行分组,以实现组织目的。它们还可以用于将相同的选项(如标题样式)应用于一组屏幕

组从createXNavigator函数返回

const Stack = createStackNavigator(); // Stack contains Screen & Navigator properties

创建导航器后,它可以用作导航器组件的子级

<Stack.Navigator>
  <Stack.Group
    screenOptions={{ headerStyle: { backgroundColor: 'papayawhip' } }}
  >
    <Stack.Screen name="Home" component={HomeScreen} />
    <Stack.Screen name="Profile" component={ProfileScreen} />
  </Stack.Group>
  <Stack.Group screenOptions={{ presentation: 'modal' }}>
    <Stack.Screen name="Search" component={SearchScreen} />
    <Stack.Screen name="Share" component={ShareScreen} />
  </Stack.Group>
</Stack.Navigator>

也可以将组组件嵌套在其他组组件中

Props属性
screenOptions

配置如何在导航器中显示组内屏幕的选项。它接受一个对象或一个返回对象的函数

<Stack.Group
  screenOptions={{
    presentation: 'modal',
  }}
>
  {/* screens */}
</Stack.Group>

当你通过一个函数时,它会收到路线和导航

<Stack.Group
  screenOptions={({ route, navigation }) => ({
    title: route.params.title,
  })}
>
  {/* screens */}
</Stack.Group>

这些选项将与各个屏幕中指定的选项合并,屏幕的选项将优先于组的选项

有关更多详细信息和示例,请参阅屏幕选项

navigationKey

一组屏幕屏幕的可选键。如果键更改,则此组中的所有现有屏幕都将被删除(如果用于堆栈导航器)或重置(如果用于选项卡或抽屉导航器)

<Stack.Group navigationKey={isSignedIn ? 'user' : 'guest'}>
  {/* screens */}
</Stack.Group>

这类似于屏幕上的navigationKey属性,但适用于一组屏幕

Screen 屏幕

屏幕组件用于配置导航器内屏幕的各个方面

createXNavigator函数返回一个屏幕

const Stack = createNativeStackNavigator(); // Stack contains Screen & Navigator properties

创建导航器后,它可以用作导航器组件的子级

<Stack.Navigator>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>

您需要为每个屏幕至少提供一个名称和一个要渲染的组件

Props属性
name

用于屏幕的名称。它接受一个字符串

<Stack.Screen name="Profile" component={ProfileScreen} />

此名称用于导航到屏幕

navigation.navigate('Profile');

它也用于路由中的名称属性

虽然支持,但我们建议避免在屏幕名称中使用空格或特殊字符,并保持简单

options

配置如何在导航器中显示屏幕的选项。它接受一个对象或一个返回对象的函数

<Stack.Screen
  name="Profile"
  component={ProfileScreen}
  options={{
    title: 'Awesome app',
  }}
/>

当你通过一个函数时,它会收到路线和导航

<Stack.Screen
  name="Profile"
  component={ProfileScreen}
  options={({ route, navigation }) => ({
    title: route.params.userId,
  })}
/>

有关更多详细信息和示例,请参阅屏幕选项

initialParams

用于屏幕的初始参数。如果屏幕用作initialRouteName,它将包含initialParams中的参数。如果导航到新屏幕,传递的参数将与初始参数浅合并

<Stack.Screen
  name="Details"
  component={DetailsScreen}
  initialParams={{ itemId: 42 }}
/>
getId

回调以返回用于屏幕的唯一ID。它接收一个带有路由参数的对象

<Stack.Screen
  name="Profile"
  component={ProfileScreen}
  getId={({ params }) => params.userId}
/>

默认情况下,navigation(“屏幕名称”,params)通过屏幕名称标识屏幕。因此,如果您使用ScreenName并再次导航到ScreenName,即使参数不同,它也不会添加新屏幕,而是用新参数更新当前屏幕

// Let's say you're on `Home` screen
// Then you navigate to `Profile` screen with `userId: 1`
navigation.navigate('Profile', { userId: 1 });

// Now the stack will have: `Home` -> `Profile` with `userId: 1`

// Then you navigate to `Profile` screen again with `userId: 2`
navigation.navigate('Profile', { userId: 2 });

// The stack will now have: `Home` -> `Profile` with `userId: 2`

如果您指定了getId并且它没有返回undefined,则屏幕将由屏幕名称和返回的ID标识。这意味着,如果您使用ScreenName并使用不同的参数再次导航到ScreenName,并从getId回调返回不同的ID,它将向堆栈中添加一个新屏幕

// Let's say you're on `Home` screen
// Then you navigate to `Profile` screen with `userId: 1`
navigation.navigate('Profile', { userId: 1 });

// Now the stack will have: `Home` -> `Profile` with `userId: 1`

// Then you navigate to `Profile` screen again with `userId: 2`
navigation.navigate('Profile', { userId: 2 });

// The stack will now have: `Home` -> `Profile` with `userId: 1` -> `Profile` with `userId: 2`

getId回调也可用于确保具有相同ID的屏幕不会在堆栈中多次出现

// Let's say you have a stack with the screens: `Home` -> `Profile` with `userId: 1` -> `Settings`
// Then you navigate to `Profile` screen with `userId: 1` again
navigation.navigate('Profile', { userId: 1 });

// Now the stack will have: `Home` -> `Profile` with `userId: 1`

在上述示例中,params.userId用作ID,随后导航到具有相同userId的屏幕将导航到现有屏幕,而不是向堆栈中添加新屏幕。如果导航使用不同的用户ID,那么它将添加一个新屏幕。
如果在选项卡或抽屉导航器中指定了getId,则如果ID更改,屏幕将重新装载

component

为屏幕渲染的React组件

<Stack.Screen name="Profile" component={ProfileScreen} />
getComponent

回调以返回用于屏幕渲染的React组件

<Stack.Screen
  name="Profile"
  getComponent={() => require('./ProfileScreen').default}
/>

如果您希望在需要时延迟评估ProfileScreen模块,则可以使用此方法而不是组件属性。当使用柱塞束来提高初始负载时,这尤其有用

children

渲染回调以返回用于屏幕的React元素

<Stack.Screen name="Profile">
  {(props) => <ProfileScreen {...props} />}
</Stack.Screen>

如果需要传递其他属性,可以使用此方法代替组件性。虽然我们建议使用React上下文来传递数据,而不是

navigationKey

此屏幕的可选键。这不需要是独一无二的。如果按键更改,则将删除(如果在堆栈导航器中使用)或重置(如果在选项卡或抽屉导航器中用)具有此名称的现有屏幕

当我们有一些屏幕需要在条件发生变化时删除或重置时,这可能很有用

<Stack.Screen
  navigationKey={isSignedIn ? 'user' : 'guest'}
  name="Profile"
  component={ProfileScreen}
/>
listeners

要订阅的事件侦听器。有关更多详细信息,请参阅屏幕上的侦听器道具

Options for screens 屏幕的选项

每个屏幕都可以通过指定某些选项来配置如何在呈现它的导航器中显示它的各个方面,例如堆栈导航器中的标题、底部选项卡导航器的选项卡栏图标等。不同的导航器支持不同的选项集

在基础文档的配置标题栏部分,我们解释了其工作原理的基础知识。另请参阅屏幕选项分辨率指南,了解当有多个导航器时它们是如何工作的

有3种方法可以指定屏幕选项

options prop on Screen

您可以将名为options的属性传递给Screen组件以配置屏幕,您可以在其中为该屏幕指定具有不同选项的对象

<Stack.Navigator>
  <Stack.Screen
    name="Home"
    component={HomeScreen}
    options={{ title: 'Awesome app' }}
  />
  <Stack.Screen
    name="Profile"
    component={ProfileScreen}
    options={{ title: 'My profile' }}
  />
</Stack.Navigator>

您还可以将函数传递给选项。该功能将接收该屏幕的导航提示和路线提示。如果您想在选项中执行导航,这可能很有用

<Stack.Screen
  name="Home"
  component={HomeScreen}
  options={({ navigation }) => ({
    title: 'Awesome app',
    headerLeft: () => (
      <DrawerButton onPress={() => navigation.toggleDrawer()} />
    ),
  })}
/>
screenOptions prop on Group

您可以将名为screenOptions的属性传递给组组件,以配置组内的屏幕,您可以在其中指定具有不同选项的对象。screenOptions中指定的选项适用于组中的所有屏幕

<Stack.Navigator>
  <Stack.Group
    screenOptions={{ headerStyle: { backgroundColor: 'papayawhip' } }}
  >
    <Stack.Screen name="Home" component={HomeScreen} />
    <Stack.Screen name="Profile" component={ProfileScreen} />
  </Stack.Group>
  <Stack.Group screenOptions={{ presentation: 'modal' }}>
    <Stack.Screen name="Settings" component={Settings} />
    <Stack.Screen name="Share" component={Share} />
  </Stack.Group>
</Stack.Navigator>

与选项类似,您也可以将函数传递给screenOptions。该功能将接收每个屏幕的导航提示和路线提示。如果您想根据路线在一个地方为所有屏幕配置选项,这可能很有用

<Stack.Navigator>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Profile" component={ProfileScreen} />
  <Stack.Group
    screenOptions={({ navigation }) => ({
      presentation: 'modal',
      headerLeft: () => <CancelButton onPress={navigation.goBack} />,
    })}
  >
    <Stack.Screen name="Settings" component={Settings} />
    <Stack.Screen name="Share" component={Share} />
  </Stack.Group>
</Stack.Navigator>
screenOptions prop on the navigator

您可以将名为screenOptions的属性传递给导航器组件,在那里您可以指定具有不同选项的对象。screenOptions中指定的选项适用于导航器中的所有屏幕。因此,这里是指定要为整个导航器配置的选项的好地方

<Stack.Navigator
  screenOptions={{ headerStyle: { backgroundColor: 'papayawhip' } }}
>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>

与选项类似,您也可以将函数传递给screenOptions。该功能将接收每个屏幕的导航提示和路线提示。如果您想根据路线在一个地方为所有屏幕配置选项,这可能很有用

<Tab.Navigator
  screenOptions={({ route }) => ({
    tabBarIcon: ({ color, size }) => {
      const icons = {
        Home: 'home',
        Profile: 'account',
      };

      return (
        <MaterialCommunityIcons
          name={icons[route.name]}
          color={color}
          size={size}
        />
      );
    },
  })}
>
  <Tab.Screen name="Home" component={HomeScreen} />
  <Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
navigation.setOptions method

导航属性有一个setOptions方法,允许您从组件中更新屏幕的选项。查看导航属性的文档了解更多详细信息

<Button
  title="Update options"
  onPress={() => navigation.setOptions({ title: 'Updated!' })}
/>

Route prop reference 路由属性参考

应用程序中的每个屏幕组件都会自动提供路线属性。该道属性含有关当前路线的各种信息(导航层次结构组件中的位置)

route

key-屏幕的唯一键。自动创建或导航到此屏幕时添加。
name-屏幕的名称。在导航器组件层次结构中定义。
path-当通过深度链接打开屏幕时,存在一个包含打开屏幕的路径的可选字符串。
params-一个包含在导航时定义的参数的可选对象,例如navigation(‘Twitter’,{user:‘Dan Abramov’})。

function ProfileScreen({ route }) {
  return (
    <View>
      <Text>This is the profile screen of the app</Text>
      <Text>{route.name}</Text>
    </View>
  );
}

Navigation prop reference 导航属性参考

应用程序中的每个屏幕组件都会自动提供导航属性。该属性包含各种调度导航动作的便利功能。看起来像这样

navigation

  • navigation-转到给定的屏幕,这将根据导航器的不同而有所不同
  • goBack-返回上一屏幕,这将在堆栈中使用时弹出当前屏幕
  • reset-将导航器的导航状态替换为给定状态
  • setParams-将新参数合并到路由参数上
  • dispatch-发送一个动作对象来更新导航状态
  • setOptions-更新屏幕选项
  • isFocused-检查屏幕是否聚焦
  • canGoBack-检查是否可以从当前屏幕返回
  • getState-获取导航器的导航状态
  • getParent-获取父屏幕的导航对象(如果有的话)
  • addListener-订阅屏幕事件
  • removeListener-取消订阅屏幕上的事件

重要的是要强调导航属性不是传递给所有组件的;只有屏幕组件会自动接收此属性!React Navigation在这里没有任何魔力。例如,如果要定义MyBackButton组件并将其呈现为屏幕组件的子组件,则无法访问其上的导航属性。但是,如果要访问任何组件中的导航属性,可以使用useNavigation钩子

Navigator-dependent functions

根据当前导航仪的类型,导航属性上还有几个附加功能

如果导航器是堆栈导航器,则提供了几种导航和goBack的替代方案,您可以使用您喜欢的任何一种。这些功能是

navigation

  • replace-用新屏幕替换当前屏幕
  • push-将新屏幕推到堆栈上
  • pop-回到堆栈中
  • popToTop-转到堆栈的顶部

有关这些方法的更多详细信息,请参阅Stack导航器帮助程序和Native Stack导航器辅助程序

如果导航器是选项卡导航器,则以下内容也可用

navigation

  • jumpTo-转到选项卡导航器中的特定屏幕

有关这些方法的更多详细信息,请参阅底部选项卡导航器帮助程序、材质顶部选项卡导航器辅助程序和材质底部选项卡导航者帮助程序

如果导航器是抽屉导航器,则还可以使用以下导航器

navigation

  • jumpTo-跳转到抽屉导航器中的特定屏幕
  • openDrawer-打开抽屉
  • closeDrawer-关闭抽屉
  • toggleDrawer-切换状态,即从关闭切换到打开,反之亦然
Common API reference

您与导航属性的绝大多数交互将涉及导航、goBack和setParams

navigate

导航方法允许我们导航到您应用程序中的另一个屏幕。它需要以下论点

navigation.navigate(name, params)

name-在某个地方定义的路线的目的地名称
params-传递到目标路线的参数

function HomeScreen({ navigation: { navigate } }) {
  return (
    <View>
      <Text>This is the home screen of the app</Text>
      <Button
        onPress={() =>
          navigate('Profile', { names: ['Brent', 'Satya', 'Michaś'] })
        }
        title="Go to Brent's profile"
      />
    </View>
  );
}

在本机堆栈导航器中,根据屏幕是否已存在,使用屏幕名称调用navigation将导致不同的行为。如果该屏幕已经存在于堆栈的历史记录中,它将返回该屏幕并删除之后的任何屏幕。如果屏幕不存在,它将推送一个新屏幕

例如,如果您有一个历史记录为“主页>个人资料>设置”的堆栈,并且您调用navigation(个人资料),则返回到“个人资料”并删除“设置”屏幕时,生成的屏幕将是“主页>档案”

默认情况下,屏幕由其名称标识。但您也可以使用getId道具对其进行自定义,以考虑参数

例如,假设您为配置文件屏幕指定了一个getId属性

<Tab.Screen
  name={Profile}
  component={ProfileScreen}
  getId={({ params }) => params.userId}
/>

现在,如果你有一个历史记录为“主页”>“个人资料”(用户ID:bob)>“设置”的堆栈,并且你调用了navigation(个人资料,{userId:“alice”}),则结果屏幕将是“主页”>“个人资料(用户ID:bob)”>“设置>个人资料(userId:alice)”,因为找不到匹配的屏幕,它将添加一个新的个人资料屏幕

goBack

goBack方法允许我们返回导航器中的上一个屏幕

默认情况下,goBack将从调用它的屏幕返回

function ProfileScreen({ navigation: { goBack } }) {
  return (
    <View>
      <Button onPress={() => goBack()} title="Go back from ProfileScreen" />
    </View>
  );
}

从特定屏幕返回

考虑以下导航堆栈历史记录

navigation.navigate({ name: SCREEN, key: SCREEN_KEY_A });
navigation.navigate({ name: SCREEN, key: SCREEN_KEY_B });
navigation.navigate({ name: SCREEN, key: SCREEN_KEY_C });
navigation.navigate({ name: SCREEN, key: SCREEN_KEY_D });

现在你在屏幕D上,想回到屏幕A(弹出D、C和B)。然后,您可以使用导航

navigation.navigate({ key: SCREEN_KEY_A }); // will go to screen A FROM screen D

或者,由于屏幕A是堆栈的顶部,您可以使用navigation.popToTop()。

reset

重置方法允许我们用新状态替换导航器状态

navigation.reset({
  index: 0,
  routes: [{ name: 'Profile' }],
});

重置中指定的状态对象用新的导航状态替换现有的导航状态,即删除现有屏幕并添加新屏幕。如果你想在更改状态时保留现有的屏幕,你可以使用CommonActions.reset来代替dispatch

setParams

setParams方法允许我们更新当前屏幕的params(route.params)。setParams的工作方式类似于React的setState——它将提供的params对象与当前的params进行浅层合并

function ProfileScreen({ navigation: { setParams } }) {
  return (
    <Button
      onPress={() =>
        setParams({
          friends:
            route.params.friends[0] === 'Brent'
              ? ['Wojciech', 'Szymon', 'Jakub']
              : ['Brent', 'Satya', 'Michaś'],
          title:
            route.params.title === "Brent's Profile"
              ? "Lucy's Profile"
              : "Brent's Profile",
        })
      }
      title="Swap title and friends"
    />
  );
}
setOptions

setOptions方法允许我们在组件内设置屏幕选项。如果我们需要使用组件的属性、状态或上下文来配置我们的屏幕,这很有用

function ProfileScreen({ navigation, route }) {
  const [value, onChangeText] = React.useState(route.params.title);

  React.useEffect(() => {
    navigation.setOptions({
      title: value === '' ? 'No title' : value,
    });
  }, [navigation, value]);

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <TextInput
        style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
        onChangeText={onChangeText}
        value={value}
      />
      <Button title="Go back" onPress={() => navigation.goBack()} />
    </View>
  );
}

处指定的任何选项都与定义屏幕时指定的选项浅合并

使用navigation.setOptions时,我们建议在屏幕的选项属性中指定一个占位符,并使用navigation.setOptions进行更新。这确保了用户不会察觉到更新选项的延迟。它还使其适用于延迟加载的屏幕

您还可以使用React.useLayoutEffect来减少更新选项的延迟。但如果您支持web并进行服务器端渲染,我们建议不要这样做

Navigation events 导航事件

屏幕可以使用addListener方法在导航属性上添加监听器。例如,收听焦点活动

function Profile({ navigation }) {
  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      // do something
    });

    return unsubscribe;
  }, [navigation]);

  return <ProfileContent />;
}

有关可用事件和API使用情况的更多详细信息,请参阅导航事件

isFocused

此方法允许我们检查屏幕当前是否聚焦。如果屏幕聚焦,则返回true,否则返回false

const isFocused = navigation.isFocused();

当值更改时,此方法不会重新呈现屏幕,主要在回调中有用。您可能希望使用useIsFocused而不是直接使用它,它将返回一个布尔值a prop来指示屏幕是否聚焦

Advanced API Reference 高级api参考

调度功能的使用频率要低得多,但如果你无法使用导航、goBack等可用方法完成所需的操作,它是一个很好的逃生口。我们建议避免经常使用调度方法,除非绝对必要

dispatch

调度方法允许我们发送一个导航动作对象,该对象决定如何更新导航状态。所有导航功能,如导航,都在幕后使用调度

请注意,如果你想分派操作,你应该使用此库中提供的操作创建者,而不是直接编写操作对象

有关可用操作的完整列表,请参阅导航操作文档

import { CommonActions } from '@react-navigation/native';

navigation.dispatch(
  CommonActions.navigate({
    name: 'Profile',
    params: {},
  })
);

在分派操作对象时,您还可以指定一些其他属性

source-应被视为行动来源的路线关键。例如,替换操作将用给定的密钥替换路由。默认情况下,它将使用发送操作的路由的密钥。您可以显式传递undefined来覆盖此行为

target-应用操作的导航状态键。默认情况下,如果导航器不处理操作,则操作会自动跳转到其他导航器。如果指定了目标,如果具有相同键的导航器没有处理该操作,则该操作不会冒泡

import { CommonActions } from '@react-navigation/native';

navigation.dispatch({
  ...CommonActions.navigate('Profile'),
  source: 'someRoutekey',
  target: 'someStatekey',
});
Custom action creators

也可以将动作创建者函数传递给分派。该函数将接收当前状态,并需要返回一个导航操作对象以供使用

import { CommonActions } from '@react-navigation/native';

navigation.dispatch((state) => {
  // Add the home route to the start of the stack
  const routes = [{ name: 'Home' }, ...state.routes];

  return CommonActions.reset({
    ...state,
    routes,
    index: routes.length - 1,
  });
});

您可以使用此功能构建自己的助手,以便在应用程序中使用。下面是一个示例,它实现了在最后一个屏幕之前插入一个屏幕

import { CommonActions } from '@react-navigation/native';

const insertBeforeLast = (routeName, params) => (state) => {
  const routes = [
    ...state.routes.slice(0, -1),
    { name: routeName, params },
    state.routes[state.routes.length - 1],
  ];

  return CommonActions.reset({
    ...state,
    routes,
    index: routes.length - 1,
  });
};

然后像这样使用它

navigation.dispatch(insertBeforeLast('Home'));
canGoBack

此方法返回一个布尔值,指示当前导航器或任何父导航器中是否有任何可用的导航历史记录。您可以使用它来检查是否可以调用navigation.goBack()

if (navigation.canGoBack()) {
  navigation.goBack();
}

不要使用此方法渲染内容,因为这不会触发重新渲染。这仅用于回调、事件监听器等内部。

getParent

此方法从当前导航器嵌套的父导航器返回导航属性。例如,如果堆栈中嵌套了堆栈导航器和选项卡导航器,则可以在选项卡导航器的屏幕内使用getParent来从堆栈导航器传递导航属性

它接受一个可选的ID参数来引用特定的父导航器。例如,如果你的屏幕在一个id为“LeftDraw”的抽屉导航器下的某个地方嵌套了多级嵌套,你可以直接引用它,而无需多次调用getParent

要为导航器使用ID,首先传递一个唯一的ID属性

<Drawer.Navigator id="LeftDrawer">{/* .. */}</Drawer.Navigator>

然后,当使用getParent时,反面教材

// Avoid this
const drawerNavigation = navigation.getParent().getParent();

// ...

drawerNavigation?.openDrawer();

你可以

// Do this
const drawerNavigation = navigation.getParent('LeftDrawer');

// ...

drawerNavigation?.openDrawer();

这种方法允许组件不必知道导航器的嵌套结构。因此,强烈建议在使用getParent时使用id

如果没有匹配的父导航器,此方法将返回undefined。使用此方法时,一定要始终检查是否存在undefined

getState

此方法返回包含屏幕的导航器的状态对象。在极少数情况下,获取导航器状态可能很有用。你很可能不需要使用这种方法。如果你这样做,一定要有充分的理由

如果需要状态来呈现内容,则应使用NavigationState而不是此方法。

NavigationContext 导航上下文

NavigationContext提供导航对象(与导航属性相同的对象)。事实上,useNavigation使用此上下文来获取导航属性

大多数时候,您不会直接使用NavigationContext,因为提供的useNavigation涵盖了大多数用例。但是,如果您有其他想法,NavigationContext可供您使用

import { NavigationContext } from '@react-navigation/native';

function SomeComponent() {
  // We can access navigation object via context
  const navigation = React.useContext(NavigationContext);
}

Navigation events 导航事件

您可以监听React Navigation发出的各种事件,以获取某些事件的通知,在某些情况下,还可以覆盖默认操作。很少有核心事件,如焦点、模糊等(如下所述)适用于每个导航器,也很少有特定于导航器的事件仅适用于某些导航器

除了核心事件外,每个导航器都可以发出自己的自定义事件。例如,堆栈导航器发出transitionStart和transitionEnd事件,选项卡导航器发出tabPress事件等。您可以在单个导航器的文档中找到有关发出的事件的详细信息

Core events 核心事件

以下是每个导航器中可用的事件:

focus

当屏幕聚焦时,会触发此事件

在大多数情况下,useFocusEffect钩子可能比手动添加监听器更合适。有关更多详细信息,请参阅本指南以决定应使用哪种API

blur

当屏幕失去焦点时,会触发此事件

state

当导航器的状态发生变化时,会触发此事件。此事件在事件数据(event.data.state)中接收导航器的状态。

beforeRemove

当用户离开屏幕时,会触发此事件,有可能阻止用户离开。

Listening to events

有多种方式可以收听导航器的事件。每个注册为事件侦听器的回调都会接收一个事件对象作为其参数。事件对象包含的属性很少

  • data-关于导航器传递的事件的其他数据。如果没有传递数据,则可以未定义。
  • target-应接收事件的屏幕的路线键。对于某些事件,如果事件与特定屏幕无关,则这可能是未定义的。
  • preventDefault-对于某些事件,事件对象上可能有一个preventDefault方法。调用此方法将阻止事件执行的默认操作(例如切换tabPress上的选项卡)。对阻止操作的支持仅适用于某些事件,如tabPress,并不适用于所有事件

您可以使用以下API收听事件

navigation.addListener

在屏幕内部,您可以使用addListener方法在导航属性上添加监听器。addListener方法接受2个参数:事件的类型和对事件调用的回调。它返回一个函数,可以调用该函数取消订阅事件

const unsubscribe = navigation.addListener('tabPress', (e) => {
  // Prevent default action
  e.preventDefault();
});

通常,您会在React.useEffect中为函数组件添加一个事件监听器。例如

function Profile({ navigation }) {
  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      // do something
    });

    return unsubscribe;
  }, [navigation]);

  return <ProfileContent />;
}

取消订阅函数可以在效果中作为清理函数返回

对于类组件,您可以在componentDidMount生命周期方法中添加事件,并在componentWillUnmount中取消订阅:

class Profile extends React.Component {
  componentDidMount() {
    this._unsubscribe = navigation.addListener('focus', () => {
      // do something
    });
  }

  componentWillUnmount() {
    this._unsubscribe();
  }

  render() {
    // Content of the component
  }
}

要记住的一件事是,您只能使用addListener从即时导航器中监听事件。例如,如果你试图在嵌套在选项卡中的堆栈内的屏幕中添加一个监听器,它将不会得到tabPress事件。如果你需要监听父导航器的事件,你可以使用navigation.getParent来获取对父导航器导航属性的引用,并添加一个监听器

const unsubscribe = navigation
  .getParent('MyTabs')
  .addListener('tabPress', (e) => {
    // Do something
  });

这里的“MyTabs”是指您在父Tab.Navigator的id属性中传递的值,您要监听其事件

listeners prop on Screen

有时,您可能希望从定义导航器的组件中添加一个监听器,而不是在屏幕内。您可以使用屏幕组件上的监听器属性来添加监听器。监听器属性将事件名称作为键,监听器回调作为值

<Tab.Screen
  name="Chat"
  component={Chat}
  listeners={{
    tabPress: (e) => {
      // Prevent default action
      e.preventDefault();
    },
  }}
/>

您还可以传递一个回调函数,该函数返回带有监听器的对象。它将接收导航和路线作为参数

<Tab.Screen
  name="Chat"
  component={Chat}
  listeners={({ navigation, route }) => ({
    tabPress: (e) => {
      // Prevent default action
      e.preventDefault();

      // Do something with the `navigation` object
      navigation.navigate('AnotherPlace');
    },
  })}
/>
screenListeners prop on the navigator

您可以将名为screenListeners的属性传递给导航器组件,在那里您可以为此导航器的所有屏幕中的事件指定监听器。如果您想收听特定事件而不管屏幕如何,或者想收听常见事件,如发送到所有屏幕的状态,这可能很有用

<Stack.Navigator
  screenListeners={{
    state: (e) => {
      // Do something with the state
      console.log('state changed', e.data);
    },
  }}
>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>

与监听器类似,您也可以将函数传递给screenListeners。该功能将接收每个屏幕的导航提示和路线提示。如果您需要访问导航对象,这可能很有用

<Tab.Navigator
  screenListeners={({ navigation }) => ({
    state: (e) => {
      // Do something with the state
      console.log('state changed', e.data);

      // Do something with the `navigation` object
      if (!navigation.canGoBack()) {
        console.log("we're on the initial screen");
      }
    },
  })}
>
  <Tab.Screen name="Home" component={HomeScreen} />
  <Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>

Navigation state reference 导航状态参考

这是一个JavaScript对象,看起来像这样

const state = {
  type: 'stack',
  key: 'stack-1',
  routeNames: ['Home', 'Profile', 'Settings'],
  routes: [
    { key: 'home-1', name: 'Home', params: { sortBy: 'latest' } },
    { key: 'settings-1', name: 'Settings' },
  ],
  index: 1,
  stale: false,
};

每个导航状态对象中属性

  • type-状态所属的导航器的类型,例如堆栈、标签、抽屉。
  • key-标识导航器的唯一键。
  • routeNames-导航器中定义的屏幕名称。这是一个包含每个屏幕字符串的唯一数组。
  • routes-在导航器中呈现的路线对象(屏幕)列表。它还表示堆栈导航器中的历史。此数组中应至少存在一个项。
  • index-路由数组中聚焦路由对象的索引。
  • history-访问过的项目列表。这是一个可选属性,并非存在于所有导航器中。例如,它只存在于核心的标签和抽屉导航器中。历史数组中项目的形状可能因导航器而异。此数组中应至少存在一个项。
  • 过时-除非将过时属性显式设置为false,否则导航状态被假定为过时。这意味着状态对象需要“重新水合”。

路由数组中的每个路由对象可能包含以下属性

  • key-屏幕的唯一键。自动创建或导航到此屏幕时添加。
  • name-屏幕的名称。在导航器组件层次结构中定义。
  • params-包含导航时定义的参数的可选对象,例如navigation(“主页”,{sortBy:“最新”})。
  • state-一个可选对象,包含嵌套在此屏幕内的子导航器的导航状态。

例如,一个包含嵌套在主屏幕内的选项卡导航器的堆栈导航器可能有一个这样的导航状态对象

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,
};

值得注意的是,即使有一个嵌套的导航器,在导航发生之前也不会添加路由对象的状态属性,因此不能保证它存在

Partial state objects 部分状态对象

早些时候,有人提到导航状态中的过时属性。过时的导航状态意味着状态对象在使用前需要重新水合或修复,例如添加缺失的键、删除无效的屏幕等。作为用户,你不需要担心,React Navigation会自动修复状态对象中的任何问题,除非将stay设置为false。如果你正在编写一个自定义路由器,getRehydratedState方法允许你编写自定义再水合逻辑来修复状态对象

这也适用于index属性:index应该是堆栈中的最后一条路由,如果指定了不同的值,React Navigation会修复它。例如,如果你想重置应用程序的导航状态,使其显示Profile路由,并在返回时显示Home路由,并执行了以下操作

navigation.reset({
  index: 0,
  routes: [{ name: 'Home' }, { name: 'Profile' }],
});

React Navigation会将索引更正为1,并显示路线并按预期执行导航

在执行重置、提供初始状态等操作时,此功能非常方便,因为您可以安全地从导航状态对象中省略许多属性,并依靠React navigation为您添加这些属性,使您的代码更简单。例如,你只能提供一个没有任何键的路由数组,React Navigation会自动添加使其工作所需的一切

const state = {
  routes: [{ name: 'Home' }, { name: 'Profile' }],
};

再水化后,它会看起来像这样

const state = {
  type: 'stack',
  key: 'stack-1',
  routeNames: ['Home', 'Profile', 'Settings'],
  routes: [
    { key: 'home-1', name: 'Home' },
    { key: 'settings-1', name: 'Settings' },
  ],
  index: 1,
  stale: false,
};

在这里,React Navigation填充了缺少的部分,如键、路由名称、索引等。
也可以提供无效数据,例如不存在的屏幕,并且会自动修复。虽然不建议使用无效的状态对象编写代码,但如果你做了状态持久化之类的事情,它可能会非常有用,因为配置的屏幕在更新后可能会发生变化,如果React Navigation没有自动修复状态对象,这可能会导致问题

当你在initialState中提供一个状态对象时,React Navigation总是假设它是一个过时的状态对象,这确保了状态持久化等功能能够顺利工作,而无需对状态对象进行额外的操作

Link

链接组件渲染一个可以在按下时导航到屏幕的组件。在Web上使用时,这会呈现一个标签,在其他平台上使用Text组件。它保留了浏览器中锚点标签的默认行为,如右键单击->在新选项卡中打开链接”、Ctrl+click/⌘+click等,以提供原生体验。
href中标签的路径是根据您的链接选项生成的

import { Link } from '@react-navigation/native';

// ...

function Home() {
  return (
    <Link to={{ screen: 'Profile', params: { id: 'jane' } }}>
      Go to Jane's profile
    </Link>
  );
}

如果你想使用你自己的自定义触摸屏,你可以使用useLinkProps。
Link组件接受与useLinkProps相同的props

Hooks 钩子

useNavigation

useNavigation是一个钩子,可以访问导航对象。当您无法将导航属性直接传递到组件中,或者在嵌套较深的子组件的情况下不想传递它时,这很有用

useNavigation()返回它所在屏幕的导航属性

import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '@react-navigation/native';

function MyBackButton() {
  const navigation = useNavigation();

  return (
    <Button
      title="Back"
      onPress={() => {
        navigation.goBack();
      }}
    />
  );
}

有关更多信息,请参阅导航属性的文档

与类组件一起使用

您可以将类组件封装在函数组件中以使用钩子

class MyBackButton extends React.Component {
  render() {
    // Get it from props
    const { navigation } = this.props;
  }
}

// Wrap and export
export default function (props) {
  const navigation = useNavigation();

  return <MyBackButton {...props} navigation={navigation} />;
}

useRoute

useRoute是一个钩子,用于访问路由对象。当您无法将路由属性直接传递到组件中,或者在嵌套较深的子级的情况下不想传递它时,它很有用

useRoute()返回它所在屏幕的路由属性

import * as React from 'react';
import { Text } from 'react-native';
import { useRoute } from '@react-navigation/native';

function MyText() {
  const route = useRoute();

  return <Text>{route.params.caption}</Text>;
}

有关更多信息,请参阅路线属性的文档

与类组件一起使用

您可以将类组件封装在函数组件中以使用钩子:

class MyText extends React.Component {
  render() {
    // Get it from props
    const { route } = this.props;
  }
}

// Wrap and export
export default function (props) {
  const route = useRoute();

  return <MyText {...props} route={route} />;
}

useNavigationState

useNavigationState是一个钩子,可以访问包含屏幕的导航器的导航状态。在极少数情况下,当您希望基于导航状态渲染某些内容时,它非常有用

它接受选择器函数作为参数。选择器将接收完整的导航状态,并可以从该状态返回特定值

const index = useNavigationState((state) => state.index);

选择器功能有助于减少不必要的重新渲染,因此只有当你关心的时候,你的屏幕才会重新渲染。如果你真的需要整个状态对象,你可以显式地这样做

const state = useNavigationState((state) => state);

useNavigationState与navigation.getState()有何不同?

navigation.getState()函数也返回当前的导航状态。主要区别在于,当值发生变化时,useNavigationState钩子将触发重新呈现,而navigation.getState()不会。例如,以下代码将是不正确的

function Profile() {
  const routesLength = navigation.getState().routes.length; // 别这莫做

  return <Text>Number of routes: {routesLength}</Text>;
}

在这个例子中,即使你推一个新屏幕,这个文本也不会更新。如果你使用钩子,它会按预期工作

function Profile() {
  const routesLength = useNavigationState((state) => state.routes.length);

  return <Text>Number of routes: {routesLength}</Text>;
}

那么,你什么时候使用navigation.getState()呢?它在事件侦听器中非常有用,因为您不关心呈现的内容。在大多数情况下,最好使用钩子

Using with class component

您可以将类组件封装在函数组件中以使用钩子

class Profile extends React.Component {
  render() {
    // Get it from props
    const { routesLength } = this.props;
  }
}

// Wrap and export
export default function (props) {
  const routesLength = useNavigationState((state) => state.routes.length);

  return <Profile {...props} routesLength={routesLength} />;
}

useFocusEffect

有时我们想在屏幕聚焦时产生副作用。副作用可能涉及添加事件监听器、获取数据、更新文档标题等。虽然这可以通过使用聚焦和模糊事件来实现,但这并不符合人体工程学

为了使这更容易,库导出了一个useFocusEffect钩子

import { useFocusEffect } from '@react-navigation/native';

function Profile({ userId }) {
  const [user, setUser] = React.useState(null);

  useFocusEffect(
    React.useCallback(() => {
      const unsubscribe = API.subscribe(userId, (user) => setUser(user));

      return () => unsubscribe();
    }, [userId])
  );

  return <ProfileContent user={user} />;
}

useFocusEffect类似于React的useEffect钩子。唯一的区别是,它只在屏幕当前聚焦时运行。
只要传递给React.useCallback的依赖关系发生变化,该效果就会运行,即它将在初始渲染(如果屏幕聚焦)上运行,如果依赖关系发生了变化,它也将在后续渲染上运行。如果您没有将效果包装在React.useCallback中,则如果屏幕聚焦,该效果将在每次渲染时运行。
清理功能在需要清理之前的效果时运行,即当依赖关系发生变化并且计划了新的效果时,以及当屏幕卸载或模糊时

运行异步效果

当运行异步效果(如从服务器获取数据)时,重要的是要确保在清理函数中取消请求(类似于React.useEffect)。如果您使用的API不提供取消机制,请确保忽略状态更新

useFocusEffect(
  React.useCallback(() => {
    let isActive = true;

    const fetchUser = async () => {
      try {
        const user = await API.fetch({ userId });

        if (isActive) {
          setUser(user);
        }
      } catch (e) {
        // Handle error
      }
    };

    fetchUser();

    return () => {
      isActive = false;
    };
  }, [userId])
);

如果您不忽略结果,那么您可能会因为API调用中的竞争条件而导致数据不一致

延迟效果,直到过渡完成

一旦屏幕聚焦,useFocusEffect钩子就会运行效果。这通常意味着,如果有屏幕更改的动画,它可能还没有完成

React Navigation在原生线程中运行其动画,因此在许多情况下这不是问题。但是,如果该效果更新了UI或渲染了一些昂贵的东西,那么它可能会影响动画性能。在这种情况下,我们可以使用InteractionManager来推迟我们的工作,直到动画或手势完成

useFocusEffect(
  React.useCallback(() => {
    const task = InteractionManager.runAfterInteractions(() => {
      // Expensive task
    });

    return () => task.cancel();
  }, [])
);

useFocusEffect与为焦点事件添加监听器有何不同

当屏幕聚焦时,焦点事件会触发。由于这是一个活动,如果您订阅活动时屏幕已经聚焦,则不会调用您的监听事件。这也没有提供在屏幕未聚焦时执行清理功能的方法。您可以订阅blur事件并手动处理它,但它可能会变得混乱。除了这些事件之外,您通常还需要处理componentDidMount和componentWillUnmount,这使它变得更加复杂。

useFocusEffect允许您在focus 上运行效果,并在屏幕未聚焦时进行清理。它还处理卸载时的清理。当依赖关系发生变化时,它会重新运行效果,因此您不必担心侦听器中的值过时

何时使用聚焦和模糊事件

与useEffect一样,可以从useFocusEffect中的效果返回清理函数。cleanup函数旨在清理效果,例如中止异步任务、取消订阅事件侦听器等。它不是用来做blur事件处理的

useFocusEffect(
  React.useCallback(() => {
    return () => {
      // Do something that should run on blur
    };
  }, [])
);

同样,如果你想在屏幕收到焦点(例如跟踪屏幕焦点)时做点什么,并且不需要清理或需要在依赖关系更改时重新运行,那么你应该使用焦点事件

与类组件一起使用

您可以为您的效果制作一个组件,并在类组件中使用它

function FetchUserData({ userId, onUpdate }) {
  useFocusEffect(
    React.useCallback(() => {
      const unsubscribe = API.subscribe(userId, onUpdate);

      return () => unsubscribe();
    }, [userId, onUpdate])
  );

  return null;
}

// ...

class Profile extends React.Component {
  _handleUpdate = (user) => {
    // Do something with user object
  };

  render() {
    return (
      <>
        <FetchUserData
          userId={this.props.userId}
          onUpdate={this._handleUpdate}
        />
        {/* rest of your code */}
      </>
    );
  }
}

useIsFocused

我们可能希望根据屏幕的当前焦点状态呈现不同的内容。库导出一个useIsFocused钩子,使其更容易

import { useIsFocused } from '@react-navigation/native';

// ...

function Profile() {
  const isFocused = useIsFocused();

  return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}

请注意,当组件所在的屏幕更改焦点时,使用此钩子会触发组件的重新渲染。如果您的组件很重,这可能会导致动画过程中出现延迟。你可能想提取重要的部分来分离组件,并使用React.memo或React。PureComponent可最大限度地减少对它们的重新渲染

与类组件一起使用

您可以将类组件封装在函数组件中以使用钩子

class Profile extends React.Component {
  render() {
    // Get it from props
    const { isFocused } = this.props;
  }
}

// Wrap and export
export default function (props) {
  const isFocused = useIsFocused();

  return <Profile {...props} isFocused={isFocused} />;
}

useLinkTo

seLinkTo钩子允许我们根据链接选项使用路径而不是屏幕名称导航到屏幕。它返回一个函数,该函数接收要导航到的路径

import { useLinkTo } from '@react-navigation/native';

// ...

function Home() {
  const linkTo = useLinkTo();

  return (
    <Button onPress={() => linkTo('/profile/jane')}>
      Go to Jane's profile
    </Button>
  );
}

这是一个低级钩子,用于在上面构建更复杂的行为。我们建议使用useLinkProps钩子来构建自定义链接组件,而不是直接使用此钩子。它将确保您的组件在网络上可以正确访问

与类组件一起使用

class Home extends React.Component {
  render() {
    // Get it from props
    const { linkTo } = this.props;
  }
}

// Wrap and export
export default function (props) {
  const linkTo = useLinkTo();

  return <Profile {...props} linkTo={linkTo} />;
}

useLinkProps

useLinkProps钩子让我们构建自定义链接组件,让我们根据链接选项使用路径而不是屏幕名称导航到屏幕。它需要一条路径,并返回一个带有一些道具的对象,您可以将这些道具传递给组件

import { useLinkProps } from '@react-navigation/native';

// ...

const LinkButton = ({ to, action, children, ...rest }) => {
  const { onPress, ...props } = useLinkProps({ to, action });

  const [isHovered, setIsHovered] = React.useState(false);

  if (Platform.OS === 'web') {
    // It's important to use a `View` or `Text` on web instead of `TouchableX`
    // Otherwise React Native for Web omits the `onClick` prop that's passed
    // You'll also need to pass `onPress` as `onClick` to the `View`
    // You can add hover effects using `onMouseEnter` and `onMouseLeave`
    return (
      <View
        onClick={onPress}
        onMouseEnter={() => setIsHovered(true)}
        onMouseLeave={() => setIsHovered(false)}
        style={{ transitionDuration: '150ms', opacity: isHovered ? 0.5 : 1 }}
        {...props}
        {...rest}
      >
        <Text>{children}</Text>
      </View>
    );
  }

  return (
    <TouchableOpacity onPress={onPress} {...props} {...rest}>
      <Text>{children}</Text>
    </TouchableOpacity>
  );
};

function Home() {
  return (
    <LinkButton to={{ screen: 'Profile', params: { id: 'jane' } }}>
      Go to Jane's profile
    </LinkButton>
  );
}

然后,您可以在应用程序的其他地方使用LinkButton组件

function Home() {
  return (
    <LinkButton to={{ screen: 'Profile', params: { id: 'jane' } }}>
      Go to Jane's profile
    </LinkButton>
  );
}

useLinkProps返回的props对象包含可访问链接组件所需的props。当我们在视图、文本等上使用这些属性时,链接组件会响应用户的操作,如Ctrl+Click/⌘+Click在新选项卡中打开链接,同时在同一网页内保持常规点击

在将useLinkProps与当前版本的React Native for Web一起使用时,有几件重要的事情需要注意

  1. 您必须明确地将onPress作为Click道具传递,否则页面内导航将无法工作
  2. 您只能将视图或文本与useLinkProps一起使用。TouchableX组件不支持我们需要的正确单击事件

在React Native for Web的未来版本中,这些将不会成为问题,您将能够在Web、iOS和Android上为链接编写相同的代码。但在此之前,您需要为Web和本机编写特定于平台的代码

选项
to

你可以传递一个具有screen属性的对象

function Home() {
  return (
    <LinkButton to={{ screen: 'Profile', params: { id: 'jane' } }}>
      Go to Jane's profile
    </LinkButton>
  );
}

此对象的语法与在嵌套导航器中导航到屏幕相同。默认情况下,这将使用导航操作进行导航,除非您指定了其他操作

或者,您也可以将绝对路径传递到屏幕,例如-/profile/jane

这将用于href属性以及页面内导航

useLinkBuilder

useLinkBuilder钩子允许我们为当前导航器状态下的屏幕构建链接路径。它返回一个函数,该函数接受屏幕聚焦的名称和参数,并根据链接选项返回路径

import { Link, CommonActions, useLinkBuilder } from '@react-navigation/native';

// ...

function DrawerContent({ state, descriptors }) {
  const buildLink = useLinkBuilder();

  return state.routes((route) => (
    <Link
      to={buildLink(route.name, route.params)}
      action={CommonActions.navigate(route.name)}
    >
      {descriptors[route.key].options.title}
    </Link>
  ));
}

此挂钩旨在用于导航器中,以显示指向其中各种页面的链接,例如抽屉和标签导航器。如果你正在构建一个自定义导航器、自定义抽屉内容、自定义标签栏等,那么你可能想使用这个钩子

有几件重要的事情需要注意

  • 目标屏幕必须出现在当前导航器中。它不能位于父导航器或嵌套在子导航器中的导航器中
  • 它仅用于自定义导航器,以使其在多个应用程序中可重用。对于常规应用程序代码,直接使用路径而不是为屏幕构建路径,或者使用Link和useLinkProps来透明地处理路径

useScrollToTop

可滚动组件的预期本机行为是响应导航中的事件,当点击活动选项卡时,这些事件将滚动到顶部,正如您从本机选项卡栏中所期望的那样

为了实现它,我们导出useScrollToTop,它接受对可滚动组件的引用(例如ScrollView或FlatList)

import * as React from 'react';
import { ScrollView } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';

function Albums() {
  const ref = React.useRef(null);

  useScrollToTop(ref);

  return <ScrollView ref={ref}>{/* content */}</ScrollView>;
}
与类组件一起使用

您可以将类组件封装在函数组件中以使用钩子

class Albums extends React.Component {
  render() {
    return <ScrollView ref={this.props.scrollRef}>{/* content */}</ScrollView>;
  }
}

// Wrap and export
export default function (props) {
  const ref = React.useRef(null);

  useScrollToTop(ref);

  return <Albums {...props} scrollRef={ref} />;
}
提供滚动偏移

如果你需要偏移来滚动位置,你可以包裹和装饰传递的引用

import * as React from 'react';
import { ScrollView } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';

function Albums() {
  const ref = React.useRef(null);

  useScrollToTop(
    React.useRef({
      scrollToTop: () => ref.current?.scrollTo({ y: 100 }),
    })
  );

  return <ScrollView ref={ref}>{/* content */}</ScrollView>;
}

useTheme

useTheme钩子允许我们访问当前活动的主题。您可以在自己的组件中使用它,使它们对主题的更改做出响应

import * as React from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { useTheme } from '@react-navigation/native';

// Black background and white text in light theme, inverted on dark theme
function MyButton() {
  const { colors } = useTheme();

  return (
    <TouchableOpacity style={{ backgroundColor: colors.card }}>
      <Text style={{ color: colors.text }}>Button!</Text>
    </TouchableOpacity>
  );
}

有关如何配置主题的更多详细信息和使用指南,请参阅主题指南

与类组件一起使用

您可以将类组件封装在函数组件中以使用钩子

class MyButton extends React.Component {
  render() {
    // Get it from props
    const { theme } = this.props;
  }
}

// Wrap and export
export default function (props) {
  const theme = useTheme();

  return <MyButton {...props} theme={theme} />;
}

Actions 行动

CommonActions参考

导航操作是一个至少包含类型属性的对象。在内部,路由器可以使用getStateForAction方法处理该操作,以从现有导航状态返回新状态

每个导航操作至少可以包含以下属性

type (required) - 表示动作名称的字符串

payload (options) - 包含有关操作的附加信息的对象。例如,它将包含用于导航的名称和参数

source (optional) - 路由的关键应被视为行动的来源。这用于某些操作,以确定在哪个路由上应用该操作。默认情况下,navigation.dispatch会添加分派该操作的路由的键

target (optional) - 应应用该操作的导航状态键

重要的是要强调,当操作未处理时,调度导航操作不会抛出任何错误(类似于当您调度一个不由redux中的reducer处理的操作而什么都没有发生时)

常见操作

该库在CommonActions命名空间下导出多个动作创建者。您应该使用这些动作创建者,而不是手动编写动作对象

navigate

导航操作允许导航到特定路由。它需要以下论点

name - string 已在某处注册的路线的目的地名称

key - string 要导航到的路由标识符。如果该路由已存在,请导航回该路由

params - 要合并到目的地路由的参数

传递的选项对象应至少包含一个键或名称属性,以及可选的参数。如果同时传递了键和名称,如果找不到匹配项,堆栈导航器将使用指定的键创建新路由

import { CommonActions } from '@react-navigation/native';

navigation.dispatch(
  CommonActions.navigate({
    name: 'Profile',
    params: {
      user: 'jane',
    },
  })
);

在堆栈导航器中,根据屏幕是否已存在,使用屏幕名称调用navigation将导致不同的行为。如果该屏幕已经存在于堆栈的历史记录中,它将返回该屏幕并删除之后的任何屏幕。如果屏幕不存在,它将推送一个新屏幕

默认情况下,屏幕由其名称标识。但您也可以使用getId属性对其进行自定义,以考虑参数

reset

重置操作允许将导航状态重置为给定状态。它需要以下论点

state - object - 要使用的新导航状态对象

import { CommonActions } from '@react-navigation/native';

navigation.dispatch(
  CommonActions.reset({
    index: 1,
    routes: [
      { name: 'Home' },
      {
        name: 'Profile',
        params: { user: 'jane' },
      },
    ],
  })
);

重置中指定的状态对象用新的导航状态替换现有的导航状态。这意味着,如果您提供没有键的新路由对象,或提供具有不同键的路由对象,它将删除这些路由的现有屏幕并添加新屏幕

如果你想保留现有的屏幕,但只想修改状态,你可以传递一个函数到dispatch,在那里你可以获得现有的状态。然后,您可以随意更改它(确保不要更改现有状态,而是为您的更改创建新的状态对象)。并返回具有所需状态的重置动作

import { CommonActions } from '@react-navigation/native';

navigation.dispatch((state) => {
  // Remove all the screens after `Profile`
  const index = state.routes.findIndex((r) => r.name === 'Profile');
  const routes = state.routes.slice(0, index + 1);

  return CommonActions.reset({
    ...state,
    routes,
    index: routes.length - 1,
  });
});

用重置重写历史记录

由于重置操作可以用新的状态对象更新导航状态,因此可用于重写导航历史。然而,在大多数情况下,不建议重写历史以更改后堆栈

这可能会导致用户体验混乱,因为用户希望能够回到之前的屏幕

在支持Web平台时,浏览器的历史记录仍将反映旧的导航状态,因此如果用户使用浏览器的后退按钮,他们将看到旧屏幕,这会根据用户按下的后退按钮产生两种不同的体验。

因此,如果你有这样的用例,可以考虑另一种方法——例如,一旦用户导航回已更改的屏幕,就更新历史记录

goBack

goBack动作创建者允许返回历史中的前一条路由。这不需要任何争论

import { CommonActions } from '@react-navigation/native';

navigation.dispatch(CommonActions.goBack());

如果你想从特定路由返回,你可以添加一个引用路线键的源属性和一个引用包含路线的导航器键的目标属性

import { CommonActions } from '@react-navigation/native';

navigation.dispatch({
  ...CommonActions.goBack(),
  source: route.key,
  target: state.key,
});

默认情况下,发送操作的路由的键作为源属性传递,目标属性未定义

setParams

setParams操作允许更新特定路由的参数。它需要以下论点

params - object - 要将新参数合并到现有路由参数中

import { CommonActions } from '@react-navigation/native';

navigation.dispatch(CommonActions.setParams({ user: 'Wojtek' }));

如果要为特定路由设置参数,可以添加引用路由键的源属性

import { CommonActions } from '@react-navigation/native';

navigation.dispatch({
  ...CommonActions.setParams({ user: 'Wojtek' }),
  source: route.key,
});

如果源属性显式设置为undefined,它将为聚焦路由设置参数

StackActions参考

StackActions是一个对象,包含用于生成特定于基于堆栈的导航器的操作的方法。它的方法扩展了CommonActions中可用的操作

支持以下操作

replace

替换操作允许替换导航状态下的路由。它需要以下论点

  • name - string - 已在某处注册的路由的目的地名称
  • params - object - 传递到目的地路由的参数
import { StackActions } from '@react-navigation/native';

navigation.dispatch(
  StackActions.replace('Profile', {
    user: 'jane',
  })
);

如果要替换特定路由,可以添加引用路由键的源属性和引用导航状态键的目标属性

import { StackActions } from '@react-navigation/native';

navigation.dispatch({
  ...StackActions.replace('Profile', {
    user: 'jane',
  }),
  source: route.key,
  target: navigation.getState().key,
});

如果源属性显式设置为undefined,它将替换聚焦的路由

push

推送操作在堆栈顶部添加一条路由并向前导航。这与导航的不同之处在于,如果给定名称的路由已经存在,导航将弹回堆栈中的较早位置。推送总是在顶部添加,因此一条路线可以多次出现

  • name - string - 要推到堆栈上的路由名称
  • params - object - 传递到目的地路由的屏幕参数
import { StackActions } from '@react-navigation/native';

const pushAction = StackActions.push('Profile', { user: 'Wojtek' });

navigation.dispatch(pushAction);

pop

弹出操作将带您返回堆栈中的上一个屏幕。它有一个可选参数(count),允许您指定要弹出的屏幕数量

import { StackActions } from '@react-navigation/native';

const popAction = StackActions.pop(1);

navigation.dispatch(popAction);

popToTop

popToTop操作将带您返回堆栈中的第一个屏幕,并忽略所有其他屏幕。它在功能上与StackActions.pop({n:currentIndex})相同

import { StackActions } from '@react-navigation/native';

navigation.dispatch(StackActions.popToTop());

DrawerActions参考

DrawerActions 是一个包含用于生成特定于基于抽屉的导航器的操作的方法的对象。它的方法扩展了CommonActions中可用的操作

支持以下操作

openDrawer

openDrawer操作可用于打开抽屉窗格

import { DrawerActions } from '@react-navigation/native';

navigation.dispatch(DrawerActions.openDrawer());

closeDrawer

closeDrawer动作可用于关闭抽屉窗格

import { DrawerActions } from '@react-navigation/native';

navigation.dispatch(DrawerActions.closeDrawer());

toggleDrawer

toggleDrawer可用于在抽屉窗格关闭时打开,或在打开时关闭

import { DrawerActions } from '@react-navigation/native';

navigation.dispatch(DrawerActions.toggleDrawer());

jumpTo

jumpTo操作可用于跳转到抽屉导航器中的现有路由

name - string 要跳转到的路由名称

params - object 传递到目标路由的屏幕参数

import { DrawerActions } from '@react-navigation/native';

const jumpToAction = DrawerActions.jumpTo('Profile', { name: 'Satya' });

navigation.dispatch(jumpToAction);

TabActions 参考

TabActions是一个对象,包含用于生成特定于基于选项卡的导航器的操作的方法。它的方法扩展了CommonActions中可用的操作

支持以下操作

jumpTo

jumpTo操作可用于跳转到选项卡导航器中的现有路由

name - string 要跳转到的路由名称

params - object 传递到目标路由的屏幕参数

import { TabActions } from '@react-navigation/native';

const jumpToAction = TabActions.jumpTo('Profile', { user: 'Satya' });

navigation.dispatch(jumpToAction);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值