React Navigation点击Tab按钮强制显示initialRouteName页面
问题基础信息
页面结构
主容器为底部标签导航(Main Tab),结构如下:
- Tab A:仅包含1个页面 Screen A1
- Tab B:嵌套栈导航(TabBStack),栈的
initialRouteName配置为TabBScreen1,栈内包含2个页面:Screen B1、Screen B2
现有跳转逻辑
- 可从Screen A1跨标签跳转至Tab B栈内的Screen B2页面
- 可从Tab B栈内的Screen B1跳转至同栈的Screen B2页面
现象对比
符合预期的行为:初始状态点击Tab B的底部标签按钮,会正确加载Screen B1;从Screen B1跳转至Screen B2后,再次点击Tab B的底部按钮,会自动弹出Screen B2回到Screen B1。
异常问题:如果初始状态下直接从Screen A1跨标签跳转到Tab B下的Screen B2页面,此时点击Tab B的底部标签按钮无任何响应,即便TabBStack已经配置initialRouteName为TabBScreen1,也无法回到Screen B1,该场景下栈的根页面被错误设置为B2。
诉求
点击Tab B的底部标签按钮时,若当前展示的不是Screen B1,则弹出栈内所有其他页面,回到栈初始页Screen B1。
环境信息
使用的导航相关依赖版本:
"@react-navigation/bottom-tabs": "^6.2.0", "@react-navigation/native": "^6.0.8", "@react-navigation/stack": "^6.1.1"
问题复现代码
初始示例代码如下:
import React, { useRef } from "react"; import { StyleSheet, View, Text, Button } from "react-native"; import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; const Tab = createBottomTabNavigator(); const Stack = createStackNavigator(); const TabAScreen1 = ({ route, navigation }) => { return ( <View> <Text>Tab A screen 1</Text> <Button title="Go to tab B screen 2" onPress={() => { navigation.navigate("TabBStack", { screen: "TabBScreen2" }); }} /> </View> ); }; const TabBScreen1 = ({ route, navigation }) => { return ( <View> <Text>Tab B screen 1</Text> <Button title="Go to tab B screen 2" onPress={() => { navigation.navigate("TabBScreen2"); }} /> </View> ); }; const TabBScreen2 = ({ route, navigation }) => { return ( <View> <Text>Tab B screen 2</Text> </View> ); }; function TabBStack() { return ( <Stack.Navigator initialRouteName="TabBScreen1" screenOptions={{ headerShown: false }}> <Stack.Screen name="TabBScreen1" component={TabBScreen1}/> <Stack.Screen name="TabBScreen2" component={TabBScreen2}/> </Stack.Navigator> ); } function MainTab() { return ( <Tab.Navigator screenOptions={{ headerShown: false }}> <Tab.Screen name="TabAScreen1" component={TabAScreen1}/> <Tab.Screen name="TabBStack" component={TabBStack}/> </Tab.Navigator> ); }; const App = props => { const navigationRef = useRef(); return ( <NavigationContainer> <MainTab/> </NavigationContainer> ); }; const styles = StyleSheet.create({}); export default App;
已编写的监听器基础代码框架:
function MainTab() { return ( <Tab.Navigator screenOptions={{ headerShown: false }}> <Tab.Screen name="TabAScreen1" component={TabAScreen1}/> <Tab.Screen name="TabBStack" component={TabBStack} listeners={({ navigation }) => ({ tabPress: (e) => { console.log("Tab B pressed"); // 若当前页面不是Screen B1,弹出栈内所有页面回到Screen B1 }, })} /> </Tab.Navigator> ); };
解决方案
核心逻辑是在tabPress事件触发时,先判断当前是否已经停留在TabB栈,若是则阻止默认跳转行为,再主动调用navigate方法重置嵌套栈到初始页。React Navigation 6的navigate方法默认会在目标页面已存在于栈内时,自动弹出栈上层的所有页面,无需手动调用pop方法。
修改后的MainTab组件代码如下:
function MainTab() { return ( <Tab.Navigator screenOptions={{ headerShown: false }}> <Tab.Screen name="TabAScreen1" component={TabAScreen1}/> <Tab.Screen name="TabBStack" component={TabBStack} listeners={({ navigation, route }) => ({ tabPress: (e) => { const state = navigation.getState(); const currentTabName = state.routes[state.index]?.name; // 若当前已经在TabB栈内,阻止默认跳转行为,主动重置栈到初始页 if (route.name === currentTabName) { e.preventDefault(); // 直接navigate到栈的初始页,React Navigation会自动弹出栈内其他页面 navigation.navigate("TabBStack", { screen: "TabBScreen1" }); } }, })} /> </Tab.Navigator> ); };
实现说明:跨标签跳转后点击Tab无响应,是因为跨标签navigate到嵌套栈非初始页时,Tab导航默认判定当前已经在目标Tab,不会触发默认的栈重置逻辑。手动阻止默认事件后主动调用带screen参数的navigate方法,即可强制把嵌套栈重置到指定初始页,同时兼容同Tab内跳转后点击Tab返回初始页的原有默认行为。
内容的提问来源于stack exchange,提问作者Jon

