javascript - React Navigation v5 如何使用 TypeScript 在标签页之间导航
这是我的导航器。我有一个底部标签(bottomTab),其中包含三个堆栈导航器作为屏幕:'Home'、'Profile'、'Discover',以及一个主根堆栈导航器,里面有底部标签导航器和一些模态屏幕。我无法理解如何按照文档指南直接用 TypeScript 进行类型检查。有人能解释我哪里做错了吗?目前,如果我想从标签 'Home' 导航到标签 'Profile',我只能看到 'Home' 和所有模态屏幕,以及到 tabNavigator 本身的导航选项,但看不到到我的 tabNavigator 的各个标签的导航选项:
我的 mainNavigator 代码:
export type TabParamList = {
Home: undefined;
Discover: undefined;
Profile: undefined;
}
export type MainStackParamList = {
TabNavigator: undefined;
ModalStreamsPlayer: { streamsQualitys: ParsedStream[], stream: Stream} | undefined;
ModalWebBrowser: { url: string, title: string } | undefined;
ModalVideoPlayer: { video: Video } | undefined;
}
export type HomeStackParamList = {
Home: undefined;
}
export type DiscoverStackParamList = {
Discover: undefined;
}
export type ProfileStackParamList = {
Profile: undefined;
}
const Tab = createBottomTabNavigator<TabParamList>();
const RootStack = createStackNavigator<MainStackParamList>();
const HomeStack = createStackNavigator<HomeStackParamList>();
const DiscoverStack = createStackNavigator<DiscoverStackParamList>();
const ProfileStack = createStackNavigator<ProfileStackParamList>();
const screenOptions = ({ route }): StackNavigationOptions => {
return {
title: route.name,
headerTitleStyle: {
fontFamily: 'ProximaNova-Semibold',
fontSize: 18,
textTransform: 'uppercase',
lineHeight: 22,
color: '#D6FFFD'
},
headerStyle: {
backgroundColor: '#133740',
elevation: 0,
shadowOpacity: 0,
borderBottomWidth: 0
},
headerTintColor: '#D6FFFD',
headerTitleAlign: 'center',
}
}
const TabHomeStack = () => {
return (
<HomeStack.Navigator>
<HomeStack.Screen
name='Home'
component={HomeScreen}
options={screenOptions}
/>
</HomeStack.Navigator>
);
};
const TabDiscoverStack = () => {
return (
<DiscoverStack.Navigator>
<DiscoverStack.Screen
name='Discover'
component={DiscoverScreen}
options={screenOptions}
/>
</DiscoverStack.Navigator>
);
}
const TabProfileStack = () => {
return (
<ProfileStack.Navigator>
<ProfileStack.Screen
name='Profile'
component={ProfileScreen}
options={screenOptions}
/>
</ProfileStack.Navigator>
)
}
const TabNavigator = () => {
return (
<Tab.Navigator
tabBarOptions={{
showLabel: false,
// activeTintColor: '#2F80ED',
// inactiveTintColor: '#999999',
style: {
backgroundColor: '#133740',
height: Platform.OS === 'ios' ? 94 : 60,
borderTopWidth: 0
}
}}>
<Tab.Screen
name='Home'
component={TabHomeStack}
options={{ tabBarIcon: ({color, focused, size}) => (
<Image
source={ focused ? images.tabBarIconHomeActive : images.tabBarIconHomeNormal }
/>
)}}
/>
<Tab.Screen
name='Discover'
component={TabDiscoverStack}
options={{ tabBarIcon: ({color, focused, size}) => (
<Image
source={ focused ? images.tabBarIconDiscoverActive : images.tabBarIconDiscoverNormal }
/>
)}}
/>
<Tab.Screen
name='Profile'
component={TabProfileStack}
options={{ tabBarIcon: ({color, focused, size}) => (
<Image
source={ focused ? images.tabBarIconProfileActive : images.tabBarIconProfileNormal }
/>
)}}
/>
</Tab.Navigator>
);
};
const RootStackNavigator = () => {
return (
<RootStack.Navigator mode='modal'>
<RootStack.Screen
name='TabNavigator'
component={TabNavigator}
options={{ headerShown: false }}
/>
<RootStack.Screen
name='ModalStreamsPlayer'
component={StreamsPlayer}
options={{ headerShown: false }}
/>
<RootStack.Screen
name='ModalWebBrowser'
component={WebScreen}
options={{ headerShown: false }}
/>
<RootStack.Screen
name='ModalVideoPlayer'
component={YoutubePlayer}
options={{ headerShown: false }}
/>
</RootStack.Navigator>
);
}
export default RootStackNavigator;
主页标签的主屏幕:
type HomeScreenNavigationProp = CompositeNavigationProp<
BottomTabNavigationProp<HomeStackParamList, 'Home'>,
StackNavigationProp<MainStackParamList>
>;
type HomeScreenRouteProp = RouteProp<
HomeStackParamList, 'Home'
>;
export type HomeProps = {
navigation: HomeScreenNavigationProp;
route: HomeScreenRouteProp;
};
export default class HomeScreen extends React.Component<HomeProps> {
render() {
return (
<View style={styles.container}>
<ScrollView style={styles.scrollView}>
<View style={{ paddingBottom: 24 }}>
<StreamsScreen navigation={this.props.navigation} />
<View style={styles.separator}></View>
<NewsScreen navigation={this.props.navigation} />
<View style={styles.separator}></View>
<VideosScreen navigation={this.props.navigation} />
<View style={styles.separator}></View>
</View>
</ScrollView>
</View>
);
}
}
答案#1
你需要在你的屏幕中使用 两次 CompositeNavigationProp,并且对于嵌套的导航器使用 NavigatorScreenParams,(https://reactnavigation.org/docs/typescript#combining-navigation-props)这似乎是使用模态窗口的常见模式,但文档缺乏相关说明。
在你的导航器中:
import { NavigatorScreenParams } from "@react-navigation/native";
/* ... */
export type TabParamList = {
Home: undefined;
Discover: undefined;
Profile: undefined;
}
export type MainStackParamList = {
TabNavigator: NavigatorScreenParams<TabParamList>;
ModalStreamsPlayer: {...};
ModalWebBrowser: {...};
ModalVideoPlayer: {...};
}
/* ... */
注意:如果你为每个标签页使用 StackNavigator,请为每个标签页使用 NavigatorScreenParams
在你的屏幕上:
/* ... */
type PrimaryNavigator = StackNavigationProp<HomeStackParamList, "Home">;
type PrimaryNavigatorParent = BottomTabNavigationProp<TabParamList>;
type RootNavigatorParent = BottomTabNavigationProp<MainStackParamList>;
type PrimaryNavigatorNested = CompositeNavigationProp<PrimaryNavigator, RootNavigatorParent>;
type HomeProps = {
navigation: CompositeNavigationProp<PrimaryNavigatorNested, PrimaryNavigatorParent>;
route: HomeScreenRouteProp;
}
/* ... */
答案#2
你可以参考这个:https://dev-yakuza.github.io/en/react-native/react-navigation-v5/ 在这个参考资料中,也提供了源代码。https://github.com/dev-yakuza/react-navigation-v5-exercise
这是我的案例步骤,与上述参考不同:
- 为导航参数定义一个类型 这是个人资料堆栈中的两个屏幕,例如
// profile stack params list
export type ProfileStackParams = {
Profile: undefined;
ProfileEdit: undefined;
};
这里的未定义意味着参数未被定义。如果有必要,你可以指定你的参数。
- 定义一个导航路由属性,例如使用 ProfileStackParams,定义路由参数:
export type ProfileRouteProp = RouteProp<ProfileStackParams, 'Profile'>;
或者你可以定义一个导航属性:
export type ProfileNavigationProp = StackNavigationProp<ProfileStackParams, 'Profile'>;
- 设置底部标签导航器的个人资料堆栈
// stack navigator
const Stack = createStackNavigator();
// bottom tab navigator, here I use material bottom tab navigator
//const Tab = createBottomTabNavigator<BottomTabParams>();
const Tab = createMaterialBottomTabNavigator<BottomTabParams>();
然后,设置配置文件栈和其他栈。
const TabProfileStack = (props): JSX.Element => {
console.log('TabProfileStack props', props);
return (
<Stack.Navigator mode="card" headerMode="screen">
<Stack.Screen
name="Profile"
component={Profile}
/>
<Stack.Screen name="ProfileEdit" component={ProfileEdit} />
</Stack.Navigator>
);
};
- 设置底部导航
const TabNavigator = (props) => {
return (
<Tab.Navigator
initialRouteName="Profile"
labeled={true}
>
<Tab.Screen
name="Feed"
component={TabFeedStack}
/>
<Tab.Screen
name="Profile"
component={TabProfileStack}
/>
</Tab.Navigator>
);
};
标签导航器的一个问题是,你不能向标签导航器的子屏幕传递参数。
在这种情况下,你可以使用 React.useContext 来设置/获取参数。正因为如此,我的 Profile 组件不接受任何 props。
const ProfileScreen = (): JSX.Element => {
// use context instead of navigation params
const {authState} = useContext(AuthContext)!;
