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

自定义导航器

导航器允许您定义应用程序的导航结构。导航器还会渲染常见的元素,例如标题和标签栏,您可以对其进行配置。

在幕后,导航器是普通的 React 组件。

内置导航器

我们包含了一些常用的导航器,例如

构建自定义导航器的 API

导航器捆绑了一个路由器和一个视图,该视图接收导航状态并决定如何渲染它。我们导出一个useNavigationBuilder钩子来构建与 React Navigation 其他部分集成的自定义导航器。

useNavigationBuilder

此钩子允许组件连接到 React Navigation。它接受以下参数

  • createRouter - 一个工厂方法,返回一个路由器对象(例如 StackRouterTabRouter)。

  • options - 钩子和路由器的选项。导航器应将它的 props 转发到这里,以便用户可以提供 props 来配置导航器。默认情况下,接受以下选项

    • children(必需) - children prop 应包含作为 Screen 组件的路由配置。
    • screenOptions - screenOptions prop 应包含所有屏幕的默认选项。
    • initialRouteName - initialRouteName prop 决定在初始渲染时要聚焦的屏幕。此 prop 被转发到路由器。

    如果这里传递了任何其他选项,它们将被转发到路由器。

此钩子返回一个具有以下属性的对象

  • state - 导航器的导航状态。组件可以获取此状态并决定如何渲染它。

  • navigation - 导航对象,包含导航器用于操作导航状态的各种辅助方法。这与屏幕的导航对象不同,并且包含一些辅助方法,例如 emit,用于向屏幕发出事件。

  • descriptors - 这是一个包含每个路由描述符的对象,其路由键作为其属性。可以通过 descriptors[route.key] 访问路由的描述符。每个描述符包含以下属性

    • navigation - 屏幕的导航 prop。您不需要手动将其传递给屏幕。但如果我们要渲染屏幕之外需要接收 navigation prop 的组件(例如标题组件),它很有用。
    • options - 一个 getter,如果指定了屏幕的选项(例如 title),则返回这些选项。
    • render - 一个用于渲染实际屏幕的函数。调用 descriptors[route.key].render() 将返回一个包含屏幕内容的 React 元素。使用此方法渲染屏幕非常重要,否则任何子导航器将无法正确连接到导航树。

示例

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,
});

dataevent 对象的 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 种类型

  • 视图接受的道具类型
  • 支持的屏幕选项类型
  • 导航器发出的事件类型的映射

例如,要类型检查我们的自定义选项卡导航器,我们可以执行以下操作

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,
});

// ...