使用 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>
这将为 Navigator
和 Screen
组件的 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 中的导航器包导出通用类型,以定义相应导航器中 navigation
和 route
属性的类型。
¥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'>;
这允许我们输入检查你使用 navigate
、push
等导航的路由名称和参数。当前路由的名称对于输入检查 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
类型获取 navigation
和 route
的类型,如下所示:
¥You can get the types for navigation
and route
from the Props
type as follows:
type ProfileScreenNavigationProp = Props['navigation'];
type ProfileScreenRouteProp = Props['route'];
或者,你也可以分别注释 navigation
和 route
属性。
¥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
你可以通过传递嵌套屏幕的 screen
和 params
属性来实现 导航到嵌套导航器中的屏幕:
¥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>();
注释 options
和 screenOptions
¥Annotating options
and screenOptions
当你将 options
传递给 Screen
或 screenOptions
属性传递给 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.
使用 options
和 screenOptions
的函数形式时,你可以使用用于注释 navigation
和 route
属性的相同类型来注释参数。
¥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>>();
指定 useNavigation
、Link
、ref
等的默认类型
¥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.
如果你在应用中大量使用 useNavigation
、Link
等,则指定此类型非常重要,因为它将确保类型安全。它还将确保你在 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.
-
最好创建一个单独的文件(例如
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. -
与其直接在组件中使用
CompositeNavigationProp
,不如创建一个可以重用的辅助类型。¥Instead of using
CompositeNavigationProp
directly in your components, it's better to create a helper type that you can reuse. -
为根导航器指定全局类型可以避免在许多地方进行手动注释。
¥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']>();
// ...
}