使用 TypeScript 进行类型检查
React Navigation 可以配置为使用 TypeScript 对屏幕及其参数以及各种其他 API 进行类型检查。这在使用 React Navigation 时提供了更好的智能感知和类型安全性。
¥React Navigation can be configured to type-check screens and their params, as well as various other APIs using TypeScript. This provides better intelliSense and type safety when working with React Navigation.
- Static
- Dynamic
使用静态 API 配置 TypeScript 有 2 个步骤:
¥There are 2 steps to configure TypeScript with the static API:
-
每个屏幕组件都需要指定它接受的
route.params
属性的类型。StaticScreenProps
类型使其更简单:¥Each screen component needs to specify the type of the
route.params
prop that it accepts. TheStaticScreenProps
type makes it simpler:import type { StaticScreenProps } from '@react-navigation/native';
type Props = StaticScreenProps<{
username: string;
}>;
function ProfileScreen({ route }: Props) {
// ...
} -
为根导航器生成
ParamList
类型并将其指定为RootParamList
类型的默认类型:¥Generate the
ParamList
type for the root navigator and specify it as the default type for theRootParamList
type:import type { StaticParamList } from '@react-navigation/native';
const HomeTabs = createBottomTabNavigator({
screens: {
Feed: FeedScreen,
Profile: ProfileScreen,
},
});
const RootStack = createNativeStackNavigator({
screens: {
Home: HomeTabs,
},
});
type RootStackParamList = StaticParamList<typeof RootStack>;
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}这需要对
useNavigation
钩子进行类型检查。¥This is needed to type-check the
useNavigation
hook.
导航器特定类型
¥Navigator specific types
通常,我们建议使用 useNavigation
prop 的默认类型以与导航器无关的方式访问导航对象。但是,如果你需要使用特定于导航器的 API,则需要手动注释 useNavigation
:
¥Generally, we recommend using the default types for the useNavigation
prop to access the navigation object in a navigator-agnostic manner. However, if you need to use navigator-specific APIs, you need to manually annotate useNavigation
:
type BottomTabParamList = StaticParamList<typeof BottomTabNavigator>;
type ProfileScreenNavigationProp = BottomTabNavigationProp<
BottomTabParamList,
'Profile'
>;
// ...
const navigation = useNavigation<ProfileScreenNavigationProp>();
请注意,以这种方式注释 useNavigation
不是类型安全的,因为我们无法保证你提供的类型与导航器的类型匹配。
¥Note that annotating useNavigation
this way is not type-safe since we can't guarantee that the type you provided matches the type of the navigator.
使用动态 API 嵌套导航器
¥Nesting navigator using dynamic API
考虑以下示例:
¥Consider the following example:
const Tab = createBottomTabNavigator();
function HomeTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}
const RootStack = createStackNavigator({
Home: HomeTabs,
});
此处,HomeTabs
组件使用动态 API 定义。这意味着当我们使用 StaticParamList<typeof RootStack>
为根导航器创建参数列表时,它不会知道嵌套导航器中定义的屏幕。为了解决这个问题,我们需要明确指定嵌套导航器的参数列表。
¥Here, the HomeTabs
component is defined using the dynamic API. This means that when we create the param list for the root navigator with StaticParamList<typeof RootStack>
, it won't know about the screens defined in the nested navigator. To fix this, we'd need to specify the param list for the nested navigator explicitly.
这可以通过使用屏幕组件接收的 route
prop 的类型来完成:
¥This can be done by using the type of the route
prop that the screen component receives:
type HomeTabsParamList = {
Feed: undefined;
Profile: undefined;
};
type HomeTabsProps = StaticScreenProps<
NavigatorScreenParams<HomeTabsParamList>
>;
function HomeTabs(_: HomeTabsProps) {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}
现在,使用 StaticParamList<typeof RootStack>
时,它将包括嵌套导航器中定义的屏幕。
¥Now, when using StaticParamList<typeof RootStack>
, it will include the screens defined in the nested navigator.
使用动态 API 时,需要指定每个屏幕的类型以及嵌套结构,因为无法从代码中推断出来。
¥When using the dynamic API, it is necessary to specify the types for each screen as well as the nesting structure as it cannot be inferred from the code.
对导航器进行类型检查
¥Typechecking the navigator
要对我们的路由名称和参数进行类型检查,我们需要做的第一件事是创建一个对象类型,将路由名称映射到路由的参数。例如,假设我们的根导航器中有一条名为 Profile
的路由,它应该有一个参数 userId
:
¥To typecheck our route name and params, the first thing we need to do is to create an object type with mappings for route names 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 mapping, 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 mapping 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 which allows you to pass incorrect route names.
类型检查屏幕
¥Type checking screens
要对我们的屏幕进行类型检查,我们需要注释屏幕收到的 navigation
和 route
属性。React Navigation 中的导航器包导出泛型类型,以从相应的导航器定义 navigation
和 route
属性的类型。
¥To typecheck our screens, we need to annotate the navigation
and the route
props received by a screen. The navigator packages in React Navigation export 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
from @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
objects 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
object, 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 file: types.tsx
- where you keep the types and import from there 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 个参数,第一个参数是主导航的属性类型(拥有此屏幕的导航器的类型,在我们的例子中是包含 Profile
屏幕的选项卡导航器),第二个参数是辅助导航的属性类型(父导航器的类型)。主要类型应始终将屏幕的路由名称作为其第二个参数。
¥The CompositeScreenProps
type takes 2 parameters, the 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 the 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
object 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
object from the screen component's props instead when possible. Use useRoute
for generic code that doesn't need specific route type.
为了注释我们从 useRoute
获得的 route
对象,我们可以使用类型参数:
¥To annotate the route
object 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
的函数形式时,你可以使用从导航器导出的类型注释参数,例如 @react-navigation/stack
的 StackOptionsArgs
、@react-navigation/drawer
的 DrawerOptionsArgs
、@react-navigation/bottom-tabs
的 BottomTabOptionsArgs
等:
¥When using the function form of options
and screenOptions
, you can annotate the arguments with a type exported from the navigator, e.g. StackOptionsArgs
for @react-navigation/stack
, DrawerOptionsArgs
for @react-navigation/drawer
, BottomTabOptionsArgs
for @react-navigation/bottom-tabs
etc.:
import type {
StackNavigationOptions,
StackOptionsArgs,
} from '@react-navigation/stack';
const options = ({ route }: StackOptionsArgs): StackNavigationOptions => {
return {
headerTitle: route.name,
};
};
在 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 file (e.g.
navigation/types.tsx
) that 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']>();
// ...
}