错误:[PrivateRoute]不是<Route>组件。 <Routes>的所有组件子元素必须是<Route>或<React.Fragment>。

197

我正在使用React Router v6为我的应用程序创建私有路由。

在文件PrivateRoute.js中,我有以下代码:

import React from 'react';
import {Route,Navigate} from "react-router-dom";
import {isauth}  from 'auth'

function PrivateRoute({ element, path }) {
  const authed = isauth() // isauth() returns true or false based on localStorage
  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 Auth Example - StackBlitz,文件App.tsx

是否有什么我漏掉的东西?

19个回答

217

今天我遇到了同样的问题,根据这篇非常有帮助的文章,我想出了以下解决方案。作者是Andrew Luca

在PrivateRoute.js文件中:

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

const PrivateRoute = () => {
    const auth = null; // determine if authorized, from context or however you're doing it

    // If authorized, return an outlet that will render child elements
    // If not, return element that will navigate to login page
    return auth ? <Outlet /> : <Navigate to="/login" />;
}
在 App.js 文件中(我留下了一些其他页面作为示例):
import './App.css';
import React, {Fragment} from 'react';
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom';
import Navbar from './components/layout/Navbar';
import Home from './components/pages/Home';
import Register from './components/auth/Register'
import Login from './components/auth/Login';
import PrivateRoute from './components/routing/PrivateRoute';

const App = () => {
  return (
    <Router>
      <Fragment>
        <Navbar/>
        <Routes>
          <Route exact path='/' element={<PrivateRoute/>}>
            <Route exact path='/' element={<Home/>}/>
          </Route>
          <Route exact path='/register' element={<Register/>}/>
          <Route exact path='/login' element={<Login/>}/>
        </Routes>
      </Fragment>
    </Router>
    
  );
}
在上述路由中,这是私有路由:
<Route exact path='/' element={<PrivateRoute/>}>
      <Route exact path='/' element={<Home/>}/>
</Route>

如果授权成功,<element> 元素将显示。否则,将导航到登录页面。


3
啊,我读了那篇博客,现在更明白了,将<Route element={<Home/>} />直接渲染为私有出口的子元素是更好的选择。现在这个方案更加吸引人了。我可以看到在某些使用情况下的好处。 - Drew Reese
20
如果使用最新版本,则不需要exact属性。 - Suroor Ahmmad
1
@DrewReese 是否有一种内置的身份验证组件?我不是React专家,所以很想看看其他解决方案。在我的理解中,此示例中的PrivateRoute组件充当身份验证组件。 - Dallin Romney
1
@DrewReese 刚刚意识到您谈论的是路由内部的附加路线。如果主页是唯一需要授权的页面,则可以不使用子路由。如果多个路由需要相同的身份验证,则将它们都放在同一个PrivateRoute中很方便。 - Dallin Romney
2
在React中,React本身并没有对认证实现提出明确的意见,但是任何使用的库都可以提供自己的认证容器/包装器。这就是吸引人的地方。 :) 这实际上是我在v5中使用的相同模式,我称之为“金门”路由,它是一个认证访问点,进入了一个“围墙花园”的路由,每个路由都不需要单独进行认证检查。 - Drew Reese
显示剩余4条评论

74

只有 Route 组件可以成为 Routes 的子组件。如果您遵循v6文档,则会看到身份验证模式是使用包装组件来处理身份验证检查和重定向。

function RequireAuth({ children }: { children: JSX.Element }) {
  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 children;
}

...

<Route
  path="/protected"
  element={
    <RequireAuth>
      <ProtectedPage />
    </RequireAuth>
  }
/>
旧的v5创建自定义Route组件的方式不再适用。使用您的代码/逻辑更新的v6模式可能如下所示:
const PrivateRoute = ({ children }) => {
  const authed = isauth() // isauth() returns true or false based on localStorage
  
  return authed ? children : <Navigate to="/Home" />;
}

并使用

<Route
  path="/dashboard"
  element={
    <PrivateRoute>
      <Dashboard />
    </PrivateRoute>
  }
/>

7
达林的答案很好,但老实说,与仅使用认证组件包装目标组件相比,它并没有为您节省太多东西。如果有什么区别的话,这是一个更复杂的解决方案,因为它现在涉及渲染两个 Route 组件 一个 Outlet,只为渲染单个路径。 - Drew Reese
2
谢谢,这是我的解决方案,它起作用了。 - Because i hate myself
@DrewReese,我认为Dallin的答案的优势在于当您需要保护许多路由时才会发挥作用。因此,仅有一个受保护的路由,是的,确实没有任何意义,但是如果有5或20个呢?它开始节省不需要大量冗余的包装组件。 - MikeyT
@MikeyT 完全同意,可以看一下他回答下面的评论。我的回答最初基本上是直接从官方文档中提取的,用于保护单个组件。现在,当处理这些类型的问题时,我通常会涵盖两种用例。 - Drew Reese
为什么要使用 <RequireAuth> 来包装单独的路由?而不是将整个 <Routes> 包装起来呢?这似乎是更好的选项,对吗? - DollarAkshay
1
@DollarAkshay “更好”是主观的,但是为了回答你的问题,也许它提供了更细粒度的控制。当我在这里回答这个问题时,这是RRD文档中使用的模式。也许我应该包含一个链接到他们的示例/沙盒。我更喜欢像Dallin的答案或我的其他答案这里中的“包装一组路由”的方法。 - Drew Reese

39

优化代码,提高可读性和美观度的补充措施。

这只是一条注释,但我没有足够的积分,所以我把它作为答案发布。

Dallin的回答可行,但Drew的回答更好!并且为了完善Drew的美学回答,我建议创建一个私有组件,该组件将组件作为props而不是children传递。

私有路由文件/组件的非常基本的示例:

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

const Private = (Component) => {
    const auth = false; //your logic

    return auth ? <Component /> : <Navigate to="/login" />
}

路由文件示例:

<Routes>
    <Route path="/home" element={<Home />} />
    <Route path="/user" element={<Private Component={User} />} />
</Routes>

11
我做到了,我只需将“const Private = (Component) =>”更改为“const Private = ({Component}) =>”,并使内容更加通俗易懂,但不改变原来的意思。 - Daniel Miranda
1
当您想要将props传递给“Component”时会发生什么?您可以像“element”属性一样使其接受JSX,即Component={<User myProp />},但您基本上会回到使用“element”属性的起点,并且最好只需使用“Private”组件包装<User myProp />。 “Route”的“element”JSX语法的整个重点是路由不需要关心向其呈现的组件传递任何内容。 - Drew Reese
@DrewReese 那么,我们应该使用这种方法还是你的方法? - Jack
@fredrick 当你需要将props传递给“Component”时,这个答案就会失效。你需要通过传递一个props对象来将其传递到“Component”,或者你可以直接传递<Component {...props} />,这基本上就回到了最初的“element”prop所做的事情。 - Drew Reese
我会非常感激的,因为这对我来说非常有用 :) 我希望你能发布一个精彩的例子。提前致谢。 - Jack
显示剩余3条评论

10
我知道这并不是制作PrivateRoute的准确步骤,但我想提一下新文档推荐在react-router v6中使用稍微不同的方法来处理此模式:
<Route path="/protected" element={<RequireAuth><ProtectedPage /></RequireAuth>} />

import { Navigate, useLocation } from "react-router";

export const RequireAuth: React.FC<{ children: JSX.Element }> = ({ children }) => {
  let auth = useAuth();
  let location = useLocation();

  if (!auth.user) {
    return <Navigate to="/login" state={{ from: location }} />;
  }

  return children;
};

如果需要,你可以在ProtectedPage内添加更多的路由。

有关详细信息,请参阅文档示例。此外,请查看迈克尔·杰克逊(Michael Jackson)的这个笔记,其中涉及一些实现细节。


7

现在是2023年,我做了下面这样的事情:

// routes.tsx

import { lazy } from "react";
import { Routes, Route } from "react-router-dom";
import Private from "./Private";
import Public from "./Public";

const Home = lazy(() => import("../pages/Home/Home"));
const Signin = lazy(() => import("../pages/Signin/Signin"));

export const Router = () => {
  return (
    <Routes>
      <Route path="/" element={Private(<Home />)} />
      <Route path="/signin" element={Public(<Signin />)} />
    </Routes>
  );
};

// Private.tsx

import { Navigate } from "react-router-dom";
import { useEffect, useState } from "react";

function render(c: JSX.Element) {
  return c;
}

const Private = (Component: JSX.Element) => {
  const [hasSession, setHasSession] = useState<boolean>(false);

  useEffect(() => {
    (async function () {
      const sessionStatus = await checkLoginSession();

      setHasSession(Boolean(sessionStatus));
    })();
  }, [hasSession, Component]);


  return hasSession ? render(Component) : <Navigate to="signin" />;
};

export default Private;


希望这能帮到你!

1
迄今为止最干净的之一,应该删除Public(...)函数,只需将组件用作元素。 - Facundo Colombier
2
你说得部分正确,如果我们想为已认证的用户提供重定向,那么就需要使用Public()渲染器。如果“A”用户已经登录并存在已认证的会话,那么让公共页面如“登录”或“注册”显示出来非常尴尬。 - filoscoder

3

从您的项目中移除PrivateRoute组件,并在App.js文件中使用以下代码:

import {Navigate} from "react-router-dom";
import {isauth}  from 'auth'

...

<Route exact path="/home" element={<Home/>}/>
<Route exact path="/" element={isauth ? <Dashboard/> : <Navigate to="/Home"  />}/>

2

我尝试了所有的答案,但它总是显示错误:

错误:[PrivateRoute]不是一个组件。必须是< Router >或< React.Fragment >的组件子级。

但是我找到了一个解决方案))) -

在 PrivateRoute.js 文件中:

import React from "react"; import { Navigate } from "react-router-dom";
import {isauth}  from 'auth'

const PrivateRoute = ({ children }) => {
  const authed = isauth()

  return authed ? children : <Navigate to={"/Home" /> };

export default ProtectedRoute;

route.js文件中:
<Route
  path="/"
  element={
    <ProtectedRoute >
      <Dashboard/>
    </ProtectedRoute>
  }
/>
<Route exact path="/home" element={<Home/>}/>

2
只需将您的路由器组件设置为 element 属性即可。
<Routes>
  <Route exact path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="/dashboard" element={<Dashboard />} />
</Routes>

您还可以检查从v5升级

10
这并没有回答问题,那身份验证呢? - Xavier Lambros

2
这是创建私有路由的简单方法:
import React from 'react'
import { Navigate } from 'react-router-dom'
import { useAuth } from '../../context/AuthContext'

export default function PrivateRoute({ children }) {
  const { currentUser } = useAuth()

  if (!currentUser) {
    return <Navigate to='/login' />
  }

  return children;
}

现在,如果我们想要向Dashboard组件添加私有路由,我们可以按照以下方式应用此私有路由:
<Routes>
  <Route exact path="/" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
</Routes>

2

Routes的子元素需要是Route元素,这样我们才能更改ProtectedRoute

export type ProtectedRouteProps = {
    isAuth: boolean;
    authPath: string;
    outlet: JSX.Element;
};

export default function ProtectedRoute({
    isAuth,
    authPath,
    outlet,
}: ProtectedRouteProps) {
    if (isAuth) {
        return outlet;
    } else {
        return <Navigate to={{pathname: authPath}} />;
    }
}

然后像这样使用它:
const defaultProps: Omit<ProtectedRouteProps, 'outlet'> = {
  isAuth: //check if user is authenticated,
  authPath: '/login',
};

return (
  <div>
    <Routes>
        <Route path="/" element={<ProtectedRoute {...defaultProps} outlet={<HomePage />} />} />
    </Routes>
  </div>
);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接