使用 TypeScript 进行类型检查
React Navigation 使用 TypeScript 编写,并为 TypeScript 项目导出类型定义。
类型检查导航器
要类型检查我们的路由名称和参数,我们需要做的第一件事是创建一个对象类型,其中包含路由名称到路由参数的映射。例如,假设我们在根导航器中有一个名为 Profile
的路由,它应该有一个参数 userId
type RootStackParamList = {
Profile: { userId: string };
};
同样地,我们需要对每条路由执行相同的操作。
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Feed: { sort: 'latest' | 'top' } | undefined;
};
指定 undefined
表示路由没有参数。带有 undefined
的联合类型(例如 SomeType | undefined
)表示参数是可选的。
定义完映射后,我们需要告诉我们的导航器使用它。为此,我们可以将其作为泛型传递给 createXNavigator
函数。
import { createStackNavigator } from '@react-navigation/stack';
const RootStack = createStackNavigator<RootStackParamList>();
然后我们可以使用它。
<RootStack.Navigator initialRouteName="Home">
<RootStack.Screen name="Home" component={Home} />
<RootStack.Screen
name="Profile"
component={Profile}
initialParams={{ userId: user.id }}
/>
<RootStack.Screen name="Feed" component={Feed} />
</RootStack.Navigator>
这将为 Navigator
和 Screen
组件的 props 提供类型检查和智能感知。
包含映射的类型必须是类型别名(例如 type RootStackParamList = { ... }
)。它不能是接口(例如 interface RootStackParamList { ... }
)。它也不应该扩展 ParamListBase
(例如 interface RootStackParamList extends ParamListBase { ... }
)。这样做会导致错误的类型检查,允许您传递错误的路由名称。
类型检查屏幕
为了类型检查我们的屏幕,我们需要对屏幕接收到的 navigation
prop 和 route
prop 进行注释。React Navigation 中的导航器包导出泛型类型来定义对应导航器的 navigation
和 route
props 的类型。
例如,您可以对 Native Stack Navigator 使用 NativeStackScreenProps
。
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Feed: { sort: 'latest' | 'top' } | undefined;
};
type Props = NativeStackScreenProps<RootStackParamList, 'Profile'>;
该类型接受 3 个泛型
- 我们之前定义的参数列表对象
- 屏幕所属路由的名称
- 导航器的 ID(可选)
如果您的导航器有 id
prop,您可以执行以下操作
type Props = NativeStackScreenProps<RootStackParamList, 'Profile', 'MyStack'>;
这使我们能够类型检查使用 navigate
、push
等进行导航的路由名称和参数。当前路由的名称对于类型检查 route.params
中的参数以及调用 setParams
时是必要的。
类似地,您可以从 @react-navigation/stack
导入 StackScreenProps
,从 @react-navigation/drawer
导入 DrawerScreenProps
,从 @react-navigation/bottom-tabs
导入 BottomTabScreenProps
等等。
然后,您可以使用上面定义的 Props
类型来注释您的组件。
对于函数组件
function ProfileScreen({ route, navigation }: Props) {
// ...
}
对于类组件
class ProfileScreen extends React.Component<Props> {
render() {
// ...
}
}
您可以从 Props
类型中获取 navigation
和 route
的类型,如下所示
type ProfileScreenNavigationProp = Props['navigation'];
type ProfileScreenRouteProp = Props['route'];
或者,您也可以分别注释 navigation
和 route
属性。
要获取 navigation
属性的类型,我们需要从导航器中导入相应的类型。例如,@react-navigation/native-stack
的 NativeStackNavigationProp
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
type ProfileScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList,
'Profile'
>;
类似地,您可以从 @react-navigation/stack
中导入 StackNavigationProp
,从 @react-navigation/drawer
中导入 DrawerNavigationProp
,从 @react-navigation/bottom-tabs
中导入 BottomTabNavigationProp
等。
要获取 route
属性的类型,我们需要使用 @react-navigation/native
中的 RouteProp
类型
import type { RouteProp } from '@react-navigation/native';
type ProfileScreenRouteProp = RouteProp<RootStackParamList, 'Profile'>;
我们建议您创建一个单独的 types.tsx
文件,在其中保存类型并在您的组件文件中导入它们,而不是在每个文件中重复它们。
嵌套导航器
在嵌套导航器中类型检查屏幕和参数
您可以导航到嵌套导航器中的屏幕,方法是为嵌套屏幕传递 screen
和 params
属性
navigation.navigate('Home', {
screen: 'Feed',
params: { sort: 'latest' },
});
为了能够对它进行类型检查,我们需要从包含嵌套导航器的屏幕中提取参数。这可以使用 NavigatorScreenParams
实用程序完成
import { NavigatorScreenParams } from '@react-navigation/native';
type TabParamList = {
Home: NavigatorScreenParams<StackParamList>;
Profile: { userId: string };
};
组合导航属性
当您嵌套导航器时,屏幕的导航属性是多个导航属性的组合。例如,如果我们在堆栈中有一个选项卡,则 navigation
属性将同时具有 jumpTo
(来自选项卡导航器)和 push
(来自堆栈导航器)。为了更轻松地组合来自多个导航器的类型,您可以使用 CompositeScreenProps
类型
import type { CompositeScreenProps } from '@react-navigation/native';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import type { StackScreenProps } from '@react-navigation/stack';
type ProfileScreenProps = CompositeScreenProps<
BottomTabScreenProps<TabParamList, 'Profile'>,
StackScreenProps<StackParamList>
>;
CompositeScreenProps
类型接受 2 个参数,第一个参数是主要导航的属性类型(拥有此屏幕的导航器的类型,在本例中是包含 Profile
屏幕的选项卡导航器),第二个参数是次要导航的属性类型(父导航器的类型)。主要类型应始终以屏幕的路由名称作为其第二个参数。
对于多个父导航器,此辅助类型应嵌套。
type ProfileScreenProps = CompositeScreenProps<
BottomTabScreenProps<TabParamList, 'Profile'>,
CompositeScreenProps<
StackScreenProps<StackParamList>,
DrawerScreenProps<DrawerParamList>
>
>;
如果单独注释 navigation
属性,可以使用 CompositeNavigationProp
代替。用法类似于 CompositeScreenProps
。
import type { CompositeNavigationProp } from '@react-navigation/native';
import type { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import type { StackNavigationProp } from '@react-navigation/stack';
type ProfileScreenNavigationProp = CompositeNavigationProp<
BottomTabNavigationProp<TabParamList, 'Profile'>,
StackNavigationProp<StackParamList>
>;
注释 useNavigation
注释 useNavigation
不是类型安全的,因为类型参数无法静态验证。建议改为 指定默认类型。
要注释从 useNavigation
获取的 navigation
属性,可以使用类型参数。
const navigation = useNavigation<ProfileScreenNavigationProp>();
注释 useRoute
注释 useRoute
不是类型安全的,因为类型参数无法静态验证。建议尽可能使用 route
属性。对于不需要特定路由类型的通用代码,可以使用 useRoute
。
要注释从 useRoute
获取的 route
属性,可以使用类型参数。
const route = useRoute<ProfileScreenRouteProp>();
注释 options
和 screenOptions
当您将 options
传递给 Screen
或 screenOptions
属性传递给 Navigator
组件时,它们已经过类型检查,您无需执行任何特殊操作。但是,有时您可能希望将选项提取到单独的对象中,并且您可能希望对其进行注释。
要注释选项,我们需要从导航器中导入相应的类型。例如,StackNavigationOptions
用于 @react-navigation/stack
。
import type { StackNavigationOptions } from '@react-navigation/stack';
const options: StackNavigationOptions = {
headerShown: false,
};
类似地,您可以从 @react-navigation/drawer
导入 DrawerNavigationOptions
,从 @react-navigation/bottom-tabs
导入 BottomTabNavigationOptions
等。
当使用 options
和 screenOptions
的函数形式时,您可以使用与注释 navigation
和 route
属性相同的类型注释参数。
注释 NavigationContainer
上的 ref
如果您使用 createNavigationContainerRef()
方法创建 ref,您可以对其进行注释以类型检查导航操作。
import { createNavigationContainerRef } from '@react-navigation/native';
// ...
const navigationRef = createNavigationContainerRef<RootStackParamList>();
类似地,对于 useNavigationContainerRef()
import { useNavigationContainerRef } from '@react-navigation/native';
// ...
const navigationRef = useNavigationContainerRef<RootStackParamList>();
如果您使用的是常规的 ref
对象,您可以为 NavigationContainerRef
类型传递泛型。
使用 React.useRef
钩子的示例
import type { NavigationContainerRef } from '@react-navigation/native';
// ...
const navigationRef =
React.useRef<NavigationContainerRef<RootStackParamList>>(null);
使用 React.createRef
的示例
import type { NavigationContainerRef } from '@react-navigation/native';
// ...
const navigationRef =
React.createRef<NavigationContainerRef<RootStackParamList>>();
为 useNavigation
、Link
、ref
等指定默认类型
您可以为根导航器指定一个全局类型,而不是手动注释这些 API,该类型将用作默认类型。
为此,您可以在代码库中的某个位置添加此代码段
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
RootParamList
接口让 React Navigation 了解您的根导航器接受的参数。在这里,我们扩展了类型 RootStackParamList
,因为这是我们根部堆栈导航器的参数类型。此类型的名称并不重要。
如果您在应用程序中大量使用 useNavigation
、Link
等,指定此类型很重要,因为它将确保类型安全。它还将确保您在 linking
属性上具有正确的嵌套。
组织类型
在为 React Navigation 编写类型时,我们建议您做几件事以保持井井有条。
- 最好创建一个单独的文件(例如
navigation/types.tsx
),其中包含与 React Navigation 相关的类型。 - 最好创建一个可以重复使用的辅助类型,而不是在组件中直接使用
CompositeNavigationProp
。 - 为根导航器指定一个全局类型可以避免在许多地方进行手动注释。
考虑到这些建议,包含类型的文件可能看起来像这样
import type {
CompositeScreenProps,
NavigatorScreenParams,
} from '@react-navigation/native';
import type { StackScreenProps } from '@react-navigation/stack';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
export type RootStackParamList = {
Home: NavigatorScreenParams<HomeTabParamList>;
PostDetails: { id: string };
NotFound: undefined;
};
export type RootStackScreenProps<T extends keyof RootStackParamList> =
StackScreenProps<RootStackParamList, T>;
export type HomeTabParamList = {
Popular: undefined;
Latest: undefined;
};
export type HomeTabScreenProps<T extends keyof HomeTabParamList> =
CompositeScreenProps<
BottomTabScreenProps<HomeTabParamList, T>,
RootStackScreenProps<keyof RootStackParamList>
>;
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
现在,在注释组件时,您可以编写
import type { HomeTabScreenProps } from './navigation/types';
function PopularScreen({ navigation, route }: HomeTabScreenProps<'Popular'>) {
// ...
}
如果您使用的是 useRoute
等钩子,您可以编写
import type { HomeTabScreenProps } from './navigation/types';
function PopularScreen() {
const route = useRoute<HomeTabScreenProps<'Popular'>['route']>();
// ...
}