Storybook中react-router自定义侧边栏渲染Route报错问题
React自定义侧边栏Storybook路由报错问题
问题背景
我是React初学者,近期尝试开发自定义侧边栏组件,参考教程完成代码实现,所用依赖版本:
- react-router-dom: ^6.3.0
- storybook: ^6.1.17
相关代码实现
sidebar.jsx
const SidebarNav = styled.nav` background: #15171c; width: 250px; height: 100vh; display: flex; justify-content: center; position: fixed; top: 0; right: ${({ sidebar }) => (sidebar ? "0" : "-100%")}; transition: 350ms; z-index: 10; `; const SidebarWrap = styled.div` width: 100%; `; const Sidebar = () => { const [sidebar, setSidebar] = useState(true); return ( <> <IconContext.Provider value={{ color: "#fff" }}> <SidebarNav sidebar={sidebar}> <SidebarWrap> {sideBarData.map((item, index) => { return <SubMenu item={item} key={index} />; })} </SidebarWrap> </SidebarNav> </IconContext.Provider> </> ); }; export default Sidebar;
sidebardata.jsx
export const sideBarData = [ { title: "Question 4", path: "/test" }, // { // title: "Question 5", // path: "/testing" // } ];
sidebarsubmenu.jsx
import React, { useState } from "react"; import { Link } from "react-router-dom"; import styled from "styled-components"; const SidebarLink = styled(Link)` display: flex; color: #e1e9fc; justify-content: space-between; align-items: center; padding: 20px; list-style: none; height: 60px; text-decoration: none; font-size: 18px; &:hover { background: #252831; border-left: 4px solid green; cursor: pointer; } `; const SidebarLabel = styled.span` margin-left: 16px; `; const DropdownLink = styled(Link)` background: #252831; height: 60px; padding-left: 3rem; display: flex; align-items: center; text-decoration: none; color: #f5f5f5; font-size: 18px; &:hover { background: green; cursor: pointer; } `; const SubMenu = ({ item }) => { const [subnav, setSubnav] = useState(false); const showSubnav = () => setSubnav(!subnav); return ( <> <SidebarLink to={item.path} onClick={item.subNav && showSubnav}> <div> {item.icon} <SidebarLabel>{item.title}</SidebarLabel> </div> <div> {item.subNav && subnav ? item.iconOpened : item.subNav ? item.iconClosed : null} </div> </SidebarLink> {subnav && item.subNav.map((item, index) => { return ( <DropdownLink to={item.path} key={index}> {item.icon} <SidebarLabel>{item.title}</SidebarLabel> </DropdownLink> ); })} </> ); }; export default SubMenu;
BookSideBarLayout.jsx
import React from 'react'; import Sidebar from './BookSideBar'; function Layout(props) { return ( <div> <div style={{display: "flex"}}> <Sidebar/> </div> </div> ); } export default Layout;
BookSidebarRoutes.jsx
import React from "react"; import {BrowserRouter, Route,Routes} from "react-router-dom"; import Support from "./Support"; import Layout from "./BookSideBarLayout"; function MyRoutes() { return ( <BrowserRouter> <Layout/> <Routes> {/* <Route path="/" element={<Sidebar/>}/> */} <Route path="/test" element={<Support/>}/> </Routes> </BrowserRouter> ); } export default MyRoutes;
Storybook故事文件
import { storiesOf } from '@storybook/react'; import StoryRouter from 'storybook-react-router'; import MyRoutes from '../../src/components/BookSidebarRoutes'; // export default { // title: 'Side Bar', // //decorators : [(Story) => (<MemoryRouter><Story/></MemoryRouter>)] // }; storiesOf('Params', module) .addDecorator(StoryRouter()) .add('params', () => ( <MyRoutes/> )); export const Default = () => <MyRoutes />;
报错信息
运行故事时持续抛出错误:A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.
尝试在MyRoutes组件中使用Switch组件调整,问题未解决。
问题原因
- 所用的
storybook-react-router库默认适配react-router-dom v5版本,和当前使用的v6版本路由上下文不兼容,无法正确识别v6的<Routes>组件逻辑,导致路由校验失败。 - 代码存在双重Router嵌套问题:
MyRoutes组件内部写了<BrowserRouter>,StoryRouter装饰器又额外包裹了一层路由容器,路由上下文混乱触发报错。 - react-router-dom v6已完全移除
Switch组件,v6下所有<Route>必须作为<Routes>的直接子元素,使用Switch无效。
修复步骤
- 移除不兼容v6的
storybook-react-router依赖,不需要额外安装路由相关storybook插件,直接使用react-router-dom v6自带的MemoryRouter作为故事装饰器即可。 - 把
MyRoutes组件内部硬编码的<BrowserRouter>移到外层,避免嵌套多个路由容器,路由容器根据使用场景区分:生产环境用BrowserRouter,Storybook/测试环境用MemoryRouter。
修正后代码
BookSidebarRoutes.jsx
import React from "react"; import { Route,Routes } from "react-router-dom"; import Support from "./Support"; import Layout from "./BookSideBarLayout"; function MyRoutes() { return ( <> <Layout/> <Routes> <Route path="/test" element={<Support/>}/> </Routes> </> ); } export default MyRoutes;
Storybook故事文件
import { MemoryRouter } from 'react-router-dom'; import MyRoutes from '../../src/components/BookSidebarRoutes'; export default { title: 'Side Bar', decorators : [ (Story) => ( <MemoryRouter initialEntries={['/test']}> <Story/> </MemoryRouter> ) ] }; export const Default = () => <MyRoutes />;
生产环境根组件使用示例(App.jsx)
import { BrowserRouter } from 'react-router-dom'; import MyRoutes from './components/BookSidebarRoutes'; function App() { return ( <BrowserRouter> <MyRoutes /> </BrowserRouter> ) } export default App;
内容的提问来源于stack exchange,提问作者rohan arora
相关产品推荐
相关产品推荐

