2
首发于公众号 前端混合开发,欢迎关注。

移动应用程序由多个屏幕组成。在构建移动应用程序时,首要考虑的是如何处理用户在应用程序中的导航问题,例如屏幕的展示和屏幕之间的切换。

React Navigation 是 React Native 最著名的导航库之一。在本教程中,我们将探讨 React Native 中导航的基础知识,介绍如何开始使用 React Navigation,并通过一些 React Native 导航示例进行讲解。

什么是 React Navigation

React Navigation 是一个独立的库,可帮助我们在 React 应用程序中实现导航功能。

React Navigation 是用 JavaScript 编写的,并不直接使用 iOS 和 Android 上的原生导航 API。相反,它重新创建了这些 API 的某些子集。这样就可以集成第三方 JS 插件,实现最大程度的自定义,并且更易于调试,而无需学习 Objective-CSwiftJavaKotlin 等语言。

什么是 React Native Navigation

React Native Navigation 是一个受欢迎的 React Navigation 替代方案。它是一个依赖于并且被设计用于与 React Native 一起使用的模块。React Native Navigation 有一点不同,它直接使用 iOS 和 Android 上的原生导航 API,这使得它能够提供更加原生的外观和感觉。在撰写本文时,React Native Navigation 的当前稳定版本是 React Navigation 6.1。

另一种选择:React Router Native

React Router Native 是在 React Native 应用程序中实现导航功能的另一种解决方案。它由 Remix 团队开发。

React Router Native 与 React Router 框架共享大部分 API 代码。这意味着,使用过 React Router 的 Web 开发人员会发现,使用 React Router Native 也很容易。

就易用性而言,React Navigation 和 React Router Native 完全相同。例如,请看下面的 Router Native 代码:

const Home = () => <Text>Home</Text>;
const About = () => <Text>About</Text>;

const App = () => (
  <NativeRouter>
    <View>
      <View>
        {/* Define our links. They are like anchor tags */}
        <Link to="/">
          <Text>Home</Text>
        </Link>
        <Link to="/about">
          <Text>About</Text>
        </Link>
      </View>
      {/*Define our routes for this project*/}
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
    </View>
    {/*The NativeRouter*/}
  </NativeRouter>
);

与 Navigation 相比,我们可以看到代码是相似的:

import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

function HomeScreen() {
  return (
    <View>
      <Text>Home Screen</Text>
    </View>
  );
}

function AboutScreen() {
  return (
    <View>
      <Text>About Screen</Text>
    </View>
  );
}

const Stack = createNativeStackNavigator();
export default function App() {
  return (
    <NavigationContainer>
     <Stack.Navigator>
        {/*Define our routes*/}
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="About" component={AboutScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

两个库中用于实现路由的代码完全相同。这是一大优点,因为这意味着学习这两个框架的难度都不大。

如果你来自Web 开发背景,我会推荐使用 React Router Native,因为它的使用方式与 React Router 相同。否则,应该选择 React Navigation,因为它拥有更大的社区,因此有更多的开发支持。

安装 React Navigation

既然我们已经了解了什么是 React Navigation 以及它与 React Router Native 的关系,那么就让我们看看如何在应用程序中使用它。

注:在本文中,我们将在 React Native 应用程序中使用 Expo。

首先,我们使用下面的命令创建一个新的应用程序:

npx create-expo-app ReactNavigationDemo

这将创建一个名为 ReactNavigationDemo 的新项目:

image.png

接下来, cd 进入项目文件夹,打开代码编辑器:

cd ReactNavigationDemo

如果使用的是 VS Code,则可以使用此功能在编辑器中打开当前文件夹:

code .

用这个启动应用程序:

npx expo start

接下来,使用以下任一命令在 React Native 项目中安装 React Navigation 库:

/* npm */
npm install @react-navigation/native

/* yarn */
yarn add @react-navigation/native

我们还需要安装一些依赖项,即 react-native-screensreact-native-safe-area-context

如果你注意到了,我们没有使用 npmyarn 安装这些依赖项。相反,我们使用了 npx expo install ,因为它会安装与我们的项目软件包兼容的依赖版本。

我建议您始终使用该命令来安装依赖包,因为 npm 和 yarn 将始终安装最新版本,而最新版本可能与您的项目不兼容。缺点是可能会出现生产级别的错误。

React Native 堆栈导航器

React Navigation 使用 JavaScript 构建,让我们创建的组件和导航模式在外观和感觉上都与真正的原生模式无异。

React Navigation 提供了一个基于堆栈的导航模型,允许屏幕被推入和弹出导航堆栈。通过堆栈导航,你可以使用一个堆栈导航器来定义你的应用程序的导航结构,该导航器维护着一个屏幕堆栈。任何给定时间只有一个屏幕呈现给用户,每个屏幕在被推入堆栈时显示,即当用户导航到某个屏幕时,它就被推到堆栈的顶部。

想象一堆纸张。导航到一个新屏幕会将其放在堆栈的顶部,而导航回去则会将其从堆栈中移除。堆栈导航器还提供了类似于原生 iOS 和 Android 的过渡效果和手势。

注意,一个应用程序可以有多个堆栈导航器。

理解堆栈导航器与原生堆栈导航器的区别

在 React Native 中,我们有两个堆栈导航库: @react-navigation/stack@react-navigation/native-stack 。这两个库都提供了基于堆栈的导航模型,便于在屏幕之间进行转换,将每个新屏幕放在堆栈的顶部。

然而,默认情况下,虽然 @react-navigation/stack 被配置为具有熟悉的 iOS 和 Android 外观和感觉,并且可以自定义动画,但 @react-navigation/native-stack 则利用了原生 API;iOS 上的 UINavigationController 和 Android 上的 Fragment,这样导航的行为就会与原生构建的应用程序一样。

为了理解这两个库之间的区别,让我们从以下几个关键因素来看看它们:

  1. 定制性:根据你的需求,@react-navigation/native-stack 可能不如 @react-navigation/stack 可定制。所以,如果你需要根据自己的感觉定制导航动画,@react-navigation/stack 应该是你的首选。
  2. 性能@react-navigation/native-stack 提供了更好的性能。与 @react-navigation/stack 相比,它使用本地堆栈 View 组件来渲染屏幕,使过渡动画更快、更好、更流畅。
  3. 兼容性:两个库都与 React Navigation 兼容。@react-navigation/native-stack 还通过 react-native-web 提供了对Web的基本支持。另一方面,@react-navigation/stack 则没有。 你还必须安装 react-native-gesture-handler 并在入口或根文件(index.js 或 App.js)的顶部导入它。跳过这一步通常会导致生产级别的崩溃,即使在开发中工作正常。此外,如果你想要使用 UIkit 风格来动画化你的头部,你将需要安装一个额外的包:@react-native-masked-view/masked-view

React Native 导航器 React Native

在本节中,我们将探讨 React Native 导航中的不同导航器,以及如何使用 React Navigation 库实现它们。

1.使用堆栈导航器在屏幕组件之间导航

首先,我们创建两个文件,即 HomescreenAboutscreen

/* components/Homescreen.js */

import React from "react";
import { Button, View, Text } from "react-native";

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

请注意上面按钮的 onPress 属性 —— 我们稍后会解释它的作用:

/* components/Aboutscreen.js */

import React, { Component } from "react";
import { Button, View, Text } from "react-native";
export default function AboutScreen() {
  return (
    <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
      <Text>About Screen</Text>
    </View>
  );
}

项目文件夹应该是这样的:

image.png

我们对 App.js 也进行一些修改。 在这里,我们必须进行以下导入:

//tell React that we will implement a navigator
import { NavigationContainer } from "@react-navigation/native";

//create a stack navigator
import { createNativeStackNavigator } from "@react-navigation/native-stack";

在根 App.js 文件中实现导航非常有用,因为从 App.js 导出的组件是 React Native 应用程序的入口点(或根组件),而其他每个组件都是其后代。

正如你所看到的,我们将在导航功能中封装所有其他组件:

/* App.js */

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './components/HomeScreen';
import AboutScreen from './components/AboutScreen';

const Stack = createNativeStackNavigator();

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

在上面的代码中, createNativeStackNavigator 为我们的应用程序提供了一种在屏幕之间过渡的方式,其中每个新屏幕都位于堆栈的顶部。我们将其配置为熟悉的 iOS 和 Android 外观和感觉:在 iOS 中,新屏幕从右侧滑入,而在 Android 中,新屏幕从底部淡入。

在这里,我们执行了 createNativeStackNavigator 函数,并将其实例存储在 Stack 变量中。稍后,我们将使用 Stack.Screen 标记传递路由。 Home 路由对应于 HomeScreen ,而 About 路由对应于 AboutScreen

此外, Stack 会按顺序显示,例如, HomeScreen 默认首先显示,因为它位于 Stack 的最上方。要覆盖这一默认选项,可以指定初始路径。请参阅下面的代码:

/* App.js */

// imports....
const Stack = createNativeStackNavigator();

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

在幕后,createStackNavigator 函数为 HomeScreen 和 AboutScreen 组件提供了一个 navigate 属性。

这个属性允许导航到指定的屏幕组件。这就是为什么我们可以在 HomeScreen.js 上的一个按钮上使用它,当按下时,会导致页面跳转到 AboutScreen,如下所示:

<Button title="Go to About" onPress={() => navigation.navigate("About")} />;

在 App.js 代码中,我们将组件封装在 NavigationContainer 组件中,最终创建了一个应用程序容器。该组件管理导航树并包含导航状态。

最后,运行应用程序

npx expo start

2.使用 TabNavigator

大多数移动应用程序都有一个以上的屏幕。在此类移动应用程序中,常见的导航方式是基于标签的导航。React Navigation 有一个名为 createBottomTabNavigator 的组件可以帮助我们实现这一点。

在实现基于标签的导航之前,使用以下任一命令安装 bottom-tabs 模块:

/* npm */
npm install @react-navigation/bottom-tabs

/* yarn */
yarn add @react-navigation/bottom-tabs

我们创建一个 ContactScreen 文件,在应用程序中添加另一个屏幕:

/* components/ContactScreen.js */

import React, { Component } from "react";
import { Button, View, Text } from "react-native";

export default function ContactScreen() {
  return (
    <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
      <Text>Contact Screen</Text>
    </View>
  );
}

接下来,我们将其导入 App.js 文件:

import ContactScreen from './components/ContactScreen';

我们还将在我们的 App.js 文件中实现我们的标签导航。这是因为建议我们在根文件中实现所有的导航配置,因为这些配置包裹了所有的导航结构,并将我们的屏幕作为子元素渲染。

我们用这行代码替换 createNativeStackNavigator 行:

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

我们的堆栈导航器也将发生变化。 App.js 应该是这样的:

/* App.js */

import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import ContactScreen from './components/ContactScreen';
import HomeScreen from './components/HomeScreen';
import AboutScreen from './components/AboutScreen';

const Tab = createBottomTabNavigator();

export default function App() {
    return (
        <NavigationContainer>
            <Tab.Navigator initialRouteName="Home">
                <Tab.Screen
                    name="Home"
                    component={HomeScreen}
                />
                <Tab.Screen
                    name="About"
                    component={AboutScreen}
                />
                <Tab.Screen
                    name="Contact"
                    component={ContactScreen}
                />
            </Tab.Navigator>
        </NavigationContainer>
    );
}

运行应用程序,它将看起来像这样:

使用抽屉导航器

要使用抽屉导航,请首先使用以下任一命令安装 @react-navigation/drawer 包:

接下来,我们将使用 npx expo install 安装依赖项:

npx expo install react-native-gesture-handler react-native-reanimated

接下来,转到 Reanimated 文档中设置项目中的手势控制。完成这一步后,在你的 App.js 的顶部导入手势处理器包:

import "react-native-gesture-handler"; 

我们还要用抽屉导航更新导航器。复制并用下面的代码替换 App.js 代码:

/* App.js */

import 'react-native-gesture-handler';
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import ContactScreen from './components/ContactScreen';
import HomeScreen from './components/HomeScreen';
import AboutScreen from './components/AboutScreen';
const Drawer = createDrawerNavigator();
export default function App() {
    return (
        <NavigationContainer>
            <Drawer.Navigator initialRouteName="Home">
                <Drawer.Screen
                    name="Home"
                    component={HomeScreen}
                />
                <Drawer.Screen
                    name="About"
                    component={AboutScreen}
                />
                <Drawer.Screen
                    name="Contact"
                    component={ContactScreen}
                />
            </Drawer.Navigator>
        </NavigationContainer>
    );
}

运行应用程序查看结果:

你可以通过在路由名称旁边添加图标来自定义你的抽屉导航。我们的图标放置在我们的资源文件夹内:

image.png

我们可以通过在以下屏幕组件文件中添加 navigationOptions 来进行自定义:

<NavigationContainer>
  <Drawer.Navigator initialRouteName="Home">
    <Drawer.Screen
      name="Home"
      component={HomeScreen}
      options={{ //change the configuration of our screen
        drawerIcon: ({ color, number, focused }) => { //set the icon:
          return ( //the icon will be an image
            <Image
              source={require("../assets/home-icon.png")}
              style={{ height: 30, width: 30 }}
            />
          );
        },
      }}
    />
 <Drawer.Screen
      name="About"
      component={AboutScreen}
      options={{
        drawerIcon: ({ color, number, focused }) => { //set the icon for all screens
          return (
            <Image
              source={require("../assets/about-icon.png")}
              style={{ height: 30, width: 30 }}
            />
          );
        },
      }}
    />
 <Drawer.Screen
      name="Contact"
      component={ContactScreen}
      options={{
        drawerIcon: ({ color, number, focused }) => {
          return (
            <Image
              source={require("../assets/contact-icon.png")}
              style={{ height: 30, width: 30 }}
            />
          );
        },
      }}
    />
  </Drawer.Navigator>
</NavigationContainer>

drawerActiveTintColor 属性允许你根据导航标签和标签的激活或非激活状态应用任何颜色。例如,我们可以更改我们导航抽屉标签的激活状态颜色。转到 Drawer.Navigator 变量,并添加到 options 对象中:

<Drawer.Navigator
  initialRouteName="Home"
  screenOptions={{ drawerActiveTintColor: "#e91e63" }}
>
//... further code.

这就导致了颜色的变化:

image.png

4.使用 useNavigation() 钩子

React Navigation 还提供了一个名为 useNavigation 的 Hook。这个 Hook 使函数组件能够访问导航对象,并允许它们以编程方式触发导航操作。当你无法直接将导航属性传递给组件时,它非常有用。

老实说,我更经常使用 Hook,因为它更容易在我的功能组件中进行管理,而且使用起来也非常方便。

在你的 HomeScreen 文件中,用下面的代码替换你的代码:

/* components/HomeScreen.js */

import React from 'react';
import { Button, View, Text } from 'react-native';
import { useNavigation } from '@react-navigation/native';

export default function HomeScreen() {
    const navigation = useNavigation();

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

useNavigation 钩子是从 @react-navigation/native 模块导入的,它会返回一个带有编程操作的导航对象。

在 About 页面中,可以为返回按钮实现相同的方法。请参阅以下代码:

/* components/About.js */

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';
import { useNavigation } from '@react-navigation/native';
export default function AboutScreen() {
    const navigation = useNavigation();
    return (
        <View
            style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}
        >
            <Text>About Screen</Text>
            <Button
                title="Go Back"
                onPress={() => {
                    navigation.goBack();
                }}
            />
        </View>
    );
}

在 React Navigation 中向屏幕传递参数

向路由传递参数有两个简单的步骤:传递参数,然后在子路由或屏幕中读取参数。

首先,将参数作为 navigation.navigate 函数的第二个参数放入一个对象中,从而将参数传递给路由:

然后,读取屏幕组件中的参数。参数可在 route.params 中找到:

export default function HomeScreen({ route, navigation }) {

// 'route' 变量为我们提供页面信息。
// 它还存储参数及其值

const { paramName } = route.params; // 我们的参数 'paramName' 存储在这里。
// ..后续代码..

}

最后,要设置头部标题,我们可以像这样使用 options 属性的 title 属性:

<Drawer.Screen
  name="Home"
  component={HomeScreen}
  options={{
    title: "Home Page", //set the title of the page to 'Home page'
  }}
/>

image.png

总结

还有更多可以做的事情,而 React Navigation 将满足你的大部分需求。要了解更多信息,请查看 React Navigation 文档,并随时从我的 GitHub 仓库中获取最终代码

交流

首发于公众号 大迁世界,欢迎关注。📝 每周一篇实用的前端文章 🛠️ 分享值得关注的开发工具 ❓ 有疑问?我来回答

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。


王大冶
68.1k 声望105k 粉丝