React Native Tabs导航Bug:切换底部标签数秒后自动返回首个标签
问题现象
在React Native应用中集成React Navigation实现嵌套导航(底部Tab内嵌顶部Tab)时出现异常:在底部标签1内嵌的顶部标签中完成导航操作后,手动切换到底部标签2,等待数秒后页面会自动跳转回最初访问的底部标签1。
项目依赖配置
"dependencies": { "@react-native-async-storage/async-storage": "^1.17.3", "@react-native-community/cli": "^6.4.0", "@react-navigation/bottom-tabs": "^6.3.1", "@react-navigation/drawer": "^6.4.1", "@react-navigation/material-bottom-tabs": "^6.2.1", "@react-navigation/material-top-tabs": "^6.2.1", "@react-navigation/native": "^6.0.10", "@react-navigation/native-stack": "^6.6.1", "accordion-collapse-react-native": "^1.1.0", "pushy-react-native": "^1.0.22", "react": "^17.0.2", "react-native": "^0.68.2", "react-native-background-fetch": "^4.1.0", "react-native-background-geolocation": "^4.7.1", "react-native-bootsplash": "^4.1.4", "react-native-gesture-handler": "^2.4.1", "react-native-in-app-review": "^3.3.3", "react-native-linear-gradient": "^2.5.6", "react-native-modal": "^13.0.1", "react-native-pager-view": "^5.4.15", "react-native-paper": "^4.12.0", "react-native-reanimated": "^2.6.0", "react-native-render-html": "^6.3.4", "react-native-safe-area-context": "^3.4.1", "react-native-screens": "^3.13.1", "react-native-tab-view": "^3.1.1", "react-native-vector-icons": "^9.1.0", "rn-fetch-blob": "^0.10.13" }
复现参考
核心代码片段
1. 根入口app.js
<NavigationContainer onReady={() => RNBootSplash.hide({ fade: true })}> <AllAppNavigation.Navigator initialRouteName={initialRouteName} screenOptions={{header: () => null}}> <AllAppNavigation.Screen name="IndexScreen" children={IndexDrawerNavigator} /> <AllAppNavigation.Screen name="HomeScreen" children={StackNavigator} initialParams={{gidcom: String(GIdCom), gdatacom: String(GDataCom)}} /> </AllAppNavigation.Navigator> </NavigationContainer>
2. 栈导航(Stack Navigator)
<Stack.Navigator screenOptions={screenOptionStyle}> <Stack.Screen name="StackHome" component={BottomTabNavigator} initialParams={{screen:'TabHome', // 异步context未加载完成时通过参数传值给底部导航 gidcom: idCom, gdatacom: JSON.stringify(GDataCom) }} options={() => ({ headerLeft: () => LogoComune(GDataCom), headerTitle: () => TitoloHeader(GDataCom), headerRight: () => DotsIcon(navigation) })} /> </Stack.Navigator>
3. 嵌套标签导航器
const MaterialTopNavigatorAvvisi = () => { return ( <Tab2.Navigator> <Tab2.Screen name="InPrimoPiano" options={{tabBarLabel: 'In Primo Piano'}} component={AvvisiScreen} initialParams={{filtro: 'primopiano=true'}} /> <Tab2.Screen name="Tutti" options={{tabBarLabel: 'Tutti'}} component={AvvisiScreen} initialParams={{filtro: ''}} /> </Tab2.Navigator> ); } const MaterialTopNavigatorEventi = () => { return ( <Tab2.Navigator> <Tab2.Screen name="Oggi" options={{tabBarLabel: 'Oggi'}} component={EventiScreen} initialParams={{filtro: 'tipo=0'}} /> <Tab2.Screen name="Futuri" options={{tabBarLabel: 'Futuri'}} component={EventiScreen} initialParams={{filtro: 'tipo=1'}} /> <Tab2.Screen name="Principali" options={{tabBarLabel: 'Principali'}} component={EventiScreen} initialParams={{filtro: 'tipo=2'}} /> </Tab2.Navigator> ); }
4. AvvisiScreen页面实现
const ItemView = ({item}, context) => { return ( <ElementoLista item={item} colore={ColoreBase} destinazione='AvvisiDettaglio' /> ); }; useEffect(() => { fetch( BuildApiUrl("avvisi", idCom, null, filtro) ) .then((response) => response.json()) .then((json) => {setData(json)}) .catch((error) => console.error(error)) .finally(() => setLoading(false)); }, []); const wait = (timeout) => { return new Promise(resolve => setTimeout(resolve, timeout)); } const [refreshing, setRefreshing] = React.useState(false); const onRefresh = React.useCallback(() => { setRefreshing(true); wait(2000).then(() => setRefreshing(false)); }, []); return ( <SafeAreaView style={styles.center}> {isLoading ? <View style={[styles.container, styles.horizontal]}><ActivityIndicator size="large" /></View> : ( <View style={styles.center}> {(data == '') ? <NoResultMsg/> : (<FlatList data={data} keyExtractor={item => item.Id.toString()} renderItem={(item) => ItemView(item)} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} /> } </FlatList>)} </View> )} </SafeAreaView> ); };
异常原因
- 错误通过父级Screen的
initialParams传screen:'TabHome'指定子导航初始路由,当异步全局数据(GIdCom、GDataCom)加载完成后,父组件重渲染导致BottomTabNavigator被重新挂载,直接读取initialParams强制跳回TabHome(底部标签1),和观察到的「等待数秒后自动回跳」的时间点完全吻合,数秒延迟就是异步数据加载的耗时。 - 导航器组件引用不稳定:如果StackNavigator、BottomTabNavigator、顶部Tab导航器被定义在App等组件的内部,每次重渲染都会生成新的组件引用,导致整个导航树被卸载重挂,导航状态完全重置。
- 栈导航的options配置中直接引用外部navigation实例,没有从options的回调参数中解构获取,会触发导航状态异常更新。
修复方案
- 移除Stack.Screen传给BottomTabNavigator的initialParams中的
screen:'TabHome'字段,底部导航的初始路由直接在BottomTab.Navigator上通过initialRouteName属性配置,不要通过父级参数强制指定子路由。 - 所有导航器组件统一挪到组件作用域外定义,不要写在页面/根组件的函数内部,保证组件引用稳定,避免重渲染时导航器被意外重挂载。
- 修正Screen的options中navigation的获取方式,从回调参数中解构:
options={({navigation}) => ({ headerLeft: () => LogoComune(GDataCom), headerTitle: () => TitoloHeader(GDataCom), headerRight: () => DotsIcon(navigation) })} - 检查异步数据加载逻辑,数据更新时不要调用
navigate重置当前导航栈,需要更新参数用navigation.setParams方法实现。
内容的提问来源于stack exchange,提问作者Steelkeeper
相关产品推荐
相关产品推荐

