一、 React Navigation简介

在多页面应用程序中,页面的跳转是通过路由或导航器来实现的。在RN利用开发过程中,晚期的路由能够间接应用官网提供的Navigator组件,不过从0.44版本开始,Navigator被官网从react native的外围组件库中剥离进去,放到react-native-deprecated-custom-components的模块中,并且Navigator组件也不再被官网举荐应用。此时,咱们能够抉择由React Native社区开源的一款路由导航库React Navigation。

和之前的版本一样,React Navigation反对的导航类型有三种,别离是StackNavigator、TabNavigator和DrawerNavigator。

  • StackNavigator:蕴含导航栏的页面导航组件,相似于官网的Navigator组件。
  • TabNavigator:底部展现tabBar的页面导航组件。
  • DrawerNavigator:用于实现侧边栏抽屉页面的导航组件。

1.1 装置

应用React Navigation之前,须要在你的React Native我的项目中装置所需的软件包,装置的形式分为npm和 yarn两种形式,如下所示。

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

目前为止,咱们所装置的包都是导航的一些基础架构所须要的包,如果要创立不同的导航栈成果,须要装置独自的插件宝。常见的有如下插件:react-native-gesture-handler, react-native-reanimated, react-native-screens 、react-native-safe-area-context 和@react-native-community/masked-view,而后运行如下命令装置插件。

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

须要的依赖库的作用如下所示:

  • react-native-gesture-handler:用于手势切换页面。
  • react-native-screens:用于原生层开释未展现的页面,改善 app 内存应用。
  • react-native-safe-area-context: 用于保障页面显示在平安区域(次要针对刘海屏)。
  • @react-native-community/masked-view:用在头部导航栏中返回按钮的色彩设置。
  • @react-navigation/native 为 React Navigation 的外围。

官网文档中提到的 react-native-reanimated,该依赖在应用 DrawerNavigator 才用的到,不知道为啥放到了总的装置文档中,尽量都装置上。

React Native 0.60 及其以上版本,不须要再运行react-native link 来装置原生库,如果没有链接上原生的库,也能够应用上面的命令进行装置。

npx pod-install ios

1.2 RN的提醒插件

自从2019年《React Native挪动开发实战 第2版》出版之后,我对RN的关注就比拟少。并且因为更新了WebStrom如同之前装置的插件都生效了。

在RN开发中,如果没有装置代码提醒插件,编写代码是十分苦楚的,好在我应用之前的ReactNative.xml还能提醒。装置ReactNative.xml的步骤如下:
1,下载ReactNative.xml,链接地址ReactNative.xml
2,而后/Users/mac/Library/Application Support/JetBrains/WebStorm2020.1/目录下新建templates文件,并将下面下载的ReactNative.xml拷贝进去。
3,重启WebStrom,就能够看到提醒了

二、 Hello React Navigation

在Web浏览器利用开发中,咱们能够应用锚(标记)链接到不同的页面,当用户单击链接时,URL被推到浏览器历史堆栈中。当用户按下返回按钮时,浏览器会从历史堆栈的顶部弹出以前拜访过的页面。而在React Native开发中,咱们能够应用React Navigation的堆栈导航器来治理页面的跳转和导航栈的治理。

为了实现多页面的栈治理,咱们须要用到React Navigation提供的createStackNavigator组件,并且应用前请确保装置了如下的插件。

npm install @react-navigation/stack

2.1 堆栈导航器

createStackNavigator是一个函数,它返回的对象蕴含两个属性,即屏幕和导航器,它们都是用于配置导航器的React组件。而NavigationContainer是一个治理导航树并蕴含导航状态的组件,该组件必须包装所有导航器构造。

为此咱们新建一个治理路由页面的App.js文件,并增加如下代码:

// In App.js in a new projectimport * as React from 'react';import { View, Text } from 'react-native';import { NavigationContainer } from '@react-navigation/native';import { createStackNavigator } from '@react-navigation/stack';function HomeScreen() {  return (    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>      <Text>Home Screen</Text>    </View>  );}const Stack = createStackNavigator();function App() {  return (    <NavigationContainer>      <Stack.Navigator>        <Stack.Screen name="Home" component={HomeScreen} />      </Stack.Navigator>    </NavigationContainer>  );}export default App;

而后,咱们批改RN我的项目的index.js文件的入口文件的门路为下面的App.js文件的门路,并运行我的项目,成果如下图所示。

2.2 配置导航器

所有的路由配置都会对应导航器上的属性,因为咱们还没有设置咱们导航器的任何属性,应用的都是默认的配置,当初咱们来为咱们的堆栈导航器增加第二个screen。

function DetailsScreen() {  return (    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>      <Text>Details Screen</Text>    </View>  );}const Stack = createStackNavigator();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组件的形式增加路由。Screen组件有一个name属性用来指定路由的名字,还有一个component属性,用来指定咱们将要渲染的组件。在下面的示例代码中,Home路由对应的是HomeScreen组件,Details路由对应的是DetailsScreen组件。

须要阐明的是,component属性接管的是组件而不是一个render()办法。所以不要给它传递一个内联函数(比方component={()=><HomeScreen />}),否则你的组件在被卸载并再次加载的时候回失落所有的state。

2.3 设置option参数

导航器中的每一个路由页面都能够设置一些参数,例如title。

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

如果要传递一些额定的属性,还能够应用一个render函数作为组件的属性,如下所示。

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

三、 路由栈页面跳转

一个残缺的利用通常是由多个路由形成的,并且路由之间能够互相跳转。在Web浏览器利用开发中,咱们能够应用如下的形式实现页面的跳转。

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

当然,也能够应用如下的形式。

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

3.1 跳转到一个新页面

那对于React Navigation来说,怎么跳转到一个新的页面呢?因为在React Navigation栈导航器中,navigation属性会被传递到每一个screen组件中,所以咱们能够应用如下的形式执行页面的跳转。

import * as 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

在下面的代码中,咱们用到了navigation和navigate()办法。

  • navigation :在堆栈导航器中,navigation属性会被传递到每一个screen组件中。
  • navigate() : 咱们调用navigate办法来实现页面的跳转。

运行下面的代码,运行成果如下图所示。

如果咱们调用navigation.navigate办法跳转到一个咱们没有在堆栈导航器中定义的路由的话会怎么呢,什么事件也不会产生。

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

运行下面的代码,当你点击【Go to Details】时,什么事件都不会产生,这是因为咱们曾经在Details页面了。

当初咱们假如一下,如果是咱们想要增加另一个详情页面呢?这是一个很常见的场景,在这个场景中咱们想在同一个Details页面展现不同的数据。为了实现这个 ,咱们能够用push()办法来代替navigate()办法,push()办法能够间接向堆栈中增加新的路由而忽视以后导航历史。

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

运行下面的代码,成果如下图所示。

3.3 路由返回

堆栈导航器提供的导航头默认蕴含一个返回按钮,点击按钮能够返回到上一个页面,如果导航堆栈中只有一个页面,也就是说并没有能够返回的页面的时候,这个回退按钮就不显示。

如果咱们应用的是自定义的导航头,能够应用navigation.goBack()办法来实现路由返回,如下所示。

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

另一个常见的需要就是返回到多个页面。比方,如果你的堆栈中有多个页面,你想顺次移除页面而后返回到第一个页面上。在下面的场景中,咱们想要返回Home页面,所以咱们能够应用navigate('Home')

另一种办法就是navigation.popToTop(),这个办法将会返回到堆栈中的第一个页面上(初始页面),如下所示。

function DetailsScreen({ navigation }) {  return (    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>      <Text>Details Screen</Text>      <Button        title="Go to Details... again"        onPress={() => navigation.push('Details')}      />      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />      <Button title="Go back" onPress={() => navigation.goBack()} />      <Button        title="Go back to first screen in stack"        onPress={() => navigation.popToTop()}      />    </View>  );}

四、路由传参

4.1 根本用法

在页面跳转过程中,免不了会进行数据的传递。在React Navigation中,路由之间传递参数次要有两种形式:

  • 将参数封装成一个对象,而后将这个对象作为navigation.navigate办法的第二个参数,从而实现路由跳转参数的传递。
  • 应用route.params()办法读取传递过去的参数。

不论是哪种形式,咱们倡议对须要传递对数据进行序列化,如下所示。

import * as React from 'react';import { Text, View, Button } 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={() => {          /* 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>  );}const Stack = createStackNavigator();export default function App() {  return (    <NavigationContainer>      <Stack.Navigator>        <Stack.Screen name="Home" component={HomeScreen} />        <Stack.Screen name="Details" component={DetailsScreen} />      </Stack.Navigator>    </NavigationContainer>  );}

4.2 更新参数

在路由页面上也能够更新这些参数,navigation.setParams()办法就是用来更新这些参数的。当然,咱们也能够设置所传参数的默认值,当跳转页面时没有指定这些参数的值,那么他们就会应用默认值。参数的默认值能够在 initialParams属性中指定,如下所示。

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

4.3 返回参数给上一个路由

咱们将将数据通过参数的形式传递给一个新的路由页面,也能够将数据回传给先前路由的页面。例如,有一个路由页面,该页面有一个创立帖子的按钮,这个按钮能够关上一个新的页面来创立帖子,在创立完帖子之后,须要想将帖子的一些数据回传给先前的页面。

对于这种需要,咱们能够应用navigate来实现。如果路由曾经在堆栈中存在了,他们他看起来就像goBack一样,而后只须要将数据绑定到nvigate的params回传给上一页即可,代码如下。

import * as React from 'react';import { Text, TextInput, View, Button } from 'react-native';import { NavigationContainer } from '@react-navigation/native';import { createStackNavigator } from '@react-navigation/stack';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 style={{ margin: 10 }}>Post: {route.params?.post}</Text>    </View>  );}function CreatePostScreen({ navigation, route }) {  const [postText, setPostText] = React.useState('');  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 });        }}      />    </>  );}const Stack = createStackNavigator();export default function App() {  return (    <NavigationContainer>      <Stack.Navigator mode="modal">        <Stack.Screen name="Home" component={HomeScreen} />        <Stack.Screen name="CreatePost" component={CreatePostScreen} />      </Stack.Navigator>    </NavigationContainer>  );}

当你点击【Done】按钮后,home页面的route.params将会被更新并刷新文本。

4.4 参数传递给嵌套导航器

如果波及有嵌套导航器的场景,则须要以略微不同的形式传递参数。例如,假如在Acount页面嵌套了一个设置页面,并且心愿将参数传递到该导航器中的设置页面,那么能够应用上面的形式。

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

五、导航栏配置

5.1 设置导航栏题目

React Navigation的Screen组件有一个options属性,这个属性蕴含了很多可配置的选项,例如设置导航栏的题目,如下所示。

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

5.2 在题目中应用参数

为了在题目中应用参数,咱们能够把options属性定义为一个返回配置对象的办法。即咱们能够将options定义为一个办法,React Navigation调用它并为其传入两个可供使用的参数{navigation,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>  );}

传递到options办法中的参数是一个对象,该对象有两个属性navigation和route。

  • navigation:路由屏幕组件的navigation属性
  • route:屏幕组件的route属性

5.3 应用setOptions更新options

有时候,咱们须要在一个已加载的屏幕组件上更新它的options配置,对于这样的需要,咱们能够应用navigation.setOptions来实现。

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

5.4 设置导航栏的款式

React Navigation反对自定义导航头的款式,咱们能够应用如下三个款式属性 headerStyle、headerTintColor和headerTitleStyle,含意如下:

  • headerStyle:用于设置包裹住导航头的视图款式,比方为导航栏设置背景色。
  • headerTintColor:回退按钮和title都是应用这个属性来设置他们的色彩。
  • headerTitleStyle:如果须要自定义字体、字体粗细和其余文本款式,能够应用属性。
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>  );}

上面是演示示例的链接地址:Try the "header styles" example on Snack

自定义导航栏款式的时候,有以下几点须要留神:

  • 在IOS上,状态栏文本和图标是彩色的,所以你将背景色设置为暗色的话可能看起来不是很好。
  • 以上的设置仅仅只在home页面无效,当咱们跳转到details页面的时候,仍然会显示默认的色彩。

5.5 与其余屏幕组件共享options参数

有时候,咱们须要多个页面设置雷同的导航头款式。那么,咱们能够应用堆栈导航器的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>  );}

在下面的例子中,任何属于StackScreen的页面都将应用雷同款式的导航头。

5.6 自定义的组件来替换title属性

有时候,咱们想对title做更多的设置,比方应用一张图片来替换title的文字,或者将title放进一个按钮中,这些场景中咱们齐全能够应用自定义的组件来笼罩title,如下所示。

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

六、 导航栏按钮

6.1 为导航栏增加按钮

通常,咱们能够应用导航头的按钮来实现路由操作。比方,咱们点击导航栏右边的按钮能够返回上一个路由,点击导航头的左边能够进行其余操作,如下所示。

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

6.2 导航头与屏幕组件的交互

咱们只须要应用navigation.setOptions即可实现按钮与屏幕组件的交互,能够应用navigation.setOptions来拜访screen组件的属性如,state和context等。

function StackScreen() {  return (    <Stack.Navigator>      <Stack.Screen        name="Home"        component={HomeScreen}        options={({ navigation, route }) => ({          headerTitle: props => <LogoTitle {...props} />,        })}      />    </Stack.Navigator>  );}function HomeScreen({ navigation }) {  const [count, setCount] = React.useState(0);  React.useLayoutEffect(() => {    navigation.setOptions({      headerRight: () => (        <Button onPress={() => setCount(c => c + 1)} title="Update count" />      ),    });  }, [navigation]);  return <Text>Count: {count}</Text>;}

6.3 自定义返回按钮

createStackNavigator提供了平台默认的返回按钮,在IOS平台上,在按钮的旁边会有一个标签,这个标签会显示上一页题目的缩写,否则就是一个Back字样。

同样,开发者也能够能够通过设置headerBackTitle和headerTruncatedBackTitle来扭转标签的行为,如下所示。

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

七、导航栏的嵌套

导航嵌套指的是一个导航器的导航页中又蕴含了另一个导航器,例如。

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} />        <Stack.Screen name="Profile" component={Profile} />        <Stack.Screen name="Settings" component={Settings} />      </Stack.Navigator>    </NavigationContainer>  );}

在上述的例子中,Home组件蕴含了一个tab导航器。同时,这个Home组件也是堆栈导航器中的一个导航页,从而造成了一个导航嵌套的场景。

导航器的嵌套其实跟咱们通常应用的组件的嵌套相似,个别状况下,咱们通常会嵌套多个导航器。

7.1 如何实现导航嵌套

实现导航器嵌套时,有以下几点须要特地留神:

1,每个导航器治理本人的导航栈

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

2,导航器的一些特定办法在子导航器中同样可用

在Drawer导航器中嵌套了一个Stack 导航器,那么Drewer导航器的openDrawer、closeDrawer办法在被嵌套的stack导航器的navigation属性中仍然是可用的。然而如果Stack导航器没有嵌套在Drawer导航器中,那么这些办法是不可拜访的。

同样,如果你在Stack导航器中嵌套了一个Tab导航器,那么Tab导航器页面中的navigation属性会新失去push和replace这两个办法。

3, 被嵌套的导航器不会响应父级导航器的事件

如果Stack导航器被嵌套在tTab导航器中,那么Stack导航器的页面不会响应由父Tab导航器触发的事件,比方咱们应用navigation.addListener绑定的tabPress事件。为了可能响应父级导航器的事件,咱们能够应用navigation.dangerouslyGetParent().addListener来监听父级导航器的事件。

4,父级导航器先于子导航器被渲染

对于下面的这句话,咱们怎么了解呢?在Drawer导航器中嵌套了一个Stack导航器,你会看到抽屉的成果先被渲染进去,接着才是渲染Stack导航器的头部,然而如果将Drawer导航器嵌套在Stack导航器中,那么则会先渲染Stack导航器的头部再渲染抽屉成果。因而,咱们能够依据这一景象来确认咱们须要抉择哪种导航器的嵌套。

7.2 嵌套路由的跳转

咱们先来看一下上面这段代码。

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

如果,当初想从Home页面中跳转到Root页面,那么咱们能够应用上面的形式。

navigation.navigate('Root');

不过,有工夫你可能心愿显示本人指定的页面,为了实现这个性能,能够在参数中携带页面的名字,如下所示。

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

当然,咱们也能够在页面跳转的时候传递一些参数,如下所示。

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

7.3 嵌套多个导航栏

当嵌套多个stack 或者drawer的导航栏时,子导航栏和父导航栏的题目都会显示进去。然而,通常更可取的做法是在子导航器中显示题目,并在堆栈导航器中暗藏题目。此时咱们能够应用导航栏的headerShown: false

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

八、导航器的生命周期

在后面的内容中,咱们介绍了React Navigation的一些根本用法,以及路由跳转相干的内容。然而,咱们并不知道路由是如何实现页面的跳转和返回的。

如果你对前端Web比拟相熟的话,那么你就会发现当用户从路由A跳转到路由B的时候,A将会被卸载(componentWillUnmount办法会被调用)当用户从其余页面返回A页面的时候,A页面又会被从新加载。React 的生命周期办法在React Navigation中依然无效,不过相比Web的用法不尽相同,且挪动端导航更为简单。

8.1 Example

假如有一个Stack导航器,外面A、B两个页面。当须要跳转到A页面的时候,它的componentDidMount将会被调用。当跳转到B页面的时候,B页面的compouentDidMount也会被调用,然而A页面仍然在堆栈中放弃被加载的状态,因而A页面的componentWillUnmount办法不会被调用。

然而,当咱们从B页面返回到A页面的时候,B页面的compouentWillUnmount则会被调用,然而A页面的componentDidMount不会被调用,因为其没有被卸载,在整个过程中始终放弃被加载的状态。

在其余类型的导航器中也是同样的后果,假如有一个Tab导航器,其有两个Tab,每个Tab都是一个Stack导航器。

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

当初,咱们从HomeScreen跳转到DetailsScreen页面,而后应用标签栏从SettingScreen 跳转到ProfileScreen。在这一系列操作做完后,所有的四个页面都曾经被加载过了,如果你应用标签栏返回到HomeScreen,你会发现DetailsScreen页面到HomeStack的导航状态曾经被保留。

8.2 React Navigation生命周期

咱们晓得,React Native的页面都有本人的生命周期。而React Navigation则是通过将事件发送到订阅他们的页面组件中,并通过focus()和blur()办法监听用户来到返回事件,看上面一段代码。

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

事实上,咱们没必要手动增加监听器,能够间接应用useFocusEffect 挂钩来实现生命周期的监听,就像React的useEffect挂钩一样,不同的是,useFocusEffect只能用户导航器的生命周期监听,如下所示。

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 挂钩,useIsFocused会返回一个布尔值,用来批示该页面是否取得了焦点。

总的来说,React 的生命周期办法依然可用,除此之外,React Navigation又减少了更多的事件办法,开发者能够通过navigation属性来订阅他们。并且,开发者能够应用useFocusEffect或者useIsFocused实现挂钩。

九、关上一个Modal 全面屏页面

在RN中,Model是一个弹窗组件,它不是导航中的页面,因而其显示与暗藏都有其独特的形式,咱们能够应用它展现一些特地的提示信息,如下图所示。

9.1 创立一个Model堆栈导航器

首先,咱们来看一下如下一段代码:

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 MainStack = createStackNavigator();const RootStack = createStackNavigator(); function MainStackScreen() {  return (    <MainStack.Navigator>      <MainStack.Screen name="Home" component={HomeScreen} />      <MainStack.Screen name="Details" component={DetailsScreen} />    </MainStack.Navigator>  );} function RootStackScreen() {  return (    <RootStack.Navigator mode="modal">      <RootStack.Screen        name="Main"        component={MainStackScreen}        options={{ headerShown: false }}      />      <RootStack.Screen name="MyModal" component={ModalScreen} />    </RootStack.Navigator>  );}

首先,咱们来看一下下面示例中导航器的示意图。

在下面的示例中,咱们应用MainStackScreen作为RootStackScreen的屏幕组件,就相当于咱们在一个堆栈导航器中嵌入了另一个堆栈导航器。因而,当咱们运行我的项目时,RootStackScreen会渲染一个堆栈导航器,该导航器有本人的头部,当然咱们也能够将这个头部暗藏。

堆栈导航器的model属性值能够是card(默认)或者model。在IOS上,model的展示形式是从底部滑入,退出的时候则是从上往下的形式来敞开它。在Android上,model是有效的属性,因为全面屏的model与android的平台自带的行为没有任何区别。

Model堆栈导航器在React Navigation导航中是十分有用的,如果你想变更堆栈导航器页面的过渡动画,那么就能够应用mode属性。当将其设置为modal时,所有屏幕转换之间的过渡动画就会变为从底部滑到顶部,而不是原先的从右侧滑入。然而,须要留神的是,React Navigation的modal会对整个堆栈导航器起效,所以为了在其余屏幕上应用从右侧滑入的成果,咱们能够再另外增加一个导航器,这个导航器应用默认的配置就行。

十 、名称解释

10.1 Navigator

Navigator是一个React组件,它决定利用以哪种形式展示已定义的页面。NavigationContainer则是一个治理导航树并蕴含导航状态的组件,该组件是所有导航器组件的父容器,包装了所有导航器的构造。通常,咱们须要将NavigationContainer组件放在应用程序的根目录下,作为启动的根文件。

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

10.2 Router

Router即是路由器,它是一组函数,它决定如何在导航器中解决用户的操作和状态的更改,通常,开发者不须要直接参与路由器交互,除非您须要编写自定义导航器。

每个路由都是一个对象,其中蕴含标识它的键和指定路由类型的“名称”,并且还能够蕴含任意类型的参数。

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

10.3 Screen component

Screen component即屏幕组件,是咱们在路由配置中须要跳转的组件。

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

须要留神的是,只有当屏幕被React Navigation出现为一个路由时能力实现跳转性能。例如,如果咱们渲染DetailsScreen作为主屏幕的子元素,那么DetailsScreen将不会提供导航道具,当你在主屏幕上按下“Go to Details…”按钮时应用程序会抛出【未定义该对象】”的谬误。

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

10.4 Navigation Prop

通过应用Navigation提供的属性,咱们能够在两个页面之间传递数据,罕用的属性如下。

  • dispatch: 向路由器发送一个动作。
  • navigate, goBack:关上或者返回页面。

10.5 Navigation State

导航器的状态如下所示。

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