React Router与MUI Drawer、Redux Toolkit配合时路由切换异常问题
Material UI Drawer + React Router + Redux Toolkit 路由切换异常问题排查
使用Material UI的Drawer组件搭配React Router,同时用Redux Toolkit管理全局状态时,点击Drawer中的NavLink,路由切换表现不稳定——有时正常,有时需点击两次,有时完全无响应。已尝试调整元素onClick事件、检查Drawer切片与路由逻辑、给ListItem添加stopPropagation阻止事件冒泡,问题仍未解决。
Drawer.tsx
const navItems = [ { name: "Home", path: "/" }, { name: "Download", path: "/download" }, { name: "Question", path: "/questions" }, { name: "About", path: "/about" }, ]; export default function Drawer() { const isOpen = useSelector((state: RootState) => state.drawer.isOpened); const drawerDispatch = useDispatch(); return ( <MuiDrawer open={isOpen} onClose={() => drawerDispatch(toggleDrawer())}> <Box sx={{ width: 250 }} role="presentation"> <Typography variant="h6" component="div" sx={{ ml: 2, mt: 1, }} > Page </Typography> <List> {navItems.map((navItem) => ( <ListItem key={navItem.name} disablePadding onClick={() => drawerDispatch(toggleDrawer())} > <ListItemButton> <NavLink to={navItem.path}> <ListItemText primary={navItem.name} sx={{ color: "text.primary" }} /> </NavLink> </ListItemButton> </ListItem> ))} </List> </Box> </MuiDrawer> ); }
drawerSlice.ts
export interface DrawerState { isOpened: boolean; } const initialState: DrawerState = { isOpened: false, }; export const drawerSlice = createSlice({ name: "drawer", initialState, reducers: { toggle: (state) => { state.isOpened = !state.isOpened; }, }, });
main.tsx
ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <ReduxProvider store={store}> <BrowserRouter> <Routes> <Route path="/" element={<App />}> <Route index element={<HomePage />} /> <Route path="/download" element={<DownloadPage />} /> <Route path="/questions" element={<QuestionsPage />} /> <Route path="/about" element={<AboutPage />} /> </Route> </Routes> </BrowserRouter> </ReduxProvider> </React.StrictMode> );
可能的原因及解决方案
核心原因分析
- 事件冲突与执行顺序:ListItem上的
onClick会触发Drawer关闭,而NavLink的路由跳转事件可能被状态更新导致的组件重渲染中断。尤其是在StrictMode下,React的双重渲染机制会让toggleDrawer被重复执行,状态反复切换,干扰路由跳转逻辑。 - 嵌套点击元素的事件捕获问题:ListItemButton作为可点击元素包裹NavLink,点击事件可能先被ListItemButton捕获,而非直接传递给NavLink,导致路由跳转触发不稳定。
- 状态更新与路由跳转的时机冲突:Redux状态更新触发的组件重渲染,可能和React Router的路由跳转异步调度发生冲突,导致部分场景下路由跳转被忽略。
针对性解决方案
将Drawer关闭逻辑移至NavLink的点击事件
避免ListItem的onClick与NavLink的路由事件冲突,确保路由跳转优先执行后再关闭Drawer:<ListItem key={navItem.name} disablePadding> <ListItemButton> <NavLink to={navItem.path} onClick={() => drawerDispatch(toggle())} > <ListItemText primary={navItem.name} sx={{ color: "text.primary" }} /> </NavLink> </ListItemButton> </ListItem>简化嵌套层级,让ListItemButton直接作为NavLink
利用Material UI组件的component属性,将ListItemButton直接转为NavLink,减少事件传递的层级:<ListItem key={navItem.name} disablePadding> <ListItemButton component={NavLink} to={navItem.path} onClick={() => drawerDispatch(toggle())} sx={{ '&.active': { backgroundColor: 'rgba(0,0,0,0.04)' } }} > <ListItemText primary={navItem.name} sx={{ color: "text.primary" }} /> </ListItemButton> </ListItem>用
useCallback稳定dispatch函数
避免组件重渲染时生成新的toggleDrawer函数,减少不必要的状态更新触发:const toggleDrawer = useCallback(() => { drawerDispatch(toggle()); }, [drawerDispatch]);临时移除StrictMode验证
开发环境下的StrictMode会触发双重渲染,可能加剧状态切换的冲突,可暂时移除<React.StrictMode>验证是否为该问题导致(生产环境StrictMode无此影响)。
内容的提问来源于stack exchange,提问作者kripa tura
相关产品推荐
相关产品推荐

