Skip to main content
Version: 6.x

使用 TypeScript 进行类型检查

React Navigation 使用 TypeScript 编写,并导出 TypeScript 项目的类型定义。

¥React Navigation is written with TypeScript and exports type definitions for TypeScript projects.

类型检查导航器

¥Type checking the navigator

要对路由名称和参数进行类型检查,我们需要做的第一件事是创建一个对象类型,其中包含路由名称到路由参数的映射。例如,假设我们的根导航器中有一条名为 Profile 的路由,它应该有一个参数 userId

¥To type check our route name and params, the first thing we need to do is to create an object type with mappings for route name to the params of the route. For example, say we have a route called Profile in our root navigator which should have a param userId:

type RootStackParamList = {
Profile: { userId: string };
};

同样,我们需要对每条路由执行相同的操作:

¥Similarly, we need to do the same for each route:

type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Feed: { sort: 'latest' | 'top' } | undefined;
};

指定 undefined 意味着该路由没有参数。带有 undefined 的联合类型(例如 SomeType | undefined)意味着参数是可选的。

¥Specifying undefined means that the route doesn't have params. A union type with undefined (e.g. SomeType | undefined) means that params are optional.

定义映射后,我们需要告诉导航器使用它。为此,我们可以将其作为泛型传递给 createXNavigator 函数:

¥After we have defined the mappings, we need to tell our navigator to use it. To do that, we can pass it as a generic to the createXNavigator functions:

import { createStackNavigator } from '@react-navigation/stack';

const RootStack = createStackNavigator<RootStackParamList>();

然后我们就可以使用它:

¥And then we can use it:

<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>

这将为 NavigatorScreen 组件的 props 提供类型检查和智能感知。

¥This will provide type checking and intelliSense for props of the Navigator and Screen components.

注意

包含映射的类型必须是类型别名(例如 type RootStackParamList = { ... })。它不能是接口(例如 interface RootStackParamList { ... })。它也不应该扩展 ParamListBase(例如 interface RootStackParamList extends ParamListBase { ... })。这样做会导致错误的类型检查,从而允许你传递错误的路由名称。

¥The type containing the mappings must be a type alias (e.g. type RootStackParamList = { ... }). It cannot be an interface (e.g. interface RootStackParamList { ... }). It also shouldn't extend ParamListBase (e.g. interface RootStackParamList extends ParamListBase { ... }). Doing so will result in incorrect type checking where it allows you to pass incorrect route names.

类型检查屏幕

¥Type checking screens

为了对我们的屏幕进行类型检查,我们需要注释屏幕接收到的 navigation 属性和 route 属性。React Navigation 中的导航器包导出通用类型,以定义相应导航器中 navigationroute 属性的类型。

¥To type check our screens, we need to annotate the navigation prop and the route prop received by a screen. The navigator packages in React Navigation export a generic types to define types for both the navigation and route props from the corresponding navigator.

例如,你可以使用 NativeStackScreenProps 作为 Native Stack Navigator。

¥For example, you can use NativeStackScreenProps for the Native Stack Navigator.

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 个泛型:

¥The type takes 3 generics:

  • 我们之前定义的参数列表对象

    ¥The param list object we defined earlier

  • 屏幕所属路由名称

    ¥The name of the route the screen belongs to

  • 导航器的 ID(可选)

    ¥The ID of the navigator (optional)

如果你的导航器有 id 属性,你可以执行以下操作:

¥If you have an id prop for your navigator, you can do:

type Props = NativeStackScreenProps<RootStackParamList, 'Profile', 'MyStack'>;

这允许我们输入检查你使用 navigatepush 等导航的路由名称和参数。当前路由的名称对于输入检查 route.params 中的参数以及调用 setParams 时是必需的。

¥This allows us to type check route names and params which you're navigating using navigate, push etc. The name of the current route is necessary to type check the params in route.params and when you call setParams.

同样,你可以为 @react-navigation/stack 导入 StackScreenProps,从 @react-navigation/drawer 导入 DrawerScreenProps,从 @react-navigation/bottom-tabs 导入 BottomTabScreenProps,依此类推。

¥Similarly, you can import StackScreenProps for @react-navigation/stack, DrawerScreenProps from @react-navigation/drawer, BottomTabScreenProps from @react-navigation/bottom-tabs and so on.

然后你可以使用上面定义的 Props 类型来注释你的组件。

¥Then you can use the Props type you defined above to annotate your component.

对于功能组件:

¥For function components:

function ProfileScreen({ route, navigation }: Props) {
// ...
}

对于类组件:

¥For class components:

class ProfileScreen extends React.Component<Props> {
render() {
// ...
}
}

你可以从 Props 类型获取 navigationroute 的类型,如下所示:

¥You can get the types for navigation and route from the Props type as follows:

type ProfileScreenNavigationProp = Props['navigation'];

type ProfileScreenRouteProp = Props['route'];

或者,你也可以分别注释 navigationroute 属性。

¥Alternatively, you can also annotate the navigation and route props separately.

要获取 navigation 属性的类型,我们需要从导航器导入相应的类型。例如,NativeStackNavigationProp 对应 @react-navigation/native-stack

¥To get the type for the navigation prop, we need to import the corresponding type from the navigator. For example, NativeStackNavigationProp for @react-navigation/native-stack:

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 等。

¥Similarly, you can import StackNavigationProp from @react-navigation/stack, DrawerNavigationProp from @react-navigation/drawer, BottomTabNavigationProp from @react-navigation/bottom-tabs etc.

要获取 route 属性的类型,我们需要使用 @react-navigation/native 中的 RouteProp 类型:

¥To get the type for the route prop, we need to use the RouteProp type from @react-navigation/native:

import type { RouteProp } from '@react-navigation/native';

type ProfileScreenRouteProp = RouteProp<RootStackParamList, 'Profile'>;

我们建议创建一个单独的 types.tsx 文件,在其中保留类型并将其导入组件文件中,而不是在每个文件中重复它们。

¥We recommend creating a separate types.tsx file where you keep the types and import them in your component files instead of repeating them in each file.

嵌套导航器

¥Nesting navigators

嵌套导航器中的类型检查屏幕和参数

¥Type checking screens and params in nested navigator

你可以通过传递嵌套屏幕的 screenparams 属性来实现 导航到嵌套导航器中的屏幕

¥You can navigate to a screen in a nested navigator by passing screen and params properties for the nested screen:

navigation.navigate('Home', {
screen: 'Feed',
params: { sort: 'latest' },
});

为了能够进行类型检查,我们需要从包含嵌套导航器的屏幕中提取参数。这可以使用 NavigatorScreenParams 实用程序来完成:

¥To be able to type check this, we need to extract the params from the screen containing the nested navigator. This can be done using the NavigatorScreenParams utility:

import { NavigatorScreenParams } from '@react-navigation/native';

type TabParamList = {
Home: NavigatorScreenParams<StackParamList>;
Profile: { userId: string };
};

组合导航属性

¥Combining navigation props

当你嵌套导航器时,屏幕的导航属性是多个导航属性的组合。例如,如果我们在堆栈中有一个选项卡,则 navigation 属性将同时具有 jumpTo(来自选项卡导航器)和 push(来自堆栈导航器)。为了更轻松地组合多个导航器的类型,你可以使用 CompositeScreenProps 类型:

¥When you nest navigators, the navigation prop of the screen is a combination of multiple navigation props. For example, if we have a tab inside a stack, the navigation prop will have both jumpTo (from the tab navigator) and push (from the stack navigator). To make it easier to combine types from multiple navigators, you can use the CompositeScreenProps type:

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 个参数,第一个参数是主导航的 props 类型(拥有此屏幕的导航器的类型,在我们的例子中是包含 Profile 屏幕的选项卡导航器),第二个参数是辅助导航的 props 类型导航(父导航器的类型)。主要类型应始终将屏幕的路由名称作为其第二个参数。

¥The CompositeScreenProps type takes 2 parameters, first parameter is the type of props for the primary navigation (type for the navigator that owns this screen, in our case the tab navigator which contains the Profile screen) and second parameter is the type of props for secondary navigation (type for a parent navigator). The primary type should always have the screen's route name as its second parameter.

对于多个父导航器,应嵌套此辅助类型:

¥For multiple parent navigators, this secondary type should be nested:

type ProfileScreenProps = CompositeScreenProps<
BottomTabScreenProps<TabParamList, 'Profile'>,
CompositeScreenProps<
StackScreenProps<StackParamList>,
DrawerScreenProps<DrawerParamList>
>
>;

如果单独注释 navigation 属性,则可以使用 CompositeNavigationProp 代替。用法与 CompositeScreenProps 类似:

¥If annotating the navigation prop separately, you can use CompositeNavigationProp instead. The usage is similar to 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

¥Annotating useNavigation

危险

注解 useNavigation 不是类型安全的,因为无法静态验证类型参数。更喜欢 指定默认类型

¥Annotating useNavigation isn't type-safe because the type parameter cannot be statically verified. Prefer specifying a default type instead.

为了注释从 useNavigation 获得的 navigation 属性,我们可以使用类型参数:

¥To annotate the navigation prop that we get from useNavigation, we can use a type parameter:

const navigation = useNavigation<ProfileScreenNavigationProp>();

注释 useRoute

¥Annotating useRoute

危险

注解 useRoute 不是类型安全的,因为无法静态验证类型参数。如果可能,最好使用 route 属性。对于不需要特定路由类型的通用代码,请使用 useRoute

¥Annotating useRoute isn't type-safe because the type parameter cannot be statically verified. Prefer using the route prop instead when possible. Use useRoute for generic code that doesn't need specific route type.

为了注释从 useRoute 获得的 route 属性,我们可以使用类型参数:

¥To annotate the route prop that we get from useRoute, we can use a type parameter:

const route = useRoute<ProfileScreenRouteProp>();

注释 optionsscreenOptions

¥Annotating options and screenOptions

当你将 options 传递给 ScreenscreenOptions 属性传递给 Navigator 组件时,它们已经经过类型检查,你不需要执行任何特殊操作。但是,有时你可能希望将选项提取到单独的对象中,并且可能需要对其进行注释。

¥When you pass the options to a Screen or screenOptions prop to a Navigator component, they are already type-checked and you don't need to do anything special. However, sometimes you might want to extract the options to a separate object, and you might want to annotate it.

为了注释选项,我们需要从导航器中导入相应的类型。例如,StackNavigationOptions 对应 @react-navigation/stack

¥To annotate the options, we need to import the corresponding type from the navigator. For example, StackNavigationOptions for @react-navigation/stack:

import type { StackNavigationOptions } from '@react-navigation/stack';

const options: StackNavigationOptions = {
headerShown: false,
};

同样,你可以从 @react-navigation/drawer 导入 DrawerNavigationOptions,从 @react-navigation/bottom-tabs 导入 BottomTabNavigationOptions,等等。

¥Similarly, you can import DrawerNavigationOptions from @react-navigation/drawer, BottomTabNavigationOptions from @react-navigation/bottom-tabs etc.

使用 optionsscreenOptions 的函数形式时,你可以使用用于注释 navigationroute 属性的相同类型来注释参数。

¥When using the function form of options and screenOptions, you can annotate the arguments with the same type you used to annotate the navigation and route props.

NavigationContainer 上注释 ref

¥Annotating ref on NavigationContainer

如果你使用 createNavigationContainerRef() 方法创建引用,你可以对其进行注释以对导航操作进行类型检查:

¥If you use the createNavigationContainerRef() method to create the ref, you can annotate it to type-check navigation actions:

import { createNavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef = createNavigationContainerRef<RootStackParamList>();

同样,对于 useNavigationContainerRef()

¥Similarly, for useNavigationContainerRef():

import { useNavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef = useNavigationContainerRef<RootStackParamList>();

如果你使用常规 ref 对象,则可以将泛型传递给 NavigationContainerRef 类型。

¥If you're using a regular ref object, you can pass a generic to the NavigationContainerRef type..

使用 React.useRef 钩子时的示例:

¥Example when using React.useRef hook:

import type { NavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef =
React.useRef<NavigationContainerRef<RootStackParamList>>(null);

使用 React.createRef 时的示例:

¥Example when using React.createRef:

import type { NavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef =
React.createRef<NavigationContainerRef<RootStackParamList>>();

¥Specifying default types for useNavigation, Link, ref etc

你可以为根导航器指定将用作默认类型的全局类型,而不是手动注释这些 API。

¥Instead of manually annotating these APIs, you can specify a global type for your root navigator which will be used as the default type.

为此,你可以将此代码段添加到代码库中的某个位置:

¥To do this, you can add this snippet somewhere in your codebase:

declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}

RootParamList 接口让 React Navigation 了解根导航器接受的参数。这里我们扩展了 RootStackParamList 类型,因为这是根堆栈导航器的参数类型。这种类型的名称并不重要。

¥The RootParamList interface lets React Navigation know about the params accepted by your root navigator. Here we extend the type RootStackParamList because that's the type of params for our stack navigator at the root. The name of this type isn't important.

如果你在应用中大量使用 useNavigationLink 等,则指定此类型非常重要,因为它将确保类型安全。它还将确保你在 linking 属性上正确嵌套。

¥Specifying this type is important if you heavily use useNavigation, Link etc. in your app since it'll ensure type-safety. It will also make sure that you have correct nesting on the linking prop.

组织类型

¥Organizing types

在为 React Navigation 编写类型时,我们建议采取一些措施来保持组织有序。

¥When writing types for React Navigation, there are a couple of things we recommend to keep things organized.

  1. 最好创建一个单独的文件(例如 navigation/types.tsx),其中包含与 React Navigation 相关的类型。

    ¥It's good to create a separate files (e.g. navigation/types.tsx) which contains the types related to React Navigation.

  2. 与其直接在组件中使用 CompositeNavigationProp,不如创建一个可以重用的辅助类型。

    ¥Instead of using CompositeNavigationProp directly in your components, it's better to create a helper type that you can reuse.

  3. 为根导航器指定全局类型可以避免在许多地方进行手动注释。

    ¥Specifying a global type for your root navigator would avoid manual annotations in many places.

考虑到这些建议,包含类型的文件可能如下所示:

¥Considering these recommendations, the file containing the types may look something like this:

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 {}
}
}

现在,在注释组件时,你可以编写:

¥Now, when annotating your components, you can write:

import type { HomeTabScreenProps } from './navigation/types';

function PopularScreen({ navigation, route }: HomeTabScreenProps<'Popular'>) {
// ...
}

如果你使用诸如 useRoute 之类的钩子,你可以编写:

¥If you're using hooks such as useRoute, you can write:

import type { HomeTabScreenProps } from './navigation/types';

function PopularScreen() {
const route = useRoute<HomeTabScreenProps<'Popular'>['route']>();

// ...
}