React Native实现动态侧边抽屉菜单,解决状态更新导致的无限循环问题
问题根因
- 你在
StackNavigationManager组件内部定义了DrawerScreen和CustomDrawerContent两个子组件,每次父组件状态更新触发重渲染时,这两个子组件都会被重新创建为全新的函数,React会判定为组件类型变更,直接销毁旧实例挂载新实例。新挂载的Home组件会再次执行空依赖的useEffect,调用validateMenuData更新menuData状态,再次触发StackNavigationManager重渲染,形成死循环。 authContext的useMemo没有传入第二个依赖数组参数,导致每次StackNavigationManager渲染都会生成全新的authContext对象,触发所有消费该Context的组件额外重渲染,加剧了循环问题。- 小错误:React组件名必须首字母大写,你原代码的
home组件名首字母小写,也可能导致额外的渲染异常。
解决方案
- 将所有嵌套定义的子组件迁移到
StackNavigationManager外部,避免重复创建组件实例 - 给
useMemo补充空依赖数组,保证authContext只初始化一次 - 把
menuData也加入Context中,方便抽离到外部的CustomDrawerContent访问菜单数据
修正后代码示例
StackNavigationManager.js
import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { createDrawerNavigator, DrawerItemList, DrawerItem } from '@react-navigation/drawer'; import { SafeAreaView, FlatList } from 'react-native'; import { AuthContext } from './context'; import Home from './Home'; const Drawer = createDrawerNavigator(); const Stack = createStackNavigator(); // 抽离到外部的抽屉内容组件 function CustomDrawerContent(props) { const { menuData } = React.useContext(AuthContext); return ( <SafeAreaView style={{flex: 1}}> <DrawerItemList {...props} /> <FlatList data={menuData} keyExtractor={data => data.id.toString()} renderItem={({item}) => { return ( <DrawerItem label={item.name} onPress={() => alert(item.id)} /> ); }} /> </SafeAreaView> ); } // 抽离到外部的抽屉路由组件 const DrawerScreen = ({route, navigation}) => ( <Drawer.Navigator drawerContent={props => <CustomDrawerContent {...props} />} > <Drawer.Screen name="HomeDrawer" component={Home} /> </Drawer.Navigator> ); const StackNavigationManager = () => { const [menuData, setMenuData] = React.useState([]); // 补充空依赖数组,setMenuData为React保证的稳态值,不需要加入依赖 const authContext = React.useMemo(() => ({ menuData, validateMenuData: (newMenuData) => { setMenuData(newMenuData); }, }), []); return ( <AuthContext.Provider value={authContext}> <NavigationContainer> <Stack.Navigator initialRouteName="Home" > <Stack.Screen name="Home" component={DrawerScreen} options={{ headerShown: false, }} /> </Stack.Navigator> </NavigationContainer> </AuthContext.Provider> ); }; export default StackNavigationManager;
Home.js
仅需要修正组件名首字母大写即可,原有逻辑不需要改动,切换子系统时直接调用validateMenuData即可同步更新抽屉菜单。
import {AuthContext} from './context'; // 组件名首字母必须大写 const Home = ({navigation}) => { const {validateMenuData} = React.useContext(AuthContext); React.useEffect(() => { console.log('MOUNTED EFFECT'); getSubSystems(); return () => { isMounted = false; console.log('UNMOUNTED EFFECT'); }; }, []); const getSubSystems = async () => { const subSystemModel = { isActive: true, isVisible: true, }; fetchSubSystemData( subSystemModel, response => { if (response.list !== null) { let listData = [...response.list]; fetchMenuAction( listData[0].id, response2 => { validateMenuData([...response2.list]); }, ); } }, () => {}, ); }; // 原有页面渲染逻辑 } export default Home;
内容的提问来源于stack exchange,提问作者H.A
相关产品推荐
相关产品推荐

