React Navigation-Navigation Stack

原來這就是「刷過去」的效果。

置頂說明

後來我發現原來 Stack 有分兩種:

簡單來說一個是用 JS 去寫的,可以客製化的程度比較高,但可能也會有比較多 bug。後者的話則是用原生的 navigation,所以限制會變多,但 bug 會少一點。

這篇文章用的是 Native 的 Stack,不過大致上沒什麼問題,還是可以參考看看。

自我檢測

  • 我知道什麼是 Navigation Stack
  • 我知道什麼是 Drawer navigation
  • 我知道怎麼用 Navigate 來切換 screen
  • 我知道怎麼在 Screen 之間傳遞與接收 props

簡介

首先要特別講一下,在 App 裡面的導覽跟 Web 裡的導覽思路是不同的。

我們在 Web 中可以透過「URL」來判斷目前要顯示哪個元件,這很合理沒有問題,但 App 呢?仔細想想看一件事情:

App 有 URL 這玩意兒嗎?

當然沒有。

所以這邊就要介紹一下 App 的導覽思路。

首先核心觀念是「Stack Navigate」,還有一個叫做「Drawer navigation」的觀念,也是蠻重要的,但這之後才會介紹,這一篇還不會提到。總之,值得關注的是 Stack 這個關鍵字(是一種資料型態)。

簡單來說,假設我們有 Home 頁面跟 Another 頁面,那當我們第一次進入 App 時,我們的 Stack Navigate 就會長這樣:

取自 The Net Ninja 的教學影片

接著當我們進入到 Another 頁面時,就會把他「疊上去 Stack」,變成這樣:

取自 The Net Ninja 的教學影片

所以現在 Home 還是放在那邊,只是我們用 Another 把他蓋住了而已。

接著當我們再去下一個 Another 頁面時,就會變成這樣:

取自 The Net Ninja 的教學影片

好,接著我們想回到 Home 頁面的話怎麼做?如果你知道 Stack 的運作的話(不懂的話可以複習 資料結構-Stack 與 Queue),就會知道我們要:

  1. 把第一個 Another pop 掉
  2. 把第二個 Another pop 掉
  3. 回到 Home 頁面

取自 The Net Ninja 的教學影片

這個就是在 App 裡實作導覽的方式,我覺得還蠻特別的,不知道你怎麼想?

需要的套件

這邊會用 React Navigation 來做示範,詳細可以參考 官方文件

主要會裝的套件有這幾個:

  • @react-navigation/native (npm)
  • @react-navigation/native-stack (npm)
  • react-native-screens (expo)
  • react-native-safe-area-context (expo)

用法

基本頁面

這邊為了方便閱讀我就全部寫一起了,程式碼也不多:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Detail from './screens/Detail';
import About from './screens/About';
import Home from './screens/Home';
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.Screen name='Home' component={Home} options={{ title: 'PeaNu' }} />
<Stack.Screen name='About' component={About} />
<Stack.Screen name='Detail' component={Detail} />
</Stack.Navigator>
</NavigationContainer>
);
}

看起來有一種 React-router-dom 的既視感吧?總之重點有兩個地方:

  • NavigationContainer 負責包住所有 Screnn,就跟 React-router-dom 裡的 HashRouter 是差不多的概念
  • createNativeStackNavigator 建立 Stack,所有 Screnn 都會疊在這裡面

他的機制是這樣,在 Stack.Navigator 裡面的第一個 Screen 會被放入 Navigation Stack,以上面來說的話就是 Home 這個頁面。

這個頁面會是我們進入 App 時的「預設頁面」,如果要用圖來表示目前的 Navigation Stack 的話,會像這樣:

stack-view-01

假設進來後,我們又進入到 Detail 的話就會變這樣:

stack-view-02

這個就是他背後運作的方式,也是前面介紹的 Navigation Stack 的運作流程。

切換到其他頁面

這邊只要知道一件事就夠了,官方文件中有提到:

Each screen component in your app is provided with the navigation prop automatically.

附註:想知道有哪些相關的 API 可以到 這邊 來看。

意思就是我們放在 Stack 裡面的頁面都會自動被傳入 navigation 這個 props,所以只要拿進來用就好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { View, Text, Button } from "react-native";
import { globalStyles } from "../style/globalStyle";


type HomeProps = {
navigation: any
}

// 接收到 props
const Home: React.FC<HomeProps> = ({ navigation }) => {
return (
<View style={globalStyles.container}>
<Text style={globalStyles.titleText}>Home screen</Text>
{/* 加上一個跳轉的按鈕 */}
<Button title="Go to Detail" onPress={() => navigation.navigate('Detail')} />
</View>
)
}

export default Home;

附註:其實寫成 navigation.push('Detaik') 也行,不過比較少人這樣寫就是了。

只要這樣子做,我們就有一個可以跳轉的按鈕了:

navigate-button

當我們按下按鈕後,就會跳轉到我們指定的頁面(會對應到 name):

navigate-switch-to-new-screnn

這邊的 <(返回按鈕)是自動產生的,如果你也想要自製一個類似的按鈕,可以用 navigation.goBack() 的方式來實作。

在不同的 Screen 之間傳遞 props

這應該是個很常見的需求,假設我有 HomeDetail 兩個頁面,Home 負責顯示列表的標題,Detail 顯示列表的內容,那我該怎麼把放在 Home 這邊的資料傳給 Detail 來顯示?

首先,navigation.navigate() 是有第二個參數的,這個參數就是用來讓我們傳遞 props 的,所以 Home 就可以這樣子寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { useState } from "react";
import { View, FlatList, TouchableOpacity, Text } from "react-native";
import { globalStyles } from "../style/globalStyle";


interface Reviews {
name: string,
body: string,
rating: number,
key: string
}

type HomeProps = {
navigation: any
}

const Home: React.FC<HomeProps> = ({ navigation }) => {

const [reviews, setReviews] = useState<Reviews[]>([
{ name: 'Zelda, Breath of Fresh Air', rating: 5, body: 'lorem ipsum', key: '1' },
{ name: 'Gotta Catch Them All (again)', rating: 4, body: 'lorem ipsum', key: '2' },
{ name: 'Not so "Final" Fantasy', rating: 3, body: 'lorem ipsum', key: '3' },
])

return (
<View style={globalStyles.container}>
<FlatList
contentContainerStyle={{ paddingVertical: 20, justifyContent: 'center', alignItems: 'center' }}
data={reviews}
renderItem={({ item }) => (
// 把要傳的 props 放在第二個參數
<TouchableOpacity onPress={() => navigation.navigate('Detail', item)}>
<Text>{item.name}</Text>
</TouchableOpacity>
)}
/>
</View>
)
}

export default Home;

這樣就成功傳進去了,接下來的第二個問題是:要怎麼接收?

這時候 Detail 裡會自動接收到一個 props,叫做 route,所以可以這樣取得:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { View, Text } from "react-native";
import { globalStyles } from "../style/globalStyle";

type DetailProps = {
route: any
}

const Detail: React.FC<DetailProps> = ({ route }) => {
// route.params 就是從 Home 傳進去的 item,只是用解構拿出來而已
const { name, body, rating } = route.params

return (
<View style={globalStyles.container}>
<Text>{name}</Text>
<Text>{body}</Text>
<Text>{rating}</Text>
</View>
)
}

export default Detail;

在 Title 顯示 props 傳進來的參數

只是想特別寫一下,這功能應該蠻常用到的。

附註:有需要的話可以參考官方文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const HomeStack: React.FC = () => {
return (
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#eee'
},
headerTintColor: '#333',
}}
>
<Stack.Screen
name='Home'
component={Home}
options={{
title: 'GameZone',
}}
/>
<Stack.Screen
name='Detail'
component={Detail}
// 重點是這一行
options={({ route }: any) => ({ title: route.params.name }) }
/>
</Stack.Navigator>
)
}

export default HomeStack;

設定 navbar 的樣式

如果想改變預設的樣式,可以用 optionsscreenOptions 這個 props 來更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export default function App() {
return (
<NavigationContainer>
{/* 寫在這邊就是 global style */}
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#eee'
}
}}
>
{/* local style */}
<Stack.Screen
name='Home'
component={Home}
options={{
title: 'GameZone',
headerTintColor: '#333',
}} />
<Stack.Screen name='Detail' component={Detail} options={{
headerBackTitleStyle: {
fontSize: 20
}
}}/>
</Stack.Navigator>
</NavigationContainer>
);
}

想知道有哪些東西可以調的話,可以參考這裡

React Navigation-Drawer Navigation React Native 中的 style 和 layout
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×