React Native中使用useAsyncStorage引发自定义抽屉组件无限渲染循环如何解决
无限渲染问题排查与修复方案
核心问题根源
两个核心逻辑错误叠加形成了渲染死循环,CustomDrawer 组件本身的写法没有问题,问题全部出在 App 根组件的实现上:
- useEffect 缺失依赖数组,触发自循环
当前 App 组件内的 useEffect 没有传入第二个参数(依赖数组),按照 React 规则,这种写法会在每一次组件渲染完成后都执行回调。回调内的setTimeout会调用setActiveDrawer修改组件状态,状态变更又会触发 App 重渲染,渲染完成后再次执行 useEffect,形成第一个循环闭环。 - 渲染流程内直接调用 setState,触发二次循环
你把UpdateScreen作为 Drawer 的 drawerContent 渲染内容,这个函数在每次侧边栏渲染时,都会执行setNavigation(props.navigation)(即修改Scren_navigation状态)。React 明确要求渲染流程必须保持纯性,不能在渲染过程中修改组件状态:这里的 setState 会直接触发 App 重渲染,重渲染又会让 drawerContent 重新执行,再次调用 setState,和上面的 useEffect 循环形成叠加,彻底锁死页面。 - 额外隐患:setTimeout 无清理机制
没有在 useEffect 清理逻辑中清除已创建的定时器,会导致多次触发状态更新,加重渲染循环的问题。
分步修复实现
- 修正 setState 触发时机:所有状态更新都放到 useEffect 回调中执行,杜绝在渲染流程中直接调用 setState,保证渲染逻辑的纯性。
- 给 useEffect 补全依赖逻辑与定时器清理:按照实际业务逻辑指定依赖项,避免无意义的重复执行,同时在 effect 清理函数中清除未执行的定时器。
- 优化 activeDrawer 的状态存储逻辑:不再将 JSX 元素直接存到 state 中,改为存储对应的抽屉组件标识,在渲染时根据标识返回对应组件,减少不必要的元素重建。
修复后的 App 组件参考代码:
import {useEffect, useRef, useState} from 'react'; import {NavigationContainer} from '@react-navigation/native'; import {createDrawerNavigator} from '@react-navigation/drawer'; import CustomDrawer from './CustomDrawer'; import NoInternetDrawer from './NoInternetDrawer'; import EmptyDrawer from './EmptyDrawer'; import useNetInfo from './useNetInfo'; // 原有网络检测 Hook import Navigation from './Navigation'; const Drawer = createDrawerNavigator(); const App = () => { const network = useNetInfo(); // 用 ref 暂存导航对象,避免渲染流程中直接触发状态更新 const navRef = useRef(null); // 存储有效导航对象 const [navigation, setNavigation] = useState(null); // 存储当前需要展示的抽屉类型,替代直接存 JSX 的写法 const [drawerType, setDrawerType] = useState('empty'); // 监听 ref 变化,在 effect 中完成导航对象的状态更新(不在渲染流程中执行 setState) useEffect(() => { if (navRef.current && navRef.current !== navigation) { setNavigation(navRef.current); } }, [navRef.current, navigation]); // 监听网络状态与导航有效性,切换对应抽屉组件 useEffect(() => { // 未获取到有效导航对象时不执行后续逻辑 if (!navigation) return; const timer = setTimeout(() => { setDrawerType(network.isInternetReachable ? 'custom' : 'noInternet'); }, 5000); // 清理未执行的定时器,避免重复触发 return () => clearTimeout(timer); }, [network.isInternetReachable, navigation]); // 根据存储的抽屉类型渲染对应内容 const renderDrawerContent = () => { switch (drawerType) { case 'custom': return <CustomDrawer navigation={navigation} />; case 'noInternet': return <NoInternetDrawer navigation={navigation} />; default: return <EmptyDrawer />; } }; return ( <NavigationContainer> <Drawer.Navigator drawerContent={props => { // 仅将导航对象存入 ref,不直接调用 setState navRef.current = props.navigation; return renderDrawerContent(); }}> <Drawer.Screen name="StackNavigation" component={Navigation} /> </Drawer.Navigator> </NavigationContainer> ); }; export default App;
补充说明
CustomDrawer 组件内的逻辑本身没有问题:
- 内部的
useEffect传了空依赖数组,只会在组件挂载时执行一次 token 读取逻辑,不会触发重复渲染 setLoggedIn的调用是在异步回调内执行,不在渲染流程中,符合 React 的状态更新规则
内容的提问来源于stack exchange,提问作者ZeevhY Org.
相关产品推荐
相关产品推荐

