如何在独立NPM组件中定义React Router v6路由并集成?
问题场景
我们的应用拥有自定义路由与组件(如Home、About Us等),同时引入了名为Basket的内部NPM包。该Basket组件包含多步流程(如用户信息填写、支付等),计划使用React Router v6处理各步骤间的导航。
查阅文档与社区资料后,标准实现方式如下:
<BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="/basket"> <Route path="/user-details" element={<PaymentDetails />} /> </Route> </Routes> </BrowserRouter>
但由于Basket需在多个应用中复用,且维护仅需执行npm install package@latest,无法采用上述写法。
问题
以下写法无法运行并报错,该如何实现需求?
[Basket] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
错误写法示例:
<BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Basket countryCode="GB" /> </Routes> </BrowserRouter>
Basket组件代码:
export const Basket = ({ countryCode, }: BasketProps) => ( // 尝试过将下方代码作为Route或Fragment,均触发上述错误 <Route path="/basket"> <Route path="/mobile-capture" element={<UserDetails countryCode={countryCode} />} /> // ... 其他组件 </Route> );
补充说明
目前仅能通过以下方式运行,但存在诸多不妥(如多<Routes />写法无官方文档支持):
<BrowserRouter> <Routes> <Route path="/" element={<Home />} /> </Routes> <Routes> <Basket countryCode="GB" /> </Routes> </BrowserRouter>
对应的Basket组件:
export const Basket = ({ countryCode, }: BasketProps) => ( <Routes> <Route path="/user-details-capture" element={<UserDetails countryCode={countryCode} />} /> // ... 其他组件 </Routes> );
解决方案
React Router v6明确要求<Routes>的子节点必须是<Route>或<React.Fragment>,因此不能直接将Basket组件放在<Routes>内部。以下是几种符合官方规范的可复用实现方式:
方式1:让Basket导出路由配置数组
修改Basket组件,返回路由对象数组,主应用中直接展开使用:
// Basket包中导出路由配置 export const getBasketRoutes = (countryCode) => [ { path: "/basket", children: [ { path: "mobile-capture", element: <UserDetails countryCode={countryCode} /> }, // ... 其他路由配置 ] } ];
主应用中使用:
<BrowserRouter> <Routes> <Route path="/" element={<Home />} /> {/* 展开Basket的路由配置 */} {getBasketRoutes("GB").map((route, index) => ( <Route key={index} path={route.path}> {route.children.map((child, idx) => ( <Route key={idx} path={child.path} element={child.element} /> ))} </Route> ))} </Routes> </BrowserRouter>
方式2:用Outlet实现嵌套路由
将Basket作为父路由的布局组件,内部用<Outlet>渲染子路由,同时导出子路由配置供主应用注册:
// Basket包中代码 import { Outlet } from 'react-router-dom'; export const Basket = () => { return ( <div className="basket-layout"> {/* 这里可以放Basket的公共UI,比如步骤导航栏 */} <Outlet /> </div> ); }; export const basketChildRoutes = (countryCode) => [ { path: "mobile-capture", element: <UserDetails countryCode={countryCode} /> }, // ... 其他子路由 ];
主应用中使用:
import { Basket, basketChildRoutes } from 'your-basket-package'; <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="/basket" element={<Basket />}> {/* 注册Basket的子路由 */} {basketChildRoutes("GB").map((route, index) => ( <Route key={index} path={route.path} element={route.element} /> ))} </Route> </Routes> </BrowserRouter>
方式3:用createRoutesFromElements转换路由元素
在Basket中封装路由元素,通过createRoutesFromElements转换成路由对象,主应用中通过useRoutes使用:
// Basket包中代码 import { createRoutesFromElements, Route } from 'react-router-dom'; export const getBasketRouteElements = (countryCode) => createRoutesFromElements( <Route path="/basket"> <Route path="mobile-capture" element={<UserDetails countryCode={countryCode} />} /> {/* ... 其他路由 */} </Route> );
主应用中使用:
import { useRoutes } from 'react-router-dom'; function App() { const routes = useRoutes([ { path: "/", element: <Home /> }, ...getBasketRouteElements("GB"), ]); return routes; } <BrowserRouter> <App /> </BrowserRouter>
以上方式均符合React Router v6官方规范,既保证了Basket组件的可复用性,又避免了非标准写法带来的潜在问题,后续维护只需更新NPM包即可。
内容的提问来源于stack exchange,提问作者Damian

