跳到主要内容
版本:7.x

自定义导航器

导航器允许你定义应用程序的导航结构。导航器还会渲染通用元素,例如你可以配置的标题和标签栏。

在底层,导航器是普通的 React 组件。

内置导航器

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

用于构建自定义导航器的 API

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

useNavigationBuilder

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

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

  • options - hook 和路由器的选项。导航器应在此处转发其 props,以便用户可以提供 props 来配置导航器。默认情况下,接受以下选项

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

    如果在此处传递任何其他选项,它们将被转发到路由器。

hook 返回一个具有以下属性的对象

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

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

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

    • navigation - 屏幕的导航对象。你无需手动将其传递给屏幕。但是,如果我们正在屏幕外部渲染需要接收 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({
id,
initialRouteName,
children,
layout,
screenListeners,
screenOptions,
screenLayout,
tabBarStyle,
contentStyle,
}) {
const { state, navigation, descriptors, NavigationContent } =
useNavigationBuilder(TabRouter, {
id,
initialRouteName,
children,
layout,
screenListeners,
screenOptions,
screenLayout,
});

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 function createMyNavigator(config) {
return createNavigatorFactory(TabNavigator)(config);
}

然后可以像这样使用它

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,
type StyleProp,
type ViewStyle,
StyleSheet,
} from 'react-native';
import {
createNavigatorFactory,
CommonActions,
type DefaultNavigatorOptions,
type NavigatorTypeBagBase,
type ParamListBase,
type StaticConfig,
type TabActionHelpers,
type TabNavigationState,
TabRouter,
type TabRouterOptions,
type TypedNavigator,
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({
id,
initialRouteName,
children,
layout,
screenListeners,
screenOptions,
screenLayout,
backBehavior,
tabBarStyle,
contentStyle,
}: Props) {
const { state, navigation, descriptors, NavigationContent } =
useNavigationBuilder<
TabNavigationState<ParamListBase>,
TabRouterOptions,
TabActionHelpers<ParamListBase>,
TabNavigationOptions,
TabNavigationEventMap
>(TabRouter, {
id,
initialRouteName,
children,
layout,
screenListeners,
screenOptions,
screenLayout,
backBehavior,
});

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 function createMyNavigator<
const ParamList extends ParamListBase,
const NavigatorID extends string | undefined = undefined,
const TypeBag extends NavigatorTypeBagBase = {
ParamList: ParamList;
NavigatorID: NavigatorID;
State: TabNavigationState<ParamList>;
ScreenOptions: TabNavigationOptions;
EventMap: TabNavigationEventMap;
NavigationList: {
[RouteName in keyof ParamList]: TabNavigationProp<
ParamList,
RouteName,
NavigatorID
>;
};
Navigator: typeof TabNavigator;
},
const Config extends StaticConfig<TypeBag> = StaticConfig<TypeBag>,
>(config?: Config): TypedNavigator<TypeBag, Config> {
return createNavigatorFactory(TabNavigator)(config);
}

扩展导航器

所有内置导航器都导出其视图,我们可以重用它们并在其之上构建其他功能。例如,如果我们想重建底部标签导航器,我们需要以下代码

import * as React from 'react';
import {
useNavigationBuilder,
createNavigatorFactory,
TabRouter,
} from '@react-navigation/native';
import { BottomTabView } from '@react-navigation/bottom-tabs';

function BottomTabNavigator({
id,
initialRouteName,
children,
layout,
screenListeners,
screenOptions,
screenLayout,
backBehavior,
...rest
}) {
const { state, descriptors, navigation, NavigationContent } =
useNavigationBuilder(TabRouter, {
id,
initialRouteName,
children,
layout,
screenListeners,
screenOptions,
screenLayout,
backBehavior,
});

return (
<NavigationContent>
<BottomTabView
{...rest}
state={state}
navigation={navigation}
descriptors={descriptors}
/>
</NavigationContent>
);
}

export function createMyNavigator(config) {
return createNavigatorFactory(TabNavigator)(config);
}

现在,我们可以自定义它以添加其他功能或更改行为。例如,使用自定义路由器而不是默认的 TabRouter

import MyRouter from './MyRouter';

// ...

const { state, descriptors, navigation, NavigationContent } =
useNavigationBuilder(MyRouter, {
id,
initialRouteName,
children,
layout,
screenListeners,
screenOptions,
screenLayout,
backBehavior,
});

// ...