自訂導覽器
導覽器功能是讓您可以定義應用程式導覽結構。導覽器也會呈現標頭和分頁工具列等常見元素,而且您可以設定它們。
在架構中,導覽器是純粹的 React 元件。
內建導覽器
我們整合了一些常見的導覽器,例如
createStackNavigator
- 一次呈現一個畫面,並在畫面間提供轉場。開啟新畫面時,將顯示在堆疊最上方。createDrawerNavigator
- 提供抽屜式選單,預設從畫面左側滑出。createBottomTabNavigator
- 呈現分頁工具列供使用者在多個畫面間切換。createMaterialTopTabNavigator
- 呈現分頁檢視,讓使用者可以透過滑動手勢或分頁工具列在多個畫面間切換。createMaterialBottomTabNavigator
- 呈現分頁檢視,讓使用者可以透過滑動手勢或分頁工具列在多個畫面間切換。
用於建置自訂導覽器的 API
導覽器套件了路由器和檢視,可接收導覽狀態,並根據狀態決定如何呈現導覽器。我們匯出了 useNavigationBuilder
掛勾,用於建置可與 React Navigation 其他部分整合的自訂導覽器。
useNavigationBuilder
此掛勾允許元件掛接到 React Navigation。它會接受下列參數
-
createRouter
- 工廠方法,會傳回路由器物件 (例如:StackRouter
、TabRouter
)。 -
options
- 適用在 hook 和路由器的選項。導覽器應轉送其 prop 至此,以便使用者提供 prop 來設定導覽器。預設情況下,接受下列選項:children
(必要) -children
prop 的內容應包含作為Screen
組件的路由設定。screenOptions
-screenOptions
prop 的內容應包含所有畫面的預設選項。initialRouteName
-initialRouteName
prop 會決定畫面於初始呈現時要聚焦在哪個畫面。這個 prop 會轉送到路由器。
如果在此傳遞任何其他選項,這些選項也會轉送到路由器。
此 hook 會傳回一個物件,內含下列屬性:
-
state
- 導覽器的導覽狀態。組件可以使用這個狀態,並決定要如何呈現它。 -
navigation
- 導覽物件,其中包含導覽器用來處理導覽狀態的各種輔助程式方法。這與畫面的導覽物件不同,包含部分協助程式,例如用於向畫面發射事件的emit
。 -
descriptors
- 這是包含每個路由描述符的物件,其中路由鍵為屬性。路由的描述符可用於descriptors[route.key]
。每個描述符均包含下列屬性:navigation
- 畫面的導覽 prop。您不需要手動將它傳遞到畫面。但是,如果我們在畫面外呈現組件,而且這些組件也需要接收navigation
prop (例如標頭組件),這就非常方便。options
- 編碼器,如果指定選項(例如畫面標題),將會傳回這些選項。render
- 用於呈現實際畫面的函式。呼叫descriptors[route.key].render()
會傳回 React 元素,其中包含畫面內容。一定要使用這個方法來呈現畫面,否則 child 導覽器無法適當地連接到導覽樹。
範例
import * as React from 'react';
import { Text, Pressable, View } from 'react-native';
import {
NavigationHelpersContext,
useNavigationBuilder,
TabRouter,
TabActions,
} from '@react-navigation/native';
function TabNavigator({
initialRouteName,
children,
screenOptions,
tabBarStyle,
contentStyle,
}) {
const { state, navigation, descriptors, NavigationContent } =
useNavigationBuilder(TabRouter, {
children,
screenOptions,
initialRouteName,
});
return (
<NavigationContent>
<View style={[{ flexDirection: 'row' }, tabBarStyle]}>
{state.routes.map((route, index) => (
<Pressable
key={route.key}
onPress={() => {
const isFocused = state.index === index;
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name, route.params),
target: state.key,
});
}
}}
style={{ flex: 1 }}
>
<Text>{descriptors[route.key].options.title ?? route.name}</Text>
</Pressable>
))}
</View>
<View style={[{ flex: 1 }, contentStyle]}>
{state.routes.map((route, i) => {
return (
<View
key={route.key}
style={[
StyleSheet.absoluteFill,
{ display: i === state.index ? 'flex' : 'none' },
]}
>
{descriptors[route.key].render()}
</View>
);
})}
</View>
</NavigationContent>
);
}
導覽器的 navigation
物件還有一個 emit
方法,可向子畫面發射自訂事件。用法如下:
navigation.emit({
type: 'transitionStart',
data: { blurring: false },
target: route.key,
});
data
會在 event
物件的 data
屬性中顯示,即為 event.data
。
target
屬性會決定哪個畫面將收到事件。如果省略 target
屬性,事件將會派發到導覽器中的所有畫面。
createNavigatorFactory
此 createNavigatorFactory
函式用於建立函式,將建立 Navigator
與 Screen
組合。自訂導覽器需要在匯出前於 createNavigatorFactory
中封裝導覽器組件。
範例
import {
useNavigationBuilder,
createNavigatorFactory,
} from '@react-navigation/native';
// ...
export const createMyNavigator = createNavigatorFactory(TabNavigator);
之後可以這樣使用:
import { createMyNavigator } from './myNavigator';
const My = createMyNavigator();
function App() {
return (
<My.Navigator>
<My.Screen name="Home" component={HomeScreen} />
<My.Screen name="Feed" component={FeedScreen} />
</My.Navigator>
);
}
類型檢查導覽列
若要類型檢查導覽列,我們需要提供 3 種類型
- 檢視接受的 props 類型
- 受支持的螢幕選項類型
- 導覽列發出的事件類型對應表
例如,若要類型檢查自訂標籤頁導覽列,我們可以進行類似的操作
import * as React from 'react';
import {
View,
Text,
Pressable,
StyleProp,
ViewStyle,
StyleSheet,
} from 'react-native';
import {
createNavigatorFactory,
DefaultNavigatorOptions,
ParamListBase,
CommonActions,
TabActionHelpers,
TabNavigationState,
TabRouter,
TabRouterOptions,
useNavigationBuilder,
} from '@react-navigation/native';
// Props accepted by the view
type TabNavigationConfig = {
tabBarStyle: StyleProp<ViewStyle>;
contentStyle: StyleProp<ViewStyle>;
};
// Supported screen options
type TabNavigationOptions = {
title?: string;
};
// Map of event name and the type of data (in event.data)
//
// canPreventDefault: true adds the defaultPrevented property to the
// emitted events.
type TabNavigationEventMap = {
tabPress: {
data: { isAlreadyFocused: boolean };
canPreventDefault: true;
};
};
// The props accepted by the component is a combination of 3 things
type Props = DefaultNavigatorOptions<
ParamListBase,
TabNavigationState<ParamListBase>,
TabNavigationOptions,
TabNavigationEventMap
> &
TabRouterOptions &
TabNavigationConfig;
function TabNavigator({
initialRouteName,
children,
screenOptions,
tabBarStyle,
contentStyle,
}: Props) {
const { state, navigation, descriptors, NavigationContent } =
useNavigationBuilder<
TabNavigationState<ParamListBase>,
TabRouterOptions,
TabActionHelpers<ParamListBase>,
TabNavigationOptions,
TabNavigationEventMap
>(TabRouter, {
children,
screenOptions,
initialRouteName,
});
return (
<NavigationContent>
<View style={[{ flexDirection: 'row' }, tabBarStyle]}>
{state.routes.map((route, index) => (
<Pressable
key={route.key}
onPress={() => {
const isFocused = state.index === index;
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
data: {
isAlreadyFocused: isFocused,
},
});
if (!isFocused && !event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route),
target: state.key,
});
}
}}
style={{ flex: 1 }}
>
<Text>{descriptors[route.key].options.title ?? route.name}</Text>
</Pressable>
))}
</View>
<View style={[{ flex: 1 }, contentStyle]}>
{state.routes.map((route, i) => {
return (
<View
key={route.key}
style={[
StyleSheet.absoluteFill,
{ display: i === state.index ? 'flex' : 'none' },
]}
>
{descriptors[route.key].render()}
</View>
);
})}
</View>
</NavigationContent>
);
}
export default createNavigatorFactory<
TabNavigationState<ParamListBase>,
TabNavigationOptions,
TabNavigationEventMap,
typeof TabNavigator
>(TabNavigator);
擴充導覽列
所有內建導覽列都會匯出其檢視,我們可以重複使用這些檢視,並在它們之上建構額外的功能。例如,如果我們要重新建構底部標籤頁導覽列,需要下列程式碼
import * as React from 'react';
import {
useNavigationBuilder,
createNavigatorFactory,
TabRouter,
} from '@react-navigation/native';
import { BottomTabView } from '@react-navigation/bottom-tabs';
function BottomTabNavigator({
initialRouteName,
backBehavior,
children,
screenOptions,
...rest
}) {
const { state, descriptors, navigation, NavigationContent } =
useNavigationBuilder(TabRouter, {
initialRouteName,
backBehavior,
children,
screenOptions,
});
return (
<NavigationContent>
<BottomTabView
{...rest}
state={state}
navigation={navigation}
descriptors={descriptors}
/>
</NavigationContent>
);
}
export default createNavigatorFactory(BottomTabNavigator);
現在,我們可以自訂它以新增額外的功能或變更行為。例如,改用 自訂路由器,而非預設的 TabRouter
import MyRouter from './MyRouter';
// ...
const { state, descriptors, navigation, NavigationContent } =
useNavigationBuilder(MyRouter, {
initialRouteName,
backBehavior,
children,
screenOptions,
});
// ...