You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的路由跳转异步调度发生冲突,导致部分场景下路由跳转被忽略。

针对性解决方案

  1. 将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>
    
  2. 简化嵌套层级,让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>
    
  3. 用useCallback稳定dispatch函数
    避免组件重渲染时生成新的toggleDrawer函数,减少不必要的状态更新触发:

    const toggleDrawer = useCallback(() => {
      drawerDispatch(toggle());
    }, [drawerDispatch]);
    
  4. 临时移除StrictMode验证
    开发环境下的StrictMode会触发双重渲染,可能加剧状态切换的冲突,可暂时移除<React.StrictMode>验证是否为该问题导致(生产环境StrictMode无此影响)。

内容的提问来源于stack exchange,提问作者kripa tura

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 23:25:36