React中如何避免导航抽屉开关时触发子组件useEffect
解决导航抽屉开关触发子组件无关API调用的问题
问题根源
你的Layout.tsx中将包含<Outlet/>的主内容区域包裹在了<Drawer>组件内部。当open状态变化时,Material-UI的Drawer组件(即使设置了variant="permanent")会触发内部DOM结构的更新,导致<Outlet/>中的子组件(如ListBranches)被卸载后重新挂载,从而触发了仅在挂载阶段执行的空依赖useEffect,重复调用无关API。
解决方案
方案1:调整DOM结构(推荐)
将主内容区域(包含<Outlet/>的<Box>)移到<Drawer>组件外部,让抽屉的状态变化不会影响主内容组件的挂载状态。
修改后的Layout.tsx代码:
import { Box, Drawer, DrawerHeader, Typography, IconButton, Divider, List } from '@mui/material'; import { ChevronRightIcon, MenuIcon } from '@mui/icons-material'; import { Outlet } from 'react-router-dom'; // 外层用flex容器承载抽屉和主内容 <Box sx={{ display: 'flex' }}> <Drawer variant="permanent" open={open} ModalProps={{ keepMounted: true }}> <DrawerHeader sx={{ display: "flex" }}> <Typography variant="h6" component="span" sx={{ flexGrow: 1 }}> Logo </Typography> <IconButton onClick={handleDrawerClose}> {theme.direction === 'rtl' ? <ChevronRightIcon/> : <MenuIcon/>} </IconButton> </DrawerHeader> <Divider/> <List> {/* 侧边栏菜单项 */} </List> </Drawer> <Box component="main" sx={{ flexGrow: 1, paddingLeft: '25px', paddingRight: '25px' }}> <DrawerHeader/> <Outlet/> </Box> </Box>
方案2:给子组件添加稳定Key
如果无法调整DOM结构,给ListBranches组件设置固定的key,确保父组件重新渲染时不会卸载重建该组件。
在路由配置中修改:
// 路由配置示例 import { Route } from 'react-router-dom'; import ListBranches from './ListBranches'; <Route path="/branches" element={<ListBranches key="branches"/>} />
方案3:使用useRef控制API调用次数(兜底)
如果前两种方案都无法实施,在ListBranches组件中用useRef标记是否已经执行过API调用,避免重复触发:
修改后的ListBranches.tsx代码片段:
import { useState, useEffect, useRef } from 'react'; function ListBranches(props: IListBranchesProps) { const [branches, setBranches] = useState([]); const [loading, setLoading] = useState(false); const hasInitiatedFetch = useRef(false); // 标记是否已执行过API调用 useEffect(() => { if (!hasInitiatedFetch.current) { refreshBranches(); hasInitiatedFetch.current = true; } }, []) // ... 其他代码 }
内容的提问来源于stack exchange,提问作者amstech
相关产品推荐
相关产品推荐

