web前端开发

React Navigation组件5.x-中文文档

2020-06-08  本文已影响0人  shiyueZ

引言

该文档根据React Navigation文档翻译,有些内容会根据自己的理解进行说明,不会照搬直译,若发现理解有问题的地方,欢迎大家提点!由于本人是基于iOS开发,安卓版本的目前还没有去实践运行,后续有时间会去实践,如果遇到问题,可以@我。最后,这边针对iOS运行的时候遇到的问题也有汇总,并提供解决方案。最后的最后,由于本片文章会很长,所以推荐一个Chrome插件,可以自动根据文章中的h1~h6生成目录,方便查看章节内容,在编写文章时也可以用哦!Smart TOC,点击安装后,如下图操作:

截屏2020-06-0810.27.10.png

基础

1 开始

如果您已经熟悉React Native,那么您将能够快速上手React导航!如果没有学习过,你需要先读React Native Express的第1 - 4部分(包括第4部分),读完后再回到这里。

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

1.1 安装

在RN项目中安装您需要的包

npm install @react-navigation/native
yarn add @react-navigation/native

React导航由一些核心工具组成,并且导航器使用这些工具在应用中创建导航结构。为了提前加载安装工作,我们还需要安装和配置大多数导航器使用的依赖项,然后我们开始编写代码。

现在,我们需要安装react-native-gesture-handlerreact-native-reanimatedreact-native-screensreact-native-safe-area-context@react-native-community/masked-view这些库,如果您已经安装了这些最新版本的库,那可以跳过下面内容,否则继续阅读下去。

1.2 安装依赖到Expo管理项目

cd到你的项目目录下,运行:

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

这个命令会安装这些库的最合适的版本。接下来,您可以继续到项目中编写代码。
(注:用Expo管理项目,目前还没用到过,有疑问的童鞋麻烦自行查询!)

1.3 安装依赖到原生的RN项目中

cd到你的项目目录下,运行:

npm install react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

注意:安装后可能会出现有相关的对等依赖的警告,这个通常是由于一些不同版本的包引起的,只要您的项目可以顺利运行,可以忽略这些警告。

从RN 0.60或者更高版本开始,这些库会自动链接项目,因此不需要运行react-native link。

如果您是在mac上开发iOS项目,需要安装CocoaPods来完成项目链接:

npx pod-install ios

当您完成了react-native-gesture-handler的安装,在项目的入口文件(例如index.js或App.js)引入react-native-gesture-handler(确保在入口文件的第一行)

import 'react-native-gesture-handler';

注意:如果您忽略这一步,尽管在开发中运行是正常的,但在生产上将会奔溃。

现在,我们需要将整个app装载在NavigationContainer之中。通常做法是,在入口文件(例如index.js或App.js)做这些事情:

import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return (
    <NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
  );
}

注意:当您使用导航器(如堆栈导航器)时,对于任何其他依赖项,都需要遵循导航器的安装说明。如果您遇到"Unable to resolve module"的错误,您需要安装错误中提示的组件到项目中。

现在,您可以将项目编译并运行在设备或者模拟器上,并继续进行相关的代码编写。

1.4 遇到的问题

报错:TypeError: null is not an object (evaluating '_RNGestureHandlerModule.default.Direction')

解决方法:
在ios文件夹下的Podfile文件中添加:

pod 'RNGestureHandler', :path => "../node_modules/react-native-gesture-handler"

终端命令cd到ios文件,运行pod install

2 Hello React Navigation

在web浏览器上,您可能会用一个<a>标签链接到另一个页面上。当用户点击了一个链接,URL被推到浏览器历史堆栈中,当用户点击了返回按钮,浏览器会从历史堆栈中弹出之前的访问过的页面作为目前展示的页面。React Native不像web浏览器那样有内置的全局历史堆栈概念——这就是React导航的作用。

React Navigation的堆栈导航器为应用程序提供了在屏幕之间转换和管理导航历史的方法。如果您的应用程序只使用一个堆栈导航器,那么它在概念上类似于web浏览器处理导航状态——当用户与它交互时,您的应用程序从导航堆栈中推送和弹出项目,用户可以看到不同的页面。它在web浏览器和React导航中的工作方式的一个关键区别是,React导航的堆栈导航器提供了在Android和iOS中导航堆栈中的路由时需要的手势和动画。

让我们从演示最常见的导航器开始,createStackNavigator。

2.1 安装堆栈导航器库

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

npm install @react-navigation/stack
yarn add @react-navigation/stack

提醒:@react-navigation/stack 依赖于我们在开始章节安装的库 @react-native-community/masked-view,如果您还未安装,麻烦回到上一章节。

2.2 创建一个堆栈导航器

createStackNavigator是一个函数,它返回一个包含两个属性的对象:屏幕和导航器。它们都是用于配置导航器的React组件。导航器应该将屏幕元素作为子元素来定义路由的配置。

NavigationContainer是一个管理导航树并包含导航状态的组件。该组件必须包装所有导航器结构。通常,我们会将这个组件呈现在应用程序的根目录下,这个根目录通常是从app .js导出的组件。这里我自定义了一个路由文件,然后在app.js中引入:

//自定义一个路由文件NavigationComponent.js
import React from 'react';
import {Text, View} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';

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

const Stack = createStackNavigator();

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

export default NavigationComponent;
//在App.js中引入
import 'react-native-gesture-handler';
import React, {Component} from 'react';
import NavigationComponent from './Sections/常用组件/NavigationComponent';

export default class App extends Component {
  render() {
    return <NavigationComponent />;
  }
}

在Snack上尝试编写

Simulator Screen Shot - iPhone 11 - 2020-06-08 at 15.03.00.png
如果您运行这段代码,您将看到一个带有空导航栏和包含主屏幕组件的灰色内容区域的屏幕(如上所示)。您看到的导航栏和内容区域的样式是堆栈导航器的默认配置,稍后我们将学习如何配置它们。

路由名称的对大小写不敏感——您可以使用小写的home或大写的home,这取决于您。我们喜欢把路线名称大写。

屏幕唯一需要的配置是name和component props。您可以在stack navigator reference中了解更多其他可用选项的信息。

2.3 配置导航器

所有路由配置都被指定为导航器的props。我们没有向导航器传递任何props,因此它只使用默认配置。

让我们在堆栈导航器中添加第二个屏幕,并配置主屏幕首先渲染:

function HomeScreen() {...}

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

const Stack = createStackNavigator();

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

在Snack上尝试编写

现在我们的堆栈有两个路由页面,一个主页面和一个详细页面。可以使用Screen组件指定路由。屏幕组件接受一个name prop,对应于导航的路由的名称,以及一个component prop,对应于它将渲染的组件。

这里,主路由对应于HomeScreen组件,而详细信息路由对应于DetailsScreen组件。堆栈的初始路由是主路由。尝试将其更改为Details并重新加载应用程序(如您所料,React Native的快速刷新不会更新initialRouteName的更改),注意您现在将看到Details屏幕。然后将其更改为Home并重新加载一次。

注意:组件prop只接受component,而不是渲染函数。不要传递内联函数(例如component={() => <HomeScreen />}),否则当父组件重新渲染时,你的组件将会卸载和重新加载并移除所有的state。见Passing additional props来替代。

2.4 指定options

在导航器里的每个屏幕可以指定一些options,例如一个渲染页面的标题。这些options可以传递到每个屏幕组件的options prop中:

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

在Snack上尝试编写

有时我们希望为导航器中的所有屏幕指定相同的options。为此,我们可以向导航器传递screenOptions prop。

2.5 传递props

有时我们可能想要传递props到屏幕上。我们可以用两种方法来实现:

1.使用React context提供程序包装导航器,以便向屏幕传递数据(推荐)。

2.使用屏幕渲染回调来代替指定一个组件的prop:

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

注意:默认情况下,React Navigation会对屏幕组件进行优化,以防止不必要的渲染。使用渲染回调会移除这些优化。所以如果你使用渲染回调,需要为组件使用React.memo或React.PureComponent,以避免性能问题。

2.6 下一步?

接下来的问题是:“我如何从主页面跳转到详情页面?”,这将在下一节中介绍。

2.7 总结

3 屏幕页面切换

在前面的章节,我们定义了一个包含主页面和详情页面的堆栈路由,但是我们没有学习如何让用户导航主页面和详细页面(虽然我们学习了在代码中如何改变初始路由,但让用户强制克隆库和改变初始路由,以达到展示另一个页面,可以想象,这是最糟糕的用户体验)。

如果是web浏览器,我们可以这样:

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

另一个方法是:

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

我们将执行与全局window.location类似的操作,我们将使用navigation prop向下传递到屏幕组件。

3.1 导航到新页面

import React from 'react';
import { Button, View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/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

在Snack上尝试编写

让我们来分析一下:

如果我们使用navigation.navigate,导航到未在堆栈导航器上定义的路由名称,将不会发生任何事情。换句话说,我们只能导航到在堆栈导航器上定义的路由——不能导航到任意组件。
现在在我们的栈上有两个路由:(1)主路由,(2)详情路由。如果我们在详情页面再次导航到详情页面,会发生什么?

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

在Snack上尝试编写

如果你运行这段代码,当你点击"Go to Details... again"后,不会做任何事情!因为我们已经在详情页面上了。

假设,我们确实想跳转到一个新的详情页面。这在向每个路由传递一些唯一数据的情况下非常常见(稍后在讨论参数时将对此进行更多讨论!)。我们可以用navigate的push来达到目的,这个允许添加一个路由而不必理会栈上是否有这个路由。

<Button
  title="Go to Details... again"
  onPress={() => navigation.push('Details')}
/>

在Snack上尝试编写

每次使用push,在栈导航器上都会新增一个路由。而当使用navigate时,首先会判断是否有这个名称的路由,当栈上没有这个路由时才会跳转到一个新的路由页面。

3.2 返回

待写,先学新的

4 路由间传递参数

还记得我说过”稍后在讨论参数时将对此进行更多讨论!“吗?现在可以开始了。

现在我们知道如何在栈导航器上创建一些路由,并且路由之间的跳转,那么如何在路由之间传递参数呢,我们来看看。

这里有两部分:

  1. 将路由上需要的参数放在一个对象里,作为navigation.navigate函数的第二个参数:navigation.navigate('RouteName', { /* params go here */ })
  2. 在组件中获取这个参数: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 } = route.params;
  const { 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>
  );
}

在Snack上尝试编写

4.1 更新参数

页面上也可以更新参数,类似更新页面状态。navigation.setParams就可以用来更新页面参数。通过API reference for了解更多。

你也可以向页面传递一些初始参数。如果导航到页面并没有设置任何参数,这个初始参数将会被使用。它们会与传递的参数进行浅合并。初始参数被指定为initialParams 属性:

<Stack.Screen
  name="Details"
  component={DetailsScreen}
  initialParams={{ itemId: 42 }}
/>

4.2 传递参数到之前的页面

不仅仅能传递参数到新的页面,也能传递参数到之前的页面。例如,在页面上创建一个post按钮,并且点击这个按钮创建一个新的post页面。在创建了post页面以后,你想传递一些数据到上一个页面。

想做到这个,你可以使用navigate的方法,如果页面存在的话,可以使用像goBack这样的方法。你可以通过navigate携带参数将参数传回去:

function HomeScreen({ navigation, route }) {
  React.useEffect(() => {
    if (route.params?.post) {
      // Post updated, do something with `route.params.post`
      // For example, send the post to the server
    }
  }, [route.params?.post]);

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button
        title="Create post"
        onPress={() => navigation.navigate('CreatePost', {
            text: route.params?.post ? route.params?.post : '',
        })}
      />
      <Text style={{ margin: 10 }}>Post: {route.params?.post}</Text>
    </View>
  );
}

function CreatePostScreen({ navigation, route }) {
  //将已经输入的内容,传递过来赋值给TextInput的处理逻辑
  let {text} = route.params;
  let [postText, setPostText] = React.useState(text);

  return (
    <>
      <TextInput
        multiline
        placeholder="What's on your mind?"
        style={{ height: 200, padding: 10, backgroundColor: 'white' }}
        value={postText}
        onChangeText={setPostText}
      />
      <Button
        title="Done"
        onPress={() => {
          // Pass params back to home screen
          navigation.navigate('Home', { post: postText });
        }}
      />
    </>
  );
}

在Snack上尝试编写

当点击”Done“按钮,TextInput输入的内容就会回传到主页面上并刷新展示在页面上。

4.3 嵌套的导航页面传递参数

如果你有一个嵌套的导航器,传递参数有些许不同。例如,你有一个叫做Account的页面,想要传递参数到Settings页面。需要做如下操作:

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

了解更多请点击Nesting navigators

4.4 总结

5 配置header bar

我们已经知道怎么配置header标题,但是让我们在学习其他options之前再回顾一遍——复习是学习的关键

5.1 设置header标题

组件接受options prop,它是一个对象或返回一个对象的函数,它包含各种配置选项。其中一个就是title,看下面的例子:

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

在Snack上尝试"header title"

5.2 在title中使用参数

为了在title中使用参数,我们需要为页面设置一个返回配置对象的函数。在options中尝试使用this.props是很有用的,但是在组件渲染之前,没有引用组件的实例,因此没有可用的props。相反,如果我们将options设置为一个函数,那么React Navigation将用一个包含{Navigation, route}的对象调用它——在这种情况下,我们所关心的是route,它与作为route prop传递给页面prop的对象是同一对象。还记得我们可以通过route得到参数。参数,下面我们用这个来提取参数并使用它作为标题。

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>
  );
}

在Snack上尝试"params in title"

传入选项函数的参数是一个具有以下属性的对象:

在上面的例子中我们只需要route属性,在某些案例中,我们还需要用到navigation属性。

5.3 用setOptions更新options

通常需要在安装的屏幕组件本身更新活动页面的选项配置。我们可以使用navigation.setOptions来做实现。

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

在Snack上尝试"updating navigation options"

5.4 调整header样式

自定义header样式有3个关键属性:headerStyle,headerTintColor,和 headerTitleStyle

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

在Snack上尝试"header styles"

需要注意:

  1. 在iOS上,状态栏上的字体和icon是黑色的,在深色的背景上显示不是很友好,我们不会在这里讨论它,但是您应该确保配置状态栏,使其适应状态栏指南您的屏幕颜色。
  2. 我们仅仅在主页面配置了这些,当跳转到详情页面时,header又会恢复原来的样式。接下来,我们看看如何在页面之间共享样式。

5.5 页面之间共享options

通常,在多个页面上我们会设置一个相同样式的header。例如,你的公司品牌颜色是红色,然后页面header想设置为红色背景和白色字体。在上面的例子中,我们配置了主页面header的颜色,当我们跳转到详情页面时,header恢复到默认的样式了。如果我们将主页面的样式配置复制到详情页面上,那会很麻烦,如果app每个页面都需要这个样式呢?我们不用这么做,我们可以将样式配置移动到Stack.Navigator下的screenOptions属性上。

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>
  );
}

在Snack上尝试"sharing header styles"

现在,属于StackScreen上的页面都会有一个主题样式,但是,如果我们需要重载这些配置,有什么方法呢?

5.6 用自定义组件代替标题

有时候,你不仅仅只是想改变title的text和样式 -- 比如,你想用一张图片替代标题,或者将标题设置为按钮。在这些案例中,你完全可以使用自定义的组件来重载标题。

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>
  );
}

在Snack上尝试"custom header title component"

你可能注意到,为什么我们设置headerTitle为一个组件,而不是像之前一样设置title?因为headerTitle是Stack.Navigator的一个属性,headerTitle默认是一个Text组件,用来展示title的。

5.7 额外的配置

你可以在createStackNavigator reference里浏览stack navigator里所有的配置列表。

5.8 总结

(未完待续)

上一篇下一篇

猜你喜欢

热点阅读