React Router v6 私有路由实现
React Router 版本提示
本文针对 React Router v6 版本,如果您使用的是 v5 或更早版本,实现方式会有所不同。
问题描述
在使用 React Router v6 时,开发者尝试创建私有路由组件来保护需要认证的页面,但遇到了以下错误:
Error: [PrivateRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
原始代码示例:
// PrivateRoute.js
import React from 'react';
import { Route, Navigate } from "react-router-dom";
import { isauth } from 'auth'
function PrivateRoute({ element, path }) {
const authed = isauth() // 根据 localStorage 返回 true 或 false
const ele = authed === true ? element : <Navigate to="/Home" />;
return <Route path={path} element={ele} />;
}
export default PrivateRoute
// route.js
<PrivateRoute exact path="/" element={<Dashboard/>}/>
<Route exact path="/home" element={<Home/>}/>
问题根源
在 React Router v6 中,<Routes>
组件只能直接包含 <Route>
或 <React.Fragment>
作为子元素。自定义组件(如 PrivateRoute
)不能直接作为 <Routes>
的子元素使用。
解决方案
方案一:使用包装组件模式(推荐)
这是 React Router v6 官方推荐的认证模式:
// PrivateRoute.js
import { Navigate, useLocation } from "react-router-dom";
const PrivateRoute = ({ children }) => {
const isAuthenticated = isauth(); // 您的认证逻辑
const location = useLocation();
if (!isAuthenticated) {
// 重定向到登录页,同时保存试图访问的地址
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
};
export default PrivateRoute;
使用方式:
// App.js
<Routes>
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
<Route path="/login" element={<Login />} />
<Route path="/home" element={<Home />} />
</Routes>
方案二:使用 Outlet 组件
对于需要在布局中嵌套路由的情况,可以使用 <Outlet>
:
// PrivateRouteWithOutlet.js
import { Navigate, Outlet } from "react-router-dom";
const PrivateRouteWithOutlet = () => {
const isAuthenticated = isauth();
return isAuthenticated ? <Outlet /> : <Navigate to="/login" />;
};
export default PrivateRouteWithOutlet;
使用方式:
// App.js
<Routes>
<Route element={<PrivateRouteWithOutlet />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Route>
<Route path="/login" element={<Login />} />
</Routes>
方案三:函数式组件调用
通过函数调用而非 JSX 组件的方式:
// PrivateRoute.js
const PrivateRoute = ({ element, path }) => {
const authed = isauth();
const ele = authed ? element : <Navigate to="/Home" />;
return <Route path={path} element={ele} />;
};
export default PrivateRoute;
使用方式:
// route.js
<Route element={<MainLayout />}>
<Route index element={<HomePage />} />
<Route path="about" element={<AboutPage />} />
{PrivateRoute({ element: <SecurePage />, path: 'secure' })}
</Route>
方案四:集中式路由配置
对于大型应用,可以使用集中式路由管理:
// routes.js
export const publicRoutes = [
{
path: '/',
element: <Home />,
},
{
path: '/login',
element: <Login />,
}
];
export const privateRoutes = [
{
path: '/dashboard',
element: <Dashboard />,
},
{
path: '/profile',
element: <Profile />,
}
];
// AppRoutes.js
import { useRoutes } from 'react-router-dom';
import { publicRoutes, privateRoutes } from './routes';
import PrivateRoute from './PrivateRoute';
export function AppRoutes() {
const parseRouteObjects = (routes, isPrivate = false) => {
return routes.map(route => ({
path: route.path,
element: isPrivate ?
<PrivateRoute>{route.element}</PrivateRoute> :
route.element,
}));
};
const publicRouteObjects = parseRouteObjects(publicRoutes);
const privateRouteObjects = parseRouteObjects(privateRoutes, true);
const routes = useRoutes([...publicRouteObjects, ...privateRouteObjects]);
return routes;
}
最佳实践建议
- 认证状态管理:使用 React Context 或状态管理库(如 Redux)来管理全局认证状态
- 路由保护:保护路由时考虑用户角色和权限
- 用户体验:在重定向时保存原始目标地址,登录后能够返回原页面
- 加载状态:在处理认证时显示适当的加载指示器
// 使用 Context 管理认证状态
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 初始化认证状态检查
checkAuthStatus().then(user => {
setUser(user);
setLoading(false);
});
}, []);
if (loading) {
return <div>加载中...</div>;
}
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
};
常见问题解答
Q: 为什么不能像 v5 那样使用自定义路由组件?
A: React Router v6 引入了新的声明式 API,要求路由结构更加明确和严格,这提高了性能并减少了歧义。
Q: 如何处理嵌套的私有路由?
A: 使用 <Outlet>
组件可以轻松实现嵌套路由的认证保护,父路由的认证状态会自动传递给所有子路由。
Q: 如何实现基于角色的访问控制?
A: 可以扩展 PrivateRoute
组件,增加角色验证逻辑:
const RoleProtectedRoute = ({ children, requiredRole }) => {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" />;
}
if (user.role !== requiredRole) {
return <Navigate to="/unauthorized" />;
}
return children;
};
总结
React Router v6 通过强制使用标准 <Route>
组件作为 <Routes>
的直接子元素,提供了更加一致和高效的路由处理机制。虽然这改变了 v5 中创建自定义路由组件的方式,但新的模式提供了更好的性能和更清晰的代码结构。
使用包装组件模式或 <Outlet>
组件是实现私有路由的推荐方法,它们与 v6 的设计理念保持一致,同时提供了灵活性和强大的功能。
升级提示
如果您从 React Router v5 升级到 v6,请参阅官方升级指南以获取更多迁移建议。