网站搜索

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

这是我的案例步骤,与上述参考不同:

  1. 为导航参数定义一个类型 这是个人资料堆栈中的两个屏幕,例如
// profile stack params list
export type ProfileStackParams = {
  Profile: undefined;
  ProfileEdit: undefined;
};

这里的未定义意味着参数未被定义。如果有必要,你可以指定你的参数。

  1. 定义一个导航路由属性,例如使用 ProfileStackParams,定义路由参数:
export type ProfileRouteProp = RouteProp<ProfileStackParams, 'Profile'>;

或者你可以定义一个导航属性:

export type ProfileNavigationProp = StackNavigationProp<ProfileStackParams, 'Profile'>;
  1. 设置底部标签导航器的个人资料堆栈
// 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>
  );
};
  1. 设置底部导航
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)!;