我正在尝试从 React Navigation 中输入
useNavigation
。我希望能够仅传递路线名称,但除非我也传递该路线的道具,否则我会收到错误。
按照文档,我理解实现应该如下所示:
import { StackNavigationProp } from '@react-navigation/stack';
type StackParamList = {
Home: { foo: string, onBar: () => void }
About: AboutProps
}
type NavigationProps = StackNavigationProp<StackParamList>
const MyComponent = () => {
const navigation = useNavigation<NavigationProps>()
const handleOnNavigate = () => navigation.navigate('Home')
// ^ TypeError!
我在最后一行收到类型错误。如果我为该路线添加道具,错误就会消失,但我不必这样做。
navigation.navigate('Home', { foo: 'hello', onBar: () => {} })
查看 navigation.navigate
的类型声明(见下文),这应该不是必需的,因为简单地将路由名称作为唯一参数传递会发生重载。我一定做错了什么,因为这是不被接受的……但是什么、在哪里以及为什么?
这是一个重现 TypeError 的 CodeSandBox。
React 导航类型.d.ts(链接)
navigate<RouteName extends keyof ParamList>(...args: undefined extends ParamList[RouteName]
? [screen: RouteName] | [screen: RouteName, params: ParamList[RouteName]]
: [screen: RouteName, params: ParamList[RouteName]])
: void;
RootStackParamList
类型实现
// src/_app.tsx import { NavigationContainer } from "@react-navigation/native"; import { createNativeStackNavigator } from "@react-navigation/native-stack"; import HomeScreen from "./screens/home"; import AuthScreen from "./screens/auth"; export type ScreenNames = ["Home", "Auth"] // type these manually export type RootStackParamList = Record<ScreenNames[number], undefined>; export type StackNavigation = NavigationProp<RootStackParamList>; const Stack = createNativeStackNavigator<RootStackParamList>(); export const App = () => { return ( <NavigationContainer> <Stack.Navigator initialRouteName="Home"> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Auth" component={AuthScreen} /> </Stack.Navigator> </NavigationContainer> ); };
// src/screens/home.tsx
import { useNavigation } from '@react-navigation/native';
import { type StackNavigation } from "../_app";
const HomeScreen = () => {
const { navigate } = useNavigation<StackNavigation>();
const handleOnNavigate = () => navigate("Home");
// ... the rest of the component code
}
export default HomeScreen;
另请注意,他们不使用
useNavigation
钩子来访问页面/屏幕,而是使用深层嵌套的组件,因为导航器已经作为 prop 传递给传递到
Stack.Screen
的组件,这意味着您也可以使用 HomeScreen
的导航器如下:// src/screens/home.tsx
import { type StackNavigation } from "../_app";
interface HomeScreenProps {
navigation: StackNavigation;
}
const HomeScreen: React.FC<HomeScreenProps> = ({ navigation }) => {
const handleOnNavigate = () => navigation.navigate("Home");
// ... the rest of the component code
}
export default HomeScreen;
| undefined
。
让我解释一下为什么你需要这样做。考虑这个声明:
navigate<RouteName extends keyof ParamList>(
...args: undefined extends ParamList[RouteName]
? [screen: RouteName] | [screen: RouteName, params: ParamList[RouteName]]
: [screen: RouteName, params: ParamList[RouteName]]
): void;
这是最有趣的部分:
undefined extends ParamList[RouteName]
这意味着如果
undefined
扩展了 ParamList[RouteName]
,您只能使用一个参数。让我们把它分成更小的例子:
type ParamList = {
Home: { foo: string; onBar: () => void };
About: { bar: string; onBaz: () => void };
Undefined: undefined,
};
type Check<T extends keyof ParamList> = undefined extends ParamList[T] ? 'might be undefined' : 'can not be undefined'
type Result = Check<'Home'> // "can not be undefined"
type Result2 = Check<'Undefined'> // "might be undefined"
您可能已经注意到,如果您提供
Home
TS 将需要两个参数,因为
ParamList['Home']
返回一个不能是 undefined
的对象。另一方面,undefined
扩展了
ParamList['Undefined']
- 因此 TS 允许您仅使用一个参数。这就是为什么 TS 不允许你只传递一个参数。
您在类型中指定了这一点:
Home: { foo: string, onBar: () => void }
这意味着
Home
接受这些参数。如果您的路线不采用任何参数,并且您可以执行
navigate('Home')
,则不应在类型中指定任何参数。如果这些参数是可选的,那么您需要相应地指定类型:
Home: { foo: string, onBar: () => void } | undefined
StackParamList
,您可以定义导航到屏幕时期望的参数类型。如果您不希望 Home 有任何参数,您还必须在您的类型中相应地定义它:
export type StackParamList = {
Home: undefined,
About: { bar: string; onBaz: () => void };
};
如果您需要任何其他可选参数,您可以在类型声明中使用管道:
export type StackParamList = {
Home: { foo: string; onBar: () => void } | undefined;
About: { bar: string; onBaz: () => void };
};
// Will work in both ways:
const handleOnNavigate = () => navigation.navigate('Home')
作为解决方法,您还可以使用另一个重载并传递配置对象:
const handleOnNavigate = () => navigation.navigate({key : "Home"});
您需要做的就是导入所需的钩子、道具和类型,然后像上面的示例一样应用 干杯👋
查看注释掉的 NavigationProps 以及它下面可能应该包含的内容。
CompositeScreenProps
,这对我来说效果很好。这是示例路线结构。
根路由/* RootRoute.tsx */
export type RootRoute = {
MainRoute: undefined;
ChatRoute: undefined;
AuthRoute: undefined;
};
const Stack = createStackNavigator<RootRoute>();
const Root = () => {
return (
<Stack.Navigator>
<Stack.Screen name={'MainRoute'} component={MainRoute} />
<Stack.Screen name={'ChatRoute'} component={ChatRoute} />
<Stack.Screen name={'AuthRoute'} component={AuthRoute} />
</Stack.Navigator>
);
/* MainRoute.tsx */
export type MainRoute = {
Home: undefined;
...
};
const Stack = createStackNavigator<MainRoute>();
const Main = () => {
return (
<Stack.Navigator>
<Stack.Screen name={'Home'} component={...} />
<Stack.Screen name={...} component={...} />
</Stack.Navigator>
);
/* AuthRoute.tsx */
export type AuthRoute = {
Email: undefined;
Phone: undefined;
};
const Stack = createStackNavigator<AuthRoute>();
const Validation = () => {
return (
<Stack.Navigator>
<Stack.Screen name={'Email'} component={EmailAuthScreen} />
<Stack.Screen name={'Phone'} component={PhoneAuthScreen} />
</Stack.Navigator>
);
/* PhoneAuthScreen.tsx */
interface PhoneValidationProps
extends StackScreenProps<AuthRoute, 'Phone'> {}
const PhoneValidationScreen = ({ route }: PhoneValidationProps) => {
const { navigation, route } =
useNavigation<
CompositeScreenProps<
StackScreenProps<RootRoute, 'MainRoute'>,
StackScreenProps<AuthRoute, 'Phone'>
>
>();
...
}