自定义导航器
导航器允许您定义应用程序的导航结构。导航器还会渲染常见的元素,例如标题和标签栏,您可以对其进行配置。
在幕后,导航器是普通的 React 组件。
内置导航器
我们包含了一些常用的导航器,例如
createStackNavigator
- 每次渲染一个屏幕,并在屏幕之间提供过渡。当打开新屏幕时,它将被放置在堆栈顶部。createDrawerNavigator
- 提供一个抽屉,默认情况下从屏幕左侧滑入。createBottomTabNavigator
- 渲染一个标签栏,允许用户在多个屏幕之间切换。createMaterialTopTabNavigator
- 渲染标签视图,允许用户使用滑动手势或标签栏在多个屏幕之间切换。createMaterialBottomTabNavigator
- 渲染标签视图,允许用户使用滑动手势或标签栏在多个屏幕之间切换。
构建自定义导航器的 API
导航器捆绑了一个路由器和一个视图,该视图接收导航状态并决定如何渲染它。我们导出一个useNavigationBuilder
钩子来构建与 React Navigation 其他部分集成的自定义导航器。
useNavigationBuilder
此钩子允许组件连接到 React Navigation。它接受以下参数
-
createRouter
- 一个工厂方法,返回一个路由器对象(例如StackRouter
、TabRouter
)。 -
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,
});
data
在 event
对象的 data
属性下可用,即 event.data
。
target
属性决定哪个屏幕将接收事件。如果省略 target
属性,则事件将分发到导航器中的所有屏幕。
createNavigatorFactory
此 createNavigatorFactory
函数用于创建一个将 Navigator
和 Screen
配对的函数。自定义导航器需要在导出之前将导航器组件包装在 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,
});
// ...