原來這就是「刷過去」的效果。
置頂說明
後來我發現原來 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 就會長這樣:

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

所以現在 Home 還是放在那邊,只是我們用 Another 把他蓋住了而已。
接著當我們再去下一個 Another 頁面時,就會變成這樣:

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

這個就是在 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 的話,會像這樣:

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

這個就是他背後運作的方式,也是前面介紹的 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 }
 
  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') 也行,不過比較少人這樣寫就是了。
只要這樣子做,我們就有一個可以跳轉的按鈕了:

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

這邊的 <(返回按鈕)是自動產生的,如果你也想要自製一個類似的按鈕,可以用 navigation.goBack() 的方式來實作。
在不同的 Screen 之間傳遞 props
這應該是個很常見的需求,假設我有 Home 跟 Detail 兩個頁面,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 }) => {      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 的樣式
如果想改變預設的樣式,可以用 options 或 screenOptions 這個 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>   ); }
  | 
 
想知道有哪些東西可以調的話,可以參考這裡