Skip to main content
Version: 7.x

自定义导航器

导航器允许你定义应用的导航结构。导航器还渲染常见元素,例如你可以配置的标题和选项卡栏。

¥Navigators allow you to define your application's navigation structure. Navigators also render common elements such as headers and tab bars which you can configure.

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

¥Under the hood, navigators are plain React components.

内置导航器

¥Built-in Navigators

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

¥We include some commonly needed navigators such as:

  • createStackNavigator - 一次渲染一个屏幕并提供屏幕之间的转换。当打开一个新屏幕时,它将被放置在堆栈的顶部。

    ¥createStackNavigator - Renders one screen at a time and provides transitions between screens. When a new screen is opened it is placed on top of the stack.

  • createDrawerNavigator - 默认提供从屏幕左侧滑入的抽屉。

    ¥createDrawerNavigator - Provides a drawer that slides in from the left of the screen by default.

  • createBottomTabNavigator - 渲染一个选项卡栏,让用户可以在多个屏幕之间切换。

    ¥createBottomTabNavigator - Renders a tab bar that lets the user switch between several screens.

  • createMaterialTopTabNavigator - 渲染选项卡视图,让用户可以使用滑动手势或选项卡栏在多个屏幕之间切换。

    ¥createMaterialTopTabNavigator - Renders tab view which lets the user switch between several screens using swipe gesture or the tab bar.

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

¥API for building custom navigators

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

¥A navigator bundles a router and a view which takes the navigation state and decides how to render it. We export a useNavigationBuilder hook to build custom navigators that integrate with rest of React Navigation.

useNavigationBuilder

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

¥This hook allows a component to hook into React Navigation. It accepts the following arguments:

  • createRouter - 返回路由对象(例如 StackRouterTabRouter)的工厂方法。

    ¥createRouter - A factory method which returns a router object (e.g. StackRouter, TabRouter).

  • options - 钩子和刳刨机的选项。导航器应在此处转发其属性,以便用户可以提供属性来配置导航器。默认情况下,接受以下选项:

    ¥options - Options for the hook and the router. The navigator should forward its props here so that user can provide props to configure the navigator. By default, the following options are accepted:

    • children(必需的) - children 属性应包含作为 Screen 组件的路由配置。

      ¥children (required) - The children prop should contain route configurations as Screen components.

    • screenOptions - screenOptions 属性应包含所有屏幕的默认选项。

      ¥screenOptions - The screenOptions prop should contain default options for all of the screens.

    • initialRouteName - initialRouteName 属性决定屏幕专注于初始渲染。该属性被转发到路由。

      ¥initialRouteName - The initialRouteName prop determines the screen to focus on initial render. This prop is forwarded to the router.

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

    ¥If any other options are passed here, they'll be forwarded to the router.

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

¥The hook returns an object with following properties:

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

    ¥state - The navigation state for the navigator. The component can take this state and decide how to render it.

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

    ¥navigation - The navigation object containing various helper methods for the navigator to manipulate the navigation state. This isn't the same as the navigation object for the screen and includes some helpers such as emit to emit events to the screens.

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

    ¥descriptors - This is an object containing descriptors for each route with the route keys as its properties. The descriptor for a route can be accessed by descriptors[route.key]. Each descriptor contains the following properties:

    • navigation - 屏幕的导航对象。你不需要手动将其传递到屏幕。但如果我们在屏幕外部渲染也需要接收 navigation 属性的组件(例如标头组件),那么它会很有用。

      ¥navigation - The navigation object for the screen. You don't need to pass this to the screen manually. But it's useful if we're rendering components outside the screen that need to receive navigation prop as well, such as a header component.

    • options - 一个 getter,返回选项,例如屏幕的 title(如果指定)。

      ¥options - A getter which returns the options such as title for the screen if they are specified.

    • render - 可用于渲染实际屏幕的函数。调用 descriptors[route.key].render() 将返回一个包含屏幕内容的 React 元素。使用此方法渲染屏幕非常重要,否则任何子导航器都将无法正确连接到导航树。

      ¥render - A function which can be used to render the actual screen. Calling descriptors[route.key].render() will return a React element containing the screen content. It's important to use this method to render a screen, otherwise any child navigators won't be connected to the navigation tree properly.

示例:

¥Example:

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) => (
<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 方法,用于向子屏幕发出自定义事件。用法如下:

¥The navigation object for navigators also has an emit method to emit custom events to the child screens. The usage looks like this:

navigation.emit({
type: 'transitionStart',
data: { blurring: false },
target: route.key,
});

dataevent 对象的 data 属性下可用,即 event.data

¥The data is available under the data property in the event object, i.e. event.data.

target 属性确定将接收事件的屏幕。如果省略 target 属性,则该事件将分派到导航器中的所有屏幕。

¥The target property determines the screen that will receive the event. If the target property is omitted, the event is dispatched to all screens in the navigator.

createNavigatorFactory

createNavigatorFactory 函数用于创建一个将 NavigatorScreen 配对的函数。自定义导航器需要在导出前将导航器组件封装在 createNavigatorFactory 中。

¥This createNavigatorFactory function is used to create a function that will Navigator and Screen pair. Custom navigators need to wrap the navigator component in createNavigatorFactory before exporting.

示例:

¥Example:

import {
useNavigationBuilder,
createNavigatorFactory,
} from '@react-navigation/native';

// ...

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

然后可以像这样使用:

¥Then it can be used like this:

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

类型检查导航器

¥Type-checking navigators

为了对导航器进行类型检查,我们需要提供 3 种类型:

¥To type-check navigators, we need to provide 3 types:

  • 视图接受的 props 类型

    ¥Type of the props accepted by the view

  • 支持的屏幕选项类型

    ¥Type of supported screen options

  • 导航器发出的事件类型的地图

    ¥A map of event types emitted by the navigator

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

¥For example, to type-check our custom tab navigator, we can do something like this:

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

扩展导航器

¥Extending Navigators

所有内置导航器都会导出它们的视图,我们可以重用这些视图并在它们之上构建附加功能。例如,如果我们要重新构建底部选项卡导航器,我们需要以下代码:

¥All of the built-in navigators export their views, which we can reuse and build additional functionality on top of them. For example, if we want to re-build the bottom tab navigator, we need the following code:

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

¥Now, we can customize it to add additional functionality or change the behavior. For example, use a custom router instead of the default TabRouter:

import MyRouter from './MyRouter';

// ...

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

// ...