React Navigation 5.x詳解

1、 React Navigation簡介

在多頁面應用程序中,頁面的跳轉是經過路由或導航器來實現的。在RN應用開發過程當中,早期的路由能夠直接使用官方提供的Navigator組件,不過從0.44版本開始,Navigator被官方從react native的核心組件庫中剝離出來,放到react-native-deprecated-custom-components的模塊中,而且Navigator組件也再也不被官方推薦使用。此時,咱們能夠選擇由React Native社區開源的一款路由導航庫React Navigation。html

和以前的版本同樣,React Navigation支持的導航類型有三種,分別是StackNavigator、TabNavigator和DrawerNavigator。前端

  • StackNavigator:包含導航欄的頁面導航組件,相似於官方的Navigator組件。
  • TabNavigator:底部展現tabBar的頁面導航組件。
  • DrawerNavigator:用於實現側邊欄抽屜頁面的導航組件。

1.1 安裝

使用React Navigation以前,須要在你的React Native項目中安裝所需的軟件包,安裝的方式分爲npm和 yarn兩種方式,以下所示。react

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

目前爲止,咱們所安裝的包都是導航的一些基礎架構所須要的包,若是要建立不一樣的導航棧效果,須要安裝單獨的插件寶。常見的有以下插件:react-native-gesture-handler, react-native-reanimated, react-native-screensreact-native-safe-area-context@react-native-community/masked-view,而後運行以下命令安裝插件。android

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

須要的依賴庫的做用以下所示:ios

  • 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 才用的到,不曉得爲啥放到了總的安裝文檔中,儘可能都安裝上。git

React Native 0.60 及其以上版本,不須要再運行react-native link 來安裝原生庫,若是沒有連接上原生的庫,也可使用下面的命令進行安裝。github

npx pod-install ios

1.2 RN的提示插件

自從2019年《React Native移動開發實戰 第2版》出版以後,我對RN的關注就比較少。而且因爲更新了WebStrom好像以前安裝的插件都失效了。web

在RN開發中,若是沒有安裝代碼提示插件,編寫代碼是很是痛苦的,好在我使用以前的ReactNative.xml還能提示。安裝ReactNative.xml的步驟以下:
1,下載ReactNative.xml,連接地址ReactNative.xml
2,而後/Users/mac/Library/Application Support/JetBrains/WebStorm2020.1/目錄下新建templates文件,並將上面下載的ReactNative.xml拷貝進去。
3,重啓WebStrom,就能夠看到提示了npm

在這裏插入圖片描述

2、 Hello React Navigation

在Web瀏覽器應用開發中,咱們可使用錨(標記)連接到不一樣的頁面,當用戶單擊連接時,URL被推到瀏覽器歷史堆棧中。當用戶按下返回按鈕時,瀏覽器會從歷史堆棧的頂部彈出之前訪問過的頁面。而在React Native開發中,咱們可使用React Navigation的堆棧導航器來管理頁面的跳轉和導航棧的管理。react-native

爲了完成多頁面的棧管理,咱們須要用到React Navigation提供的createStackNavigator組件,而且使用前請確保安裝了以下的插件。

npm install @react-navigation/stack

2.1 堆棧導航器

createStackNavigator是一個函數,它返回的對象包含兩個屬性,即屏幕和導航器,它們都是用於配置導航器的React組件。而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 { 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>

3、 路由棧頁面跳轉

一個完整的應用一般是由多個路由構成的,而且路由之間能夠相互跳轉。在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、路由傳參

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、導航欄配置

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、 導航欄按鈕

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

7、導航欄的嵌套

導航嵌套指的是一個導航器的導航頁中又包含了另外一個導航器,例如。

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

8、導航器的生命週期

在前面的內容中,咱們介紹了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實現掛鉤。

9、打開一個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' },
  ]
}
相關文章
相關標籤/搜索