使用 TypeScript 进行类型检查
React Navigation 可以配置为使用 TypeScript 对屏幕及其参数以及各种其他 API 进行类型检查。这在使用 React Navigation 时提供更好的 intelliSense 和类型安全性。
React Navigation 旨在与 TypeScript 中的 strict
模式一起使用。如果您不使用 strict
模式,某些功能可能无法按预期工作。
- 静态
- 动态
使用静态 API 配置 TypeScript 需要 2 个步骤
-
每个屏幕组件都需要指定它接受的
route.params
prop 的类型。StaticScreenProps
类型使其更简单import type { StaticScreenProps } from '@react-navigation/native';
type Props = StaticScreenProps<{
username: string;
}>;
function ProfileScreen({ route }: Props) {
// ...
} -
为根导航器生成
ParamList
类型,并将其指定为RootParamList
类型的默认类型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
hook 所必需的。
导航器特定类型
通常,我们建议使用 useNavigation
prop 的默认类型,以便以与导航器无关的方式访问导航对象。但是,如果您需要使用导航器特定的 API,则需要手动注释 useNavigation
type BottomTabParamList = StaticParamList<typeof BottomTabNavigator>;
type ProfileScreenNavigationProp = BottomTabNavigationProp<
BottomTabParamList,
'Profile'
>;
// ...
const navigation = useNavigation<ProfileScreenNavigationProp>();
请注意,以这种方式注释 useNavigation
不是类型安全的,因为我们无法保证您提供的类型与导航器的类型匹配。
使用动态 API 嵌套导航器
考虑以下示例
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>
为根导航器创建参数列表时,它不会知道嵌套导航器中定义的屏幕。要解决此问题,我们需要显式指定嵌套导航器的参数列表。
这可以通过使用屏幕组件接收的 route
prop 的类型来完成
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>
时,它将包含嵌套导航器中定义的屏幕。
当使用动态 API 时,有必要为每个屏幕以及嵌套结构指定类型,因为它无法从代码中推断出来。
类型检查导航器
要类型检查我们的路由名称和参数,我们首先需要做的是创建一个对象类型,其中包含路由名称到路由参数的映射。例如,假设我们在根导航器中有一个名为 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 提供类型检查和 intelliSense。
包含映射的类型必须是类型别名(例如 type RootStackParamList = { ... }
)。它不能是接口(例如 interface RootStackParamList { ... }
)。它也不应该扩展 ParamListBase
(例如 interface RootStackParamList extends ParamListBase { ... }
)。这样做会导致不正确的类型检查,从而允许您传递不正确的路由名称。
如果您的导航器有 id
prop,您还需要将其作为泛型传递
const RootStack = createStackNavigator<RootStackParamList, 'MyStack'>();
类型检查屏幕
要类型检查我们的屏幕,我们需要注释屏幕接收的 navigation
和 route
props。React Navigation 中的导航器包导出泛型类型,以定义来自相应导航器的 navigation
和 route
props 的类型。
例如,您可以将 NativeStackScreenProps
用于 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 个泛型
- 我们之前定义的参数列表对象
- 屏幕所属的路由的名称
- 导航器的 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
prop 的类型,我们需要从导航器导入相应的类型。例如,@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 };
};
组合导航 props
当您嵌套导航器时,屏幕的 navigation prop 是多个 navigation props 的组合。例如,如果我们在堆栈内部有一个标签页,则 navigation
prop 将同时具有 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 个参数,第一个参数是主导航的 props 类型(拥有此屏幕的导航器的类型,在本例中是包含 Profile
屏幕的标签页导航器),第二个参数是辅助导航的 props 类型(父导航器的类型)。主类型应始终将屏幕的路由名称作为其第二个参数。
对于多个父导航器,此辅助类型应该是嵌套的
type ProfileScreenProps = CompositeScreenProps<
BottomTabScreenProps<TabParamList, 'Profile'>,
CompositeScreenProps<
StackScreenProps<StackParamList>,
DrawerScreenProps<DrawerParamList>
>
>;
如果分别注释 navigation
prop,则可以使用 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
不是类型安全的,因为类型参数无法静态验证。尽可能首选使用来自屏幕组件的 props 的 route
对象。对于不需要特定路由类型的通用代码,请使用 useRoute
。
要注释我们从 useRoute
获取的 route
对象,我们可以使用类型参数
const route = useRoute<ProfileScreenRouteProp>();
注释 options
和 screenOptions
当您将 options
传递给 Screen
或将 screenOptions
prop 传递给 Navigator
组件时,它们已经被类型检查,您无需执行任何特殊操作。但是,有时您可能想要将选项提取到单独的对象,并且您可能想要对其进行注释。
要注释选项,我们需要从导航器导入相应的类型。例如,@react-navigation/stack
的 StackNavigationOptions
import type { StackNavigationOptions } from '@react-navigation/stack';
const options: StackNavigationOptions = {
headerShown: false,
};
类似地,您可以从 @react-navigation/drawer
导入 DrawerNavigationOptions
,从 @react-navigation/bottom-tabs
导入 BottomTabNavigationOptions
等。
当使用 options
和 screenOptions
的函数形式时,您可以使用从导航器导出的类型来注释参数,例如 @react-navigation/stack
的 StackOptionsArgs
,@react-navigation/drawer
的 DrawerOptionsArgs
,@react-navigation/bottom-tabs
的 BottomTabOptionsArgs
等。
import type {
StackNavigationOptions,
StackOptionsArgs,
} from '@react-navigation/stack';
const options = ({ route }: StackOptionsArgs): StackNavigationOptions => {
return {
headerTitle: route.name,
};
};
注释 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
hook 的示例
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
prop 上具有正确的嵌套。
组织类型
在为 React Navigation 编写类型时,我们建议您进行一些组织,以保持井井有条。
- 最好创建一个单独的文件(例如
navigation/types.tsx
),其中包含与 React Navigation 相关的类型。 - 与其在组件中直接使用
CompositeNavigationProp
,不如创建一个可以重用的 helper 类型。 - 为根导航器指定全局类型可以避免在许多地方进行手动注释。
考虑到这些建议,包含类型的文件可能如下所示
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'>) {
// ...
}
如果您使用 hook(例如 useRoute
),则可以编写
import type { HomeTabScreenProps } from './navigation/types';
function PopularScreen() {
const route = useRoute<HomeTabScreenProps<'Popular'>['route']>();
// ...
}