【问题标题】:Protected route with react router v6使用反应路由器 v6 的受保护路由
【发布时间】:2020-10-04 15:20:22
【问题描述】:

用新版本 6 的 react-router 编写 ProtectedRoute 的正确方法是什么? 我写了这个,但这不是路线

const PrivateRoute = ({ component: Component, ...props }) => {   
  if (!Component) return null;

  return props.isAuthenticated
    ? <Component />
    : <Navigate to={props.redirectLink} /> }

export default PrivateRoute;

【问题讨论】:

  • 嗯,没有你说的这不是一条路线。我相信你的做法是正确的。 React-Router 中没有直接的东西。您需要编写这样的包装器。但是,我不确定您的代码中的 Navigate 是什么。
  • "如果您更喜欢使用声明式 API 进行导航(ala v5 的 Redirect 组件),v6 提供了 Navigate 组件。"你可以在这里找到更多信息github.com/ReactTraining/react-router/blob/dev/docs/…

标签: reactjs react-router react-router-dom


【解决方案1】:

这是我使用 useRoutes 实现私有路由的工作示例。

App.js

import routes from './routes';
import { useRoutes } from 'react-router-dom';

function App() {
  const { isLoggedIn } = useSelector((state) => state.auth);

  const routing = useRoutes(routes(isLoggedIn));

  return (
    <>
      {routing}
    </>
  );
}

routes.js

import { Navigate,Outlet } from 'react-router-dom';

const routes = (isLoggedIn) => [
  {
    path: '/app',
    element: isLoggedIn ? <DashboardLayout /> : <Navigate to="/login" />,
    children: [
      { path: '/dashboard', element: <Dashboard /> },
      { path: '/account', element: <Account /> },
      { path: '/', element: <Navigate to="/app/dashboard" /> },
      {
        path: 'member',
        element: <Outlet />,
        children: [
          { path: '/', element: <MemberGrid /> },
          { path: '/add', element: <AddMember /> },
        ],
      },
    ],
  },
  {
    path: '/',
    element: !isLoggedIn ? <MainLayout /> : <Navigate to="/app/dashboard" />,
    children: [
      { path: 'login', element: <Login /> },
      { path: '/', element: <Navigate to="/login" /> },
    ],
  },
];

export default routes;

【讨论】:

  • 这是我认为在这里唯一有意义的...
  • @fmsthird ...确定你是否已经在使用 redux。
  • 我得到了这个错误!错误:useRoutes() 只能在 组件的上下文中使用。我已经用路由器包裹了
  • 这个成功了。我正在寻找useRoutes() 实现。谢谢!
  • 这对我来说很有意义,谢谢
【解决方案2】:

这个例子来自react-router-dom:https://github.com/remix-run/react-router/blob/main/examples/auth/README.md

然后修改成这个https://stackblitz.com/edit/github-5kknft?file=src%2FApp.tsx

export default function App() {
  return (
    <AuthProvider>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<PublicPage />} />
          <Route path="/public" element={<PublicPage />} />
          <Route path="/login" element={<LoginPage />} />
          <Route element={<RequireAuth />}>
            <Route path="/protected" element={<ProtectedPage />} />
            <Route path="/dashboard" element={<Dashboard />} />
          </Route>
        </Route>
        <Route path="*" element={<NotFound />} />
      </Routes>
    </AuthProvider>
  );
}
function RequireAuth() {
  let auth = useAuth();
  let location = useLocation();

  if (!auth.user) {
    // Redirect them to the /login page, but save the current location they were
    // trying to go to when they were redirected. This allows us to send them
    // along to that page after they login, which is a nicer user experience
    // than dropping them off on the home page.
    return <Navigate to="/login" state={{ from: location }} />;
  }

  return <Outlet />;
}

【讨论】:

  • 这比用RequireAuth 包装每个element 道具的“官方”方式要好得多——使用这种方法有什么问题吗?或者它是否按预期工作?跨度>
【解决方案3】:

这是来自React Router documentation 的官方指南。

与其为 &lt;Route&gt; 元素创建包装器以获得所需的功能,不如在 &lt;Route element&gt; 属性中完成所有自己的组合。

以上面的例子为例,如果你想在 React Router v6 中保护某些路由免受未经身份验证的用户的攻击,你可以这样做:

import { Routes, Route, Navigate } from "react-router-dom";

function App() {
  return (
    <Routes>
      <Route path="/public" element={<PublicPage />} />
      <Route
        path="/protected"
        element={
          // Good! Do your composition here instead of wrapping <Route>.
          // This is really just inverting the wrapping, but it's a lot
          // more clear which components expect which props.
          <RequireAuth redirectTo="/login">
            <ProtectedPage />
          </RequireAuth>
        }
      />
    </Routes>
  );
}

function RequireAuth({ children, redirectTo }) {
  let isAuthenticated = getAuth();
  return isAuthenticated ? children : <Navigate to={redirectTo} />;
}

请注意,在此示例中,RequireAuth 组件不期望任何&lt;Route&gt; 的道具。这是因为它不想表现得像&lt;Route&gt;。相反,它只是在 &lt;Route&gt; 中呈现。

【讨论】:

    【解决方案4】:

    所有不错的选择。您还可以根据身份验证状态(或任何其他状态)简单地呈现不同的路由处理。您不必使用原始 Javascript 对象方法。

    不要忘记,您可以使用立即调用的匿名内部函数 (() =&gt; COMPONENT)() 来动态决定哪个组件处理特定的 &lt;Route/&gt;

    这些示例可能尚未包含在 v6 的初步文档中,因为处理私有 &lt;Route/&gt;s 实际上非常简单。

    例如

    <Routes>
          {state.authed ?
            // Wait until we have the current user...
            currentUser ?
              <Route
                path='/'
                element={(() => {
                  // Show a "no access" message if the user is NOT an App Admin doesn't have access to any schools at all (which includes not having access to anything INSIDE any school either)
                  if (!currentUser.appAdministrator && currentUser.schoolIds?.length === 0) return <AdminNoAccess />
                  return <Outlet />
                })()}
              >
                <Route
                  path='/'
                  element={(() => {
                    // If the user is a super user, we return the <SuperAdmin /> component, which renders some of its own routes/nav.
                    if (currentUser.appAdministrator) return <SuperAdmin />
                    return <Outlet />
                  })()}
                >
                  <Route
                    path='schools'
                    element={(() => {
                      if (currentUser.schoolIds?.length === 1) {
                        return <Navigate to={`schools/schoolId`} />
                      } else {
                        return <AdminSchools />
                      }
                    })()}
                  />
    
                  <Route path='users' children={<Users />} />
                </Route>
    
                <Route path={`schools/:schoolId`} element={<AdminSchool />} />
    
                <Route path='*' element={<Navigate to='schools' />} />
              </Route>
              :
              null
            :
            <>
              <Route path='login' element={<Login />} />
              <Route path='signup' element={<Signup />} />
              <Route path='forgot-password' element={<ForgotPassword />} />
              <Route path='reset-password' element={<ResetPassword />} />
    
              <Route path='*' element={<Navigate to='login' />} />
            </>
          }
        </Routes>
    

    【讨论】:

      【解决方案5】:

      您需要编写一个小型包装器并使用Navigate 组件进行重定向。您还需要渲染路线

      const Container = ({Component, redirectLink, isAuthenticated, ...props}) => {
        if(!isAuthenticated) {
             return <Navigate to={redirectLink} />;
         }
         
         return <Component {...props} />
      }
      const PrivateRoute = ({ component: Component, redirectLink, isAuthenticated, path, ...props }) => {   
      
        return (
          <Route
              path={path}
              element={<Container redirectLink={redirectLink} isAuthenticate={isAuthenticated} Component={Component} />}
          />
      )
      
      export default PrivateRoute;
      

      您可以在the github docs 上找到迁移指南

      【讨论】:

      • warning.js:24 你应该在 useEffect 中调用 navigate(),而不是在组件第一次渲染时调用。
      • 更新解决方案
      【解决方案6】:

      这是一个工作示例。

      import React from 'react';
      import { Route, Navigate } from 'react-router-dom';
      
      const PrivateRoute = ({ component: Component, redirectTo, isAuth, path, ...props }) => {
          if(!isAuth) {
              return <Navigate to={redirectTo} />;
          }
          return <Route path={path} element={<Component />} />
      };
      
      export default PrivateRoute;
      

      用法:

      <Routes>
           <Route path="app" element={<DashboardLayout />}>
               <PrivateRoute isAuth={true} path="account" component={AccountView}  redirectTo='/login'/>
           </Route>
       </Routes>
      

      【讨论】:

      • 这不再适用于反应路由器 v6.0.2
      【解决方案7】:

      这是我最新的 react-router v6 beta 工作实现。我不知道如何使用 useRoutes 实现受保护的路由。他们的文档应该添加一个关于如何以两种方式实现受保护/私有路由的示例。

      ProtectedRoute 组件

      import React from 'react';
      import PropTypes from 'prop-types';
      import { Route } from 'react-router-dom';
      import Forbidden from '../../views/errors/Forbidden';
      import { useAuth } from '../../contexts/AuthContext';
      
      const ProtectedRoute = ({ roles, element, children, ...rest }) => {
        const { user, login } = useAuth();
      
        if (!user) {
          login();
          return <></>;
        }
      
        if (roles.length > 0) {
          const routeRoles = roles.map((role) => role.toLowerCase());
          const userRoles = (user && user.roles ? user.roles : []).map((role) => role.toLowerCase());
          if (miscUtils.intersection(routeRoles, userRoles).length === 0) {
            return <Forbidden />;
          }
        }
      
        return (
          <Route element={element} {...rest}>
            {children}
          </Route>
        );
      };
      
      ProtectedRoute.propTypes = {
        roles: PropTypes.arrayOf(PropTypes.string),
        element: PropTypes.element,
        children: PropTypes.node,
      };
      
      ProtectedRoute.defaultProps = {
        roles: [],
        element: null,
        children: null,
      };
      
      export default ProtectedRoute;
      

      AppRoutes 组件

      import React from 'react';
      import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
      import Login from './components/oauth/Login';
      import Logout from './components/oauth/Logout';
      import RenewToken from './components/oauth/RenewToken';
      import ProtectedRoute from './components/ProtectedRoute';
      import NotFound from './views/errors/NotFound';
      import Index from './views/Index';
      import MainContainer from './views/MainContainer';
      import ViewUserProfile from './views/user/profile/ViewUserProfile';
      import CreateUserProfile from './views/user/profile/CreateUserProfile';
      import UpdateUserProfile from './views/user/profile/UpdateUserProfile';
      import PartnerProfile from './views/partner/profile/PartnerProfile';
      
      const AppRoutes = () => {
        return (
          <Routes>
            {/* auth pages (important: do not place under /auth path) */}
            <Route path="oauth/login" element={<Login />} />
            <Route path="oauth/logout" element={<Logout />} />
            <Route path="oauth/renew" element={<RenewToken />} />
            <Route element={<MainContainer />}>
              <Route path="/" element={<Index />} />
      
              {/* protected routes */}
              <ProtectedRoute path="user" element={<Outlet />}>
                <Route path="/" element={<Navigate to="profile" replace />} />
      
                <Route path="profile" element={<Outlet />}>
                  <Route path="/" element={<ViewUserProfile />} />
                  <Route path="create" element={<CreateUserProfile />} />
                  <Route path="update" element={<UpdateUserProfile />} />
                </Route>
              </ProtectedRoute>
      
              <ProtectedRoute path="partner" roles={['partner']} element={<Outlet />}>
                <Route path="/" element={<Navigate to="profile" replace />} />
                <Route path="profile" element={<PartnerProfile />} />
              </ProtectedRoute>
            </Route>
            <Route path="*" element={<NotFound />} />
          </Routes>
        );
      };
      
      export default AppRoutes;
      

      【讨论】:

      • 这是检查用户角色的更好解决方案。我希望我可以投票两次:D
      【解决方案8】:

      这是一个对 TypeScript 更友好的实现,它重用了 react-router v6 中的 RouteProps

      import React from 'react';
      import { RouteProps } from 'react-router';
      import { Route, Navigate } from 'react-router-dom';
      import { useAuthState } from '../../contexts';
      
      export interface PrivateRouteProps extends RouteProps {
        redirectPath: string;
      }
      
      export const PrivateRoute = ({ redirectPath, ...props }: PrivateRouteProps) => {
        const { user } = useAuthState();
        if (!user) {
          return <Navigate to={redirectPath} />;
        }
        return <Route {...props} />;
      };
      

      useAuthState 是一个钩子,可以在用户登录时检索用户。

      我就是这样使用它的:

      <Routes>
        <Route path="/" element={<Home />} />
        <PrivateRoute path="/admin" redirectPath="/signin" element={<Admin />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
      

      【讨论】:

      • 你使用 Firebase 吗?
      【解决方案9】:

      我不知道是否有人回答了这个问题,但我是这样做的。

      import {Navigate, Route} from "react-router-dom";
      
      <Route path="*" element={<Navigate to={"/register"} />} />
      

      【讨论】:

      • 保护在哪里?
      【解决方案10】:

      这是 BrowserRouter 作为路由器的结构:

      const AppRouter = () => {
        return (
          <Router>
            <Layout>
              <Routes>
                <Route exact path="" element={<Home />} />
                <Route exact path="login" element={<Login />} />
                <Route exact path="register" element={<Register />} />
      
                // These are the Private Components
                <Route
                  exact
                  path="/account"
                  element={
                    <PrivateRoute>
                      <Account />
                    </PrivateRoute>
                  }
                />
      
                <Route
                  exact
                  path="/quizzes"
                  element={
                    <PrivateRoute>
                      <Quizzes />
                    </PrivateRoute>
                  }
                />
      
                <Route
                  exact
                  path="/quizz/:quizzid"
                  element={
                    <PrivateRoute>
                      <Quizz />
                    </PrivateRoute>
                  }
                />
      
                <Route
                  exact
                  path="/admin/users"
                  element={
                    <PrivateRoute>
                      <Users />
                    </PrivateRoute>
                  }
                />
                <Route exact path="*" element={<NotFound />} />
              </Routes>
            </Layout>
          </Router>
        );
      };
      

      这是 PrivateRoute:

      import { Navigate } from "react-router-dom";
      import { useAuth } from "../auth/useAuth";
      
      function PrivateRoute({ children }) {
        const auth = useAuth();
        return auth.user ? children : <Navigate to="/login" />;
      }
      
      export default PrivateRoute;
      

      【讨论】:

        【解决方案11】:

        您可以使用auth-react-routerhttps://www.npmjs.com/package/auth-react-router

        它提供了一个非常简单的 API 来定义您的路由和更多配置(例如授权和未授权路由的重定向路由,每个路由的后备组件)

        用法:

        1. 定义路线
        // routes.tsx
        
        import React from 'react';
        import { IRoutesConfig } from 'auth-react-router';
        import LoginPage from '../pages/LoginPage.tsx';
        
        // public lazy loaded pages
        const LazyPublicPage = React.lazy(() => import('../pages/PublicPage.tsx'));
        
        // private lazy loaded pages
        const LazyPrivatePage = React.lazy(() => import('../pages/PrivatePage.tsx'));
        const LazyProfilePage = React.lazy(() => import('../pages/ProfilePage.tsx'));
        
        
        export const routes: IRoutesConfig = {
          publicRedirectRoute: '/profile', // redirect to `/profile` when authorized is trying to access public routes
          privateRedirectRoute: '/login', // redirect to `/login` when unauthorized user access a private route
          defaultFallback: <MyCustomSpinner />,
          public: [
            {
              path: '/public',
              component: <LazyPublicPage />,
            },
            {
              path: '/login',
              component: <LoginPage />,
            },
          ],
          private: [
            {
              path: '/private',
              component: <LazyPrivatePage />,
            },
            {
              path: '/profile',
              component: <LazyProfilePage />
            },
          ],
          common: [
            {
              path: '/',
              component: <p>common</p>,
            },
            {
              path: '*',
              component: <p>page not found 404</p>,
            },
          ],
        };
        
        1. 将它们链接到您的应用程序
        import { AppRouter, Routes } from 'auth-react-router';
        import { BrowserRouter } from 'react-router-dom';
        import { routes } from './routes';
        
        export const App = () => {
          const { isAuth } = useAuthProvider();
          return (
            <BrowserRouter>
              <AppRouter isAuth={isAuth} routes={routes}>
                {/* Wrap `Routes` component into a Layout component or add Header */}
                <Routes />
              </AppRouter>
            </BrowserRouter>
          );
        };
        

        【讨论】:

          猜你喜欢
          • 2021-09-28
          • 2022-07-30
          • 2021-10-27
          • 1970-01-01
          • 2021-10-17
          • 2021-03-29
          • 1970-01-01
          • 2021-11-03
          • 2022-11-16
          相关资源
          最近更新 更多