使用 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.paramsprop that it accepts. TheStaticScreenPropstype makes it simpler:import type { StaticScreenProps } from '@react-navigation/native';
type Props = StaticScreenProps<{
username: string;
}>;
function ProfileScreen({ route }: Props) {
// ...
} -
为根导航器生成
ParamList类型并将其指定为RootParamList类型的默认类型:¥Generate the
ParamListtype for the root navigator and specify it as the default type for theRootParamListtype: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
useNavigationhook.
导航器特定类型
¥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.
如果你的导航器有 id 属性,你还需要将其作为通用属性传递:
¥If you have an id prop for your navigator, you will also need to pass it as a generic:
const RootStack = createStackNavigator<RootStackParamList, 'MyStack'>();
类型检查屏幕
¥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
CompositeNavigationPropdirectly 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']>();
// ...
}