出于学习目的,我正在尝试使用 React-Router v6 创建一个受保护的自定义路由。正如许多人在我之前在论坛或此处尝试过的那样,我收到此错误:Error: [ProtectedRoute] is not a component.
此外,如果有人正在为不可重用的实现而苦苦挣扎,可以在下面查看我的工作代码:我的问题是我真的希望它可以重用,这样我就可以避免使用这种混乱的代码。如果您有任何想法,请告诉我。
我个人认为这是不可能的,因为当前的 V6 实现。也许他们将来会让我们这样做。但我希望这篇文章无论如何都能帮助那些希望保护路由的人,尤其是在使用 Parse 平台时。
自定义路线:
const ProtectedRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={(props) => {
<AuthWrapper>
<Component {...rest} {...props} />
</AuthWrapper>;
}}
/>
);
};
如果用户当前未登录,包装器使用 Parse Server 来保护路由。
const AuthWrapper = ({ children }) => {
if (Parse.User.current() !== null) {
let isAuthenticated = Parse.User.current().getSessionToken();
let authCondition = isAuthenticated !== undefined;
if (authCondition) {
return children;
}
}
return <Navigate to={"/login"} />;
};
export default AuthWrapper;
没有自定义路线的工作解决方案:(没有
<ProtectedRoute />
的ofc)
<Route
path="/book"
element={
<AuthWrapper>
<BookPage />
</AuthWrapper>
}
/>
我想做的是:
<ProtectedRoute
path={'/path'}
element={<Anything/>}
/>
react-router-dom
v6 不,而且我不怀疑它会支持自定义路由组件,就像以前版本中使用的那样,现在更喜欢组合。您已经正确地创建了一个包含一些内容的 AuthWrapper
组件,这是文档中的 v6 身份验证示例。
但它可以改进。您可以返回一个
children
来渲染嵌套的 Outlet
组件,而不是返回单个 Route
节点。这会将 AuthWrapper
组件从包装器组件转换为布局组件。
import { Navigate, Outlet } from 'react-router-dom';
const AuthLayout = () => {
if (Parse.User.current() !== null) {
const isAuthenticated = Parse.User.current().getSessionToken();
return isAuthenticated ? <Outlet /> : null; // or loading indicator, etc...
}
return <Navigate to={"/login"} replace />;
};
这允许您将单个
AuthLayout
组件渲染到 Route
中,并在其中嵌套任意数量的受保护路由。
<Route element={<AuthLayout />}>
<Route path="/book" element={<BookPage />} />
... other protected routes ...
</Route>
我认为这会解决它。你错过了渲染道具中的回报。
const ProtectedRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={(props) => {
return (<AuthWrapper>
<Component {...rest} {...props} />
</AuthWrapper>);
}}
/>
);
};
我通过做两件事做到了
所以,这是我的代码
// Overwrite is my custom type in order to override any type i need
type TExtendedRouteObject = Overwrite<
RouteObject,
{
children?: TExtendedRouteObject[];
}
> & {
/**
* In order to set to document.title
*/
title: string;
};
const mapExtendedRoutes = (routeObjects: TExtendedRouteObject[]): RouteObject[] => {
if (routeObjects.length) {
return routeObjects.map(route => {
if (route.children?.length) {
return omit({
...route,
element: <RouteWrapper {...route} />,
children: mapExtendedRoutes(route.children),
},
['title'],
) as RouteObject;
}
return omit({ // omit from lodash.omit
...route,
element: <RouteWrapper {...route} />,
}) as RouteObject;
});
}
return [];
};
const RouteWrapper: FC<TExtendedRouteObject> = ({ element, title }) => {
useEffect(() => {
document.title = title;
}, [title]);
// your auth logic goes here
return <>{element}</>;
};
const extendedRoutes: TExtendedRouteObject[] = [
{
path: '/',
title: 'Homepage',
element: <Dashboard />,
children: [...],
},
...
];
const routes: RouteObject[] = mapExtendedRoutes(extendedRoutes);
const router = createBrowserRouter(routes);
// then pass it to <RouterProvider router={router} />