這是我在2023第十五屆 iThome 鐵人賽發表的系列文章。https://ithelp.ithome.com.tw/users/20136637/ironman/6408
這篇文章主要會提到一些我自己學習時會有疑問的點,所以安裝方式那些就不提啦。
Bottom tabs
基本寫法如下:
import React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { HomeScreen, UserScreen } from '@screens'
const Tab = createBottomTabNavigator()
const App = (): JSX.Element => {
return (
<NavigationContainer>
<Tab.Navigator initialRouteName="Home">
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="User" component={UserScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
export default App
Tabs icon
預設 icon 是個倒三角形(iOS, Android 則是顯示不出來):
Tab.Navigator 的 screenOptions
裡面有兩個屬性:
- tabBarActiveTintColor: tab 被選中時 icon 的顏色
- tabBarInactiveTintColor: tab 沒被選中時的顏色
Tab.Screen 的 options
裡面有很多關於 tabBar 的屬性,這邊簡單列四個:
- tabBarIcon: tab 的 icon,有三個參數
color
,size
,focused
- tabBarLabel: tab 的名稱
- tabBarStyle: 整個 bottom tabs 的樣式
- tabBarIconStyle: 這個 tab icon 的樣式,設成
display: 'none'
可以單獨隱藏 tab 的 icon
<Tab.Navigator
initialRouteName="Home"
screenOptions={{
tabBarActiveTintColor: '#e91e63',
tabBarInactiveTintColor: 'gray',
}}
>
<Tab.Screen
name="Home"
options={{
tabBarIcon: ({ color, size }) => (
<Icon name="home" color={color} size={size} />
)
}}
component={HomeScreen}
/>
</Tab.Navigator>
隱藏 header
預設每個頁面都會顯示 header ,內容為頁面名稱:
如果不想要顯示的話,在 Tab.Navigator 的 screenOptions 設置 headerShown: false
即可隱藏。
<Tab.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
tabBarActiveTintColor: '#e91e63',
tabBarInactiveTintColor: 'gray'
}}
>
<Tab.Screen
name="Home"
// ...
單獨頁面隱藏 bottom tabs
假設我 Scan 頁面不需要顯示 bottom tabs,那我可以將 tabBarStyle 設為 display: 'none'
這樣就不會顯示了
<Tab.Screen
name="Scan"
options={{
tabBarStyle: { display: 'none' },
tabBarIcon: ({ color, size }) => (
<Icon name="camera" color={color} size={size} />
)
}}
component={ScanScreen}
/>
隱藏某個 tab
如果想直接讓某個 tab 消失在 bottom tabs 上,可以設置 tabBarButton: () => null
<Tab.Screen
name="Home"
options={{
tabBarButton: () => null
}}
component={HomeScreen}
/>
Drawer 和 Bottom tabs 合併使用
如果需要 Drawer 和 Bottom tabs 合併使用,只需要將 BottomTabNavigator 包在 Drawer.Navigator 中,所以嵌套結構大致為:Stack
> Drawer
> Tab
App.tsx
import { NavigationContainer } from '@react-navigation/native'
import { DrawerNavigator } from 'navigation/DrawerNavigator'
const Stack = createStackNavigator()
const App = (): JSX.Element => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="DrawerNavigator">
<Stack.Screen
name="DrawerNavigator"
component={DrawerNavigator}
options={{ headerShown: false }}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
DrawerNavigator.tsx
import React from 'react';
import { View, StyleSheet, Image, Text, TouchableOpacity } from 'react-native'
import {
createDrawerNavigator,
DrawerContentScrollView,
DrawerItemList
} from '@react-navigation/drawer'
import { BottomTabNavigator } from './BottomTabNavigator'
import { CustomNavigator } from './CustomNavigator'
const Drawer = createDrawerNavigator()
const DrawerHeaderContent = (props): JSX.Element => {
return (
<DrawerContentScrollView contentContainerStyle={{ flex: 1 }}>
<View>
<Text>Title</Text>
</View>
<DrawerItemList {...props} />
</DrawerContentScrollView>
)
}
export const DrawerNavigator = (): JSX.Element => {
return (
<Drawer.Navigator
screenOptions={{
drawerStyle: {
backgroundColor: '#fff',
},
}}
drawerContent={DrawerHeaderContent}
>
<Drawer.Screen
name="BottomTabNavigator"
component={BottomTabNavigator}
options={{
drawerLabel: 'Home',
drawerIcon: ({ focused, size, color}) => (
<Icon name="home" color={color} size={size} />
),
}}
/>
<Drawer.Screen
name="CustomNavigator"
component={CustomNavigator}
options={{
drawerLabel: 'Custom',
drawerIcon: ({ focused, size, color}) => (
<Icon name="gear" color={color} size={size} />
),
}}
/>
</Drawer.Navigator>
);
};
BottomTabNavigator.tsx
import React from 'react'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
import { HomeScreen } from '@screens/index'
const Tab = createBottomTabNavigator()
export const BottomTabNavigator = (): JSX.Element => {
return (
<Tab.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
tabBarActiveTintColor: '#4682A9',
tabBarInactiveTintColor: 'gray'
}}
>
<Tab.Screen
name="Home"
options={{
tabBarIcon: ({ color, size }) => (
<Icon name="home" color={color} size={size} />
)
}}
component={HomeScreen}
/>
</Tab.Navigator>
)
}
嵌套路由也是一樣的做法,所以就不多說了。
避免組件內容渲染到 Bottom tab
如果有使用 Bottom tabs 的話在 Android 上渲染組件並不會自動避開,所以就會出現組件被覆蓋住的情況:
RN 提供的 SafeAreaView 只有在 iOS 有效:
我的做法是改用 react-native-safe-area-context 這個第三方庫,支持 Android 和 web
這是react-navigation的依賴庫,所以應該已經裝好了,不需要再重裝
import { SafeAreaView } from 'react-native-safe-area-context';
function SomeComponent() {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: 'red' }}>
<View style={{ flex: 1, backgroundColor: 'blue' }} />
</SafeAreaView>
);
}
如果不想用 SafeAreaView 也可以使用 @react-navigation/bottom-tabs 提供的 useBottomTabBarHeight
hook 來獲取 bottom tabs 高度,並用 paddingBottom
隔開:
import { Platform } from 'react-native'
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
const tabBarHeight = useBottomTabBarHeight()
<View
style={[
styles.root,
{ paddingBottom: Platform.OS === 'android' ? tabBarHeight : 0 }
]}
>
{children}
</View>
路由返回到登入頁面怎麼解決
這是我在實際開發中遇到的問題,在應用中狂按上一頁會回到登入頁,但這種情況是不允許發生的,所以要想辦法避免。
應用中回到上一頁的方式是使用 useNavigationState
判斷當前的 index,如果歷史路由紀錄大於 0 的話就 pop 回到上一頁:
// Header.js
import { useNavigationState } from '@react-navigation/native'
const { index } = useNavigationState((prev) => prev)
const goBack = () => {
if (index !== 0) navigation.pop(1)
}
登入成功跳轉到首頁使用的是 navigation.navigate
:
// Login.js
const onSubmit = () => {
// ...
navigation.navigate('BottomTabNavigator', { screen: 'Home' })
}
解決辦法
其實挺好解決的,因為登入成功跳轉到首頁使用的是 navigation.navigate
,改成 navigation.replace
其實就能避免,或者使用 navigation.reset
也可以。
navigation.navigate()
替換成navigation.replace()
navigation.replace('BottomTabNavigator', { screen: 'Home' })
navigation.navigate()
替換成navigation.reset()
navigation.reset({
index: 0,
routes: [
{
name: 'BottomTabNavigator',
params: { screen: 'Home' },
}
]
})
這是 Stack Navigation 示意圖:
圖片來源:https://rahulgurung.com/Introduction-to-React-Native-Navigation/
navigate
方法用於在導航堆棧中添加新頁面。- 會保留導航歷史。
- 適用於從一個頁面到另一個頁面的常規導航。
replace
replace
方法用於替換當前頁面。- 將當前頁面替換為新頁面,並將新頁面推入導航堆棧,但之前的頁面將從導航歷史中移除。
- 用戶無法通過返回按鈕返回到被替換的頁面。
reset
reset
方法用於重置整個導航堆棧。- 你可以選擇將導航堆棧替換為一組新的頁面,以及重置堆棧的索引。
- 用戶無法通過返回按鈕返回到之前的頁面。
關於 react navigation 有一個很不錯的網站推薦給大家,基本上可能遇到的問提這邊都有教學:https://aboutreact.com/react-native/