React Navigation实现多Tab抽屉打开同屏多实例问题求助
问题说明
需要实现类似LinkedIn的交互效果:从抽屉导航点击设置、个人资料等页面时,在当前激活的Tab对应的Stack中打开页面,并支持生成多个页面实例。当前导航结构为Drawer嵌套TabNavigator,每个Tab页都是一个Stack,且所有Stack都配置了相同的页面(如Profile、Settings)。但在自定义Drawer中导航时,React Navigation无法自动确定使用哪个Stack,且无法直接获取当前激活的Stack名称,导致无法动态指定导航目标。
问题代码
App.tsx
/* eslint-disable react-native/no-inline-styles */ import 'react-native-gesture-handler'; import React from 'react'; import { Text } from 'react-native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createDrawerNavigator, DrawerContentScrollView, DrawerItem, } from '@react-navigation/drawer'; import { NavigationContainer } from '@react-navigation/native'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { StatusBar } from 'expo-status-bar'; import HomeStackScreen from '@pages/Home'; import { Routes } from '@src/constants/routes'; import { PracticalStackScreen } from '@src/pages/Practical/Practical'; import { TheoryStackScreen } from '@src/pages/Theory/Theory'; // Create a client const queryClient = new QueryClient(); const Tab = createBottomTabNavigator(); const Drawer = createDrawerNavigator(); const TabNavigator = () => { return ( <Tab.Navigator screenOptions={{ headerShown: false }} initialRouteName={Routes.student.home.STACK} > <Tab.Screen name={Routes.student.theory.STACK} component={TheoryStackScreen} options={{ title: 'TEÓRICO' }} /> <Tab.Screen name={Routes.student.home.STACK} component={HomeStackScreen} options={{ title: '' }} /> <Tab.Screen name={Routes.student.practical.STACK} component={PracticalStackScreen} options={{ title: 'PRÁCTICO' }} /> </Tab.Navigator> ); }; function CustomDrawerContent(props: any) { console.log('props ', props); return ( <DrawerContentScrollView {...props}> <Text>Mi cuenta</Text> <DrawerItem label='Configuración' onPress={() => props.navigation.navigate(Routes.student.SETTINGS)} /> <DrawerItem label='Métodos de pago' onPress={() => props.navigation.navigate(Routes.student.PAYMENT)} /> <Text>Social</Text> <DrawerItem label='Tiktok' onPress={() => props.navigation.navigate(Routes.student.PROFILE)} /> <Text>Ayuda</Text> <DrawerItem label='Preguntas frecuentes' onPress={() => props.navigation.navigate(Routes.student.FAQ)} /> <DrawerItem label='Atención al alumno' onPress={() => props.navigation.navigate(Routes.student.STUDENT_SUPPORT)} /> </DrawerContentScrollView> ); } const DrawerNavigation = () => { return ( <Drawer.Navigator useLegacyImplementation={true} drawerContent={(props) => <CustomDrawerContent {...props} />} initialRouteName={Routes.student.home.STACK} > <Drawer.Screen name={Routes.MAIN_TAB} component={TabNavigator} /> </Drawer.Navigator> ); }; export default function App() { return ( <QueryClientProvider client={queryClient}> <StatusBar /> <NavigationContainer> <DrawerNavigation /> </NavigationContainer> </QueryClientProvider> ); }
Home.tsx
/* eslint-disable react-native/no-color-literals */ import React from 'react'; import { StyleSheet, Text, View, Pressable } from 'react-native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { Routes } from '@src/constants/routes'; import Faq from '../Faq'; import Payment from '../Payment'; import Profile from '../Profile'; import Settings from '../Settings'; import StudentSupport from '../StudentSupport'; const HomeStack = createNativeStackNavigator(); export const HomeStackScreen = (): JSX.Element => { return ( <HomeStack.Navigator screenOptions={{ headerStyle: { backgroundColor: 'red' }, headerShown: false }} > <HomeStack.Screen name={Routes.student.home.MAIN} component={Home} /> <HomeStack.Screen name={Routes.student.PROFILE} component={Profile} /> <HomeStack.Screen name={Routes.student.SETTINGS} component={Settings} /> <HomeStack.Screen name={Routes.student.PAYMENT} component={Payment} /> <HomeStack.Screen name={Routes.student.FAQ} component={Faq} /> <HomeStack.Screen name={Routes.student.STUDENT_SUPPORT} component={StudentSupport} /> </HomeStack.Navigator> ); }; export const Home = ({ navigation }: any): JSX.Element => ( <View style={styles.container}> <Text>Home Screen</Text> <Pressable style={styles.button} onPress={() => navigation.navigate(Routes.student.PROFILE)}> <Text style={styles.text}>Perfil</Text> </Pressable> </View> ); const backgroundColor = '#fff'; const styles = StyleSheet.create({ button: { alignItems: 'center', backgroundColor: 'grey', borderRadius: 4, elevation: 3, justifyContent: 'center', paddingHorizontal: 32, paddingVertical: 12, }, container: { alignItems: 'center', backgroundColor, flex: 1, justifyContent: 'center', }, text: { color: 'white', fontSize: 16, fontWeight: 'bold', letterSpacing: 0.25, lineHeight: 21, }, });
Theory.tsx
import React from 'react'; import { Button, StyleSheet, Text, View } from 'react-native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { Routes } from '@src/constants/routes'; import Faq from '../Faq'; import Payment from '../Payment'; import Profile from '../Profile'; import Settings from '../Settings'; import StudentSupport from '../StudentSupport'; const TheoryStack = createNativeStackNavigator(); export const TheoryStackScreen = (): JSX.Element => { return ( <TheoryStack.Navigator screenOptions={{ headerStyle: { backgroundColor: 'red' }, headerShown: false }} > <TheoryStack.Screen name={Routes.student.theory.MAIN} component={Theory} /> <TheoryStack.Screen name={Routes.student.PROFILE} component={Profile} /> <TheoryStack.Screen name={Routes.student.SETTINGS} component={Settings} /> <TheoryStack.Screen name={Routes.student.PAYMENT} component={Payment} /> <TheoryStack.Screen name={Routes.student.FAQ} component={Faq} /> <TheoryStack.Screen name={Routes.student.STUDENT_SUPPORT} component={StudentSupport} /> </TheoryStack.Navigator> ); }; export function Theory({ navigation }: any): JSX.Element { return ( <View style={styles.container}> <Text>Theory Screen</Text> <Button title='Go to Home' onPress={() => navigation.navigate(Routes.student.home.STACK)} /> <Button title='Go to Practical' onPress={() => navigation.navigate(Routes.student.practical.STACK)} /> <Button title='Go to Profile' onPress={() => navigation.navigate(Routes.student.home.STACK, { screen: Routes.student.PROFILE }) } /> </View> ); } const backgroundColor = '#fff'; const styles = StyleSheet.create({ container: { alignItems: 'center', backgroundColor, flex: 1, justifyContent: 'center', }, });
Practical.tsx
import React from 'react'; import { Button, StyleSheet, Text, View } from 'react-native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { Routes } from '@src/constants/routes'; import Faq from '../Faq'; import Payment from '../Payment'; import Profile from '../Profile'; import Settings from '../Settings'; import StudentSupport from '../StudentSupport'; const PracticalStack = createNativeStackNavigator(); export const PracticalStackScreen = (): JSX.Element => { return ( <PracticalStack.Navigator screenOptions={{ headerStyle: { backgroundColor: 'red' }, headerShown: false }} > <PracticalStack.Screen name={Routes.student.practical.MAIN} component={Practical} /> <PracticalStack.Screen name={Routes.student.PROFILE} component={Profile} /> <PracticalStack.Screen name={Routes.student.SETTINGS} component={Settings} /> <PracticalStack.Screen name={Routes.student.PAYMENT} component={Payment} /> <PracticalStack.Screen name={Routes.student.FAQ} component={Faq} /> <PracticalStack.Screen name={Routes.student.STUDENT_SUPPORT} component={StudentSupport} /> </PracticalStack.Navigator> ); }; export function Practical({ navigation }: any): JSX.Element { return ( <View style={styles.container}> <Text>Practical Screen</Text> <Button title='Go to Home' onPress={() => navigation.navigate(Routes.student.home.STACK)} /> <Button title='Go to Theory' onPress={() => navigation.navigate(Routes.student.theory.STACK)} /> <Button title='Go to Profile' onPress={() => navigation.navigate(Routes.student.home.STACK, { screen: Routes.student.PROFILE }) } /> </View> ); } const backgroundColor = '#fff'; const styles = StyleSheet.create({ container: { alignItems: 'center', backgroundColor, flex: 1, justifyContent: 'center', }, });
解决方案
核心思路是获取当前激活的Tab对应的Stack名称,然后通过嵌套导航的方式,在该Stack中打开目标页面。
1. 修改CustomDrawerContent,添加获取活跃Stack的逻辑
在自定义抽屉组件中,通过导航状态获取当前激活的Tab对应的Stack名称,然后动态指定导航路径:
function CustomDrawerContent(props: any) { // 获取当前激活的Stack名称 const getActiveStack = () => { const drawerState = props.navigation.getState(); // 找到TabNavigator的路由状态 const tabRoute = drawerState.routes.find(route => route.name === Routes.MAIN_TAB); if (tabRoute?.state) { // 获取TabNavigator中当前激活的路由(即当前Stack) const activeTabIndex = tabRoute.state.index; return tabRoute.state.routes[activeTabIndex].name; } // 默认返回Home Stack return Routes.student.home.STACK; }; // 通用导航方法 const navigateToScreen = (screenName: string) => { const activeStack = getActiveStack(); props.navigation.navigate(Routes.MAIN_TAB, { screen: activeStack, params: { screen: screenName } }); }; return ( <DrawerContentScrollView {...props}> <Text>Mi cuenta</Text> <DrawerItem label='Configuración' onPress={() => navigateToScreen(Routes.student.SETTINGS)} /> <DrawerItem label='Métodos de pago' onPress={() => navigateToScreen(Routes.student.PAYMENT)} /> <Text>Social</Text> <DrawerItem label='Tiktok' onPress={() => navigateToScreen(Routes.student.PROFILE)} /> <Text>Ayuda</Text> <DrawerItem label='Preguntas frecuentes' onPress={() => navigateToScreen(Routes.student.FAQ)} /> <DrawerItem label='Atención al alumno' onPress={() => navigateToScreen(Routes.student.STUDENT_SUPPORT)} /> </DrawerContentScrollView> ); }
2. 关键逻辑说明
- 获取活跃Stack:通过
navigation.getState()获取Drawer的导航状态,从中找到TabNavigator的路由,再通过Tab的index属性拿到当前激活的Stack名称。 - 嵌套导航:使用
navigation.navigate的嵌套参数,先定位到MAIN_TAB,再进入对应的活跃Stack,最后打开目标页面。这种方式会在当前Stack的栈顶添加新页面实例,符合LinkedIn的交互逻辑。
3. 可选优化:封装自定义Hook
如果需要在多个地方使用获取活跃Stack的逻辑,可以封装成自定义Hook:
import { useNavigation } from '@react-navigation/native'; import { Routes } from '@src/constants/routes'; export const useActiveStack = () => { const navigation = useNavigation(); const getActiveStack = () => { const drawerState = navigation.getState(); const tabRoute = drawerState.routes.find(route => route.name === Routes.MAIN_TAB); if (tabRoute?.state) { const activeTabIndex = tabRoute.state.index; return tabRoute.state.routes[activeTabIndex].name; } return Routes.student.home.STACK; }; return getActiveStack(); };
然后在CustomDrawerContent中使用:
function CustomDrawerContent(props: any) { const activeStack = useActiveStack(); const navigateToScreen = (screenName: string) => { props.navigation.navigate(Routes.MAIN_TAB, { screen: activeStack, params: { screen: screenName } }); }; // ... 其余代码不变 }
效果验证
修改完成后,在任意Tab下打开抽屉,点击设置、个人资料等选项,页面会在当前Tab对应的Stack中打开,且每个Tab的Stack会独立维护自己的页面栈,实现多实例效果。
内容的提问来源于stack exchange,提问作者Nacho
相关产品推荐
相关产品推荐

