Skip to main content
Version: 7.x

自定义导航器

本质上,导航器是一个 React 组件,它接收一组屏幕和选项,并根据其 导航状态 渲染它们,通常还会添加一些额外的 UI,例如标题栏、标签栏或抽屉。

¥In essence, a navigator is a React component that takes a set of screens and options, and renders them based on its navigation state, generally with additional UI such as headers, tab bars, or drawers.

React Navigation 提供了一些内置导航器,但如果你需要高度自定义的行为或 UI,它们可能并不总是能满足你的需求。在这种情况下,你可以使用 React Navigation 的 API 构建自定义导航器。

¥React Navigation provides a few built-in navigators, but they might not always fit your needs if you want a very custom behavior or UI. In such cases, you can build your own custom navigators using React Navigation's APIs.

自定义导航器的行为与内置导航器完全相同,使用方法也相同。这意味着你可以以相同的方式定义屏幕,在屏幕中使用 routenavigation 对象,并使用熟悉的 API 在屏幕之间导航。导航器还能够处理深度链接、状态持久化以及其他内置导航器支持的功能。

¥A custom navigator behaves just like a built-in navigator, and can be used in the same way. This means you can define screens the same way, use route and navigation objects in your screens, and navigate between screens with familiar API. The navigator will also be able to handle deep linking, state persistence, and other features that built-in navigators support.

概述

¥Overview

在底层,导航器是使用 useNavigationBuilder hook 的普通 React 组件。

¥Under the hood, navigators are plain React components that use the useNavigationBuilder hook.

导航器组件随后使用此状态,根据用例,使用任何其他 UI 来适当地布局屏幕。然后,将此组件封装在 createNavigatorFactory 中,以创建导航器的 API。

¥The navigator component then uses this state to layout the screens appropriately with any additional UI based on the use case. This component is then wrapped in createNavigatorFactory to create the API for the navigator.

一个非常简单的示例如下:

¥A very basic example looks like this:

function MyStackNavigator(props) {
const { state, descriptors, NavigationContent } = useNavigationBuilder(
StackRouter,
props
);

const focusedRoute = state.routes[state.index];
const descriptor = descriptors[focusedRoute.key];

return <NavigationContent>{descriptor.render()}</NavigationContent>;
}

export const createMyStackNavigator = createNavigatorFactory(MyStackNavigator);

现在,我们已经有了一个可以正常工作的导航器,尽管它目前还没有任何特殊功能。

¥Now, we have an already working navigator, even though it doesn't do anything special yet.

让我们来分解一下:

¥Let's break this down:

  • 我们定义一个包含导航器逻辑的 MyNavigator 组件。这是当你在应用中使用 createMyStackNavigator 工厂函数渲染 <Stack.Navigator> 时渲染的组件。

    ¥We define a MyNavigator component that contains our navigator logic. This is the component that's rendered when you render <Stack.Navigator> in your app with the createMyStackNavigator factory function.

  • 我们使用 useNavigationBuilder hook 并传递 StackRouter,这将使我们的导航器表现得像一个堆栈导航器。此处也可以使用任何其他路由,例如 TabRouterDrawerRouter 或自定义路由。

    ¥We use the useNavigationBuilder hook and pass it StackRouter, which would make our navigator behave like a stack navigator. Any other router such as TabRouter, DrawerRouter, or a custom router can be used here as well.

  • 该钩子在 state 属性中返回 导航状态。这是导航器的当前状态。还有一个 descriptors 对象,其中包含导航器中每个屏幕的数据和辅助函数。

    ¥The hook returns the navigation state in the state property. This is the current state of the navigator. There's also a descriptors object which contains the data and helpers for each screen in the navigator.

  • 我们从状态中获取焦点路由,其值为 state.routes[state.index]。 - 因为 state.indexstate.routes 数组中当前聚焦路由的索引。

    ¥We get the focused route from the state with state.routes[state.index] - as state.index is the index of the currently focused route in the state.routes array.

  • 然后我们获取焦点路由对应的描述符(descriptors[focusedRoute.key]),并调用 render() 方法来获取屏幕的 React 元素。

    ¥Then we get the corresponding descriptor for the focused route with descriptors[focusedRoute.key] and call the render() method on it to get the React element for the screen.

  • 导航器的内容被封装在 NavigationContent 中,以提供适当的上下文和封装器。

    ¥The content of the navigator is wrapped in NavigationContent to provide appropriate context and wrappers.

这样,我们就拥有了一个基本的堆栈导航器,它只渲染当前聚焦的屏幕。与内置的堆栈导航器不同,此方法不会保持未聚焦屏幕的渲染状态。但如果你希望保持屏幕挂载,则可以遍历 state.routes 并渲染所有屏幕。你还可以读取 descriptor.options 对象以获取 options 对象,从而处理屏幕的标题、头部和其他选项。

¥With this, we have a basic stack navigator that renders only the focused screen. Unlike the built-in stack navigator, this doesn't keep unfocused screens rendered. But you can loop through state.routes and render all of the screens if you want to keep them mounted. You can also read descriptor.options to get the options to handle the screen's title, header, and other options.

除了屏幕内容之外,它没有任何其他 UI。不支持手势或动画。因此,你可以根据需要自由添加任何其他 UI、手势、动画等。你还可以根据需要以任何方式布局屏幕,例如并排或网格布局,而不是像内置的堆栈导航器那样将它们堆叠在一起。

¥This also doesn't have any additional UI apart from the screen content. There are no gestures or animations. So you're free to add any additional UI, gestures, animations etc. as needed. You can also layout the screens in any way you want, such as rendering them side-by-side or in a grid, instead of stacking them on top of each other like the built-in stack navigator does.

你可以在本文档后面看到更完整的自定义导航器示例。

¥You can see a more complete example of a custom navigator later in this document.

API 定义

¥API Definition

useNavigationBuilder

此 Hook 包含导航器的核心逻辑,负责存储和管理 导航状态。它接受一个 router 作为参数,以便了解如何处理各种导航操作。然后,它返回导航器组件要使用的状态和辅助方法。

¥This hook contains the core logic of a navigator, and is responsible for storing and managing the navigation state. It takes a router as an argument to know how to handle various navigation actions. It then returns the state and helper methods for the navigator component to use.

它接受以下参数:

¥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({ tabBarStyle, contentStyle, ...rest }) {
const { state, navigation, descriptors, NavigationContent } =
useNavigationBuilder(TabRouter, rest);

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

¥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);
}
注意

我们也可以直接使用 export const createMyNavigator = createNavigatorFactory(MyNavigator),而无需将其封装在另一个函数中。但是,为了确保导航器拥有正确的 TypeScript 支持,封装函数是必要的。

¥We can also do export const createMyNavigator = createNavigatorFactory(MyNavigator) directly instead of wrapping in another function. However, the wrapper function is necessary to have proper TypeScript support for the navigator.

然后可以像这样使用:

¥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';

// Additional 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 type of the navigation object for each screen
type TabNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = keyof ParamList,
NavigatorID extends string | undefined = undefined,
> = NavigationProp<
ParamList,
RouteName,
NavigatorID,
TabNavigationState<ParamList>,
TabNavigationOptions,
TabNavigationEventMap
> &
TabActionHelpers<ParamList>;

// The props accepted by the component is a combination of 3 things
type Props = DefaultNavigatorOptions<
ParamListBase,
string | undefined,
TabNavigationState<ParamListBase>,
TabNavigationOptions,
TabNavigationEventMap,
TabNavigationProp<ParamListBase>
> &
TabRouterOptions &
TabNavigationConfig;

function TabNavigator({ tabBarStyle, contentStyle, ...rest }: Props) {
const { state, navigation, descriptors, NavigationContent } =
useNavigationBuilder<
TabNavigationState<ParamListBase>,
TabRouterOptions,
TabActionHelpers<ParamListBase>,
TabNavigationOptions,
TabNavigationEventMap
>(TabRouter, rest);

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

// The factory function with generic types for type-inference
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,
});

// ...
注意

像这样自定义内置导航器属于高级用法,通常没有必要。考虑以下替代方案:

¥Customizing built-in navigators like this is an advanced use case and generally not necessary. Consider alternatives such as:

  • 导航器的 layout 属性用于为其添加一个封装器。

    ¥layout prop on navigators to add a wrapper around the navigator

  • 导航器上的 UNSTABLE_router 属性用于自定义路由行为

    ¥UNSTABLE_router prop on navigators to customize the router behavior

另请参阅导航器的文档,查看是否有任何现有 API 满足你的需求。

¥Also refer to the navigator's documentation to see if any existing API meets your needs.