跳至主要內容
版本:6.x

自訂導覽器

導覽器功能是讓您可以定義應用程式導覽結構。導覽器也會呈現標頭和分頁工具列等常見元素,而且您可以設定它們。

在架構中,導覽器是純粹的 React 元件。

內建導覽器

我們整合了一些常見的導覽器,例如

用於建置自訂導覽器的 API

導覽器套件了路由器和檢視,可接收導覽狀態,並根據狀態決定如何呈現導覽器。我們匯出了 useNavigationBuilder 掛勾,用於建置可與 React Navigation 其他部分整合的自訂導覽器。

useNavigationBuilder

此掛勾允許元件掛接到 React Navigation。它會接受下列參數

  • createRouter - 工廠方法,會傳回路由器物件 (例如:StackRouterTabRouter)。

  • 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 函式用於建立函式,將建立 NavigatorScreen 組合。自訂導覽器需要在匯出前於 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,
});

// ...