使用 React 函数组件保护路由
受保护的路由使我们能够确保只有登录用户才能访问我们网站中可能包含私人用户信息的特定部分。在本文中,我们将介绍一种在 React 中使用函数组件和react-router实现受保护路由的方法。我们将首先使用useState钩子来实现这一点,但在后续文章中,我们还将了解如何使用 React 的 Context API 来实现这一点。Context API 是一个内置解决方案,它允许嵌套子组件访问应用程序的状态,而无需将 props 传递到组件树的每一个节点,这种做法通常被称为 prop 钻取。
入门
让我们从创建一个新的 React 项目开始。我将使用Create-React-App实用程序自动生成一个基准 React 应用,而无需编写任何样板配置。
我使用的是yarn,但如果你愿意,也可以使用 npx。我们在终端中运行以下命令:
yarn create react-app protected-routes
进入新创建的protected-routes文件夹,运行yarn start(或npm start)来启动开发服务器。它应该会打开浏览器并显示 React 的 logo。
React 路由器
现在我们已经有了一个基本的 React 应用,让我们在src目录中创建一个名为components的新目录。我们将在此目录中创建两个组件:Landing.js 和 Dashboard.js。
受保护的路线/src/组件/Landing.js
import React from 'react';
import { Link } from 'react-router-dom';
const Landing = () => {
return (
<div>
<h1>Landing</h1>
<p><Link to='/dashboard'>View Dashboard</Link></p>
<button>Log In</button>
</div>
)
};
export default Landing;
请注意,我们使用<Link>
来自 react-router 的组件而不是锚标签,这样当用户点击链接时我们的应用程序就不会重新加载页面。
受保护的路线/src/组件/Dashboard.js
import React from 'react';
const Dashboard = () => {
return (
<div>
<h1>Dashboard</h1>
<p>Secret Page</p>
<button>Log Out</button>
</div>
)
};
export default Dashboard;
我们希望根据当前所在的路由来渲染每个组件。为此,我们需要安装react-router-dom。
yarn add react-router-dom
打开src目录下的App.js文件,并在 CSS 导入后立即从我们新安装的包中导入以下组件。注意,为了简洁起见,我将BrowserRouter命名为Router。我们再导入之前创建的两个组件。
受保护的路线/src/App.js
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Landing from './components/Landing';
import Dashboard from './components/Dashboard';
我们可以删除 return 语句中除顶层之外的所有常用 HTML <div>
,并将其替换为我们自己的代码。我们将使用刚刚导入的Route组件来指定哪个组件对应哪个路由。最后,我们需要用我们的组件(又名BrowserRouter<Route>
)包装这些组件,将它们连接在一起。我们的 App.js 文件应该如下所示:<Router>
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Landing from './components/Landing';
import Dashboard from './components/Dashboard';
function App() {
return (
<div className="App">
<Router>
<Route exact path='/' component={Landing} />
<Route exact path='/dashboard' component={Dashboard} />
</Router>
</div>
);
}
export default App;
现在,当我们在浏览器中访问localhost:3000 时,我们应该会看到一个相当简单的页面,标题为“Landing”,一个指向/dashboard 的链接,以及一个“Log In”按钮(目前还没有任何作用)。点击链接,我们会看到页面现在渲染了 Dashboard 组件,因为我们的路由现在已更改为localhost:3000/dashboard。
使用 react-router,无需编写额外的条件渲染逻辑或利用状态来跟踪我们应该显示哪个组件。很酷吧?😎。但我们仍然有一个问题:我们的秘密 Dashboard 页面任何人都可以访问。我们如何才能只允许有权查看该页面的人员导航到该页面?首先,我们需要跟踪用户是否登录。让我们看看如何使用 useState 钩子来实现这一点。
useState 钩子
在 React 16.8 版本引入 hooks 之前,在 React 中创建有状态组件的唯一方式是通过类。顾名思义,useState hook允许我们在函数组件内部使用状态。让我们实现useState来跟踪我们的登录状态。
在App.js中,在导入 React 的同一行中使用解构导入useState 。
受保护的路线/src/App.js
import React, { useState } from 'react';
接下来,在 App 函数内部,在 return 块之前,使用数组解构创建一个user 变量和一个 setUser变量,它们分别是 useState 返回给我们的第一个和第二个元素。我们将传入一个 false 的初始状态,以指示我们首次访问该页面时尚未登录。
我们还将创建一个名为handleLogin的函数,当我们点击“登录”时,它将调用setUser并将用户值翻转为true 。
function App() {
const [user, setUser] = useState(false);
const handleLogin = e => {
e.preventDefault();
setUser(true);
}
我们需要将这个handleLogin函数传递给Landing组件,但由于我们将Landing作为组件属性传递给 Route,因此它无法在当前设置下工作。我们需要将组件属性更改为render ,并将其作为返回Landing组件的函数传递。相信我,这听起来可能比实际操作更令人困惑,但如果您想了解更多信息,请随时查看这篇文章。
我们的 App.js 应该是这样的:
受保护的路线/src/App.js
import React, { useState } from 'react';
import './App.css';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Landing from './components/Landing';
import Dashboard from './components/Dashboard';
function App() {
const [user, setUser] = useState(false)
const handleLogin = e => {
e.preventDefault();
setUser(true);
}
return (
<div className="App">
<Router>
<Route exact path='/' handleLogin={handleLogin} render={props => <Landing {...props} user={user.toString()} handleLogin={handleLogin} />} />
<Route exact path='/dashboard' component={Dashboard} />
</Router>
</div>
);
}
export default App;
注意,我将用户信息作为字符串传入,以便我们可以在 Landing 组件中显示它。如果您安装了 React Developer Tools Chrome 扩展程序,则可以使用它来检查应用的状态并确保一切正常运行。
让我们使用刚刚作为 prop 传递的handleLogin函数,为Landing组件中的按钮添加一个 onClick 处理程序。记得将props作为参数传递,以便在组件内部访问它。
受保护的路线/src/组件/Landing.js
import React from 'react';
import { Link } from 'react-router-dom';
const Landing = props => {
return (
<div>
<h1>Landing</h1>
<p><Link to='/dashboard'>View Dashboard</Link></p>
<p>Logged in status: {props.user}</p>
<button onClick={props.handleLogin}>Log In</button>
</div>
)
};
export default Landing;
现在,我们应该能够点击登录按钮,并看到状态变为true。这就是我们正在切换的状态。
太好了,我们已经登录了。现在先不用管在Dashboard.js中设置“注销”按钮,我们会在下一节中完成。
现在我们需要一种方法,让用户只有登录状态为true时才能访问 Dashboard 组件。该如何实现呢?那就使用受保护的路由组件吧。
受保护的路线
我们将在components目录中创建一个名为ProtectedRoute.js的新文件。从高层次来看,这个组件将充当 react-router 的Route组件的包装器,最终返回我们希望渲染的组件。换句话说,我们通过一个中介来传递我们想要渲染的组件,该中介抽象了跟踪组件内部状态的需求。在我们的例子中,它变成了一个高阶组件。它将负责在渲染组件之前检查我们是否已登录,否则它会将用户重定向到另一个页面(我们稍后会创建)。<Dashboard>
<ProtectedRoute>
<Dashboard>
受保护的路线/src/组件/ProtectedRoute.js
import React from 'react';
import { Route } from 'react-router-dom';
const ProtectedRoute = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={
props => <Component {...rest} {...props} />
} />
)
}
export default ProtectedRoute;
ProtectedRoute 接收我们之前传入 Route 组件的所有 props,并使用render prop 返回相同的 Route 组件。让我们逐行分析每一行:
const ProtectedRoute = ({ component: Component, ...rest }) => {
- 这里我们传入一个对象,它包含了
<ProtectedRoute>
从App.js调用组件时需要传入的所有 props 。我们指定Component是为了稍后在渲染 prop 中引用它。我们使用rest语法来传入其他 props,而无需逐一了解或列出它们。
return (<Route {...rest} render={ props => <Component {...rest} {...props} />
- 我们只是返回react-router的
<Route>
组件,并使用它的render prop 来渲染我们作为参数传入的组件。除了通常提供的默认 props 之外,我们还传入了之前的...rest props。<Route>
我们将看到如何在这里添加逻辑来检查我们是否已登录。首先,让我们确保没有破坏任何东西。
打开 App.js,导入ProtectedRoute组件,并将Route替换为ProtectedRoute,其中我们指定了/dashboard路由。返回结果应该如下所示:
受保护的路线/src/App.js
return (
<div className="App">
<Router>
<Route exact path='/' handleLogin={handleLogin} render={
props => <Landing {...props} user={user.toString()}
handleLogin={handleLogin} />} />
<ProtectedRoute exact path='/dashboard' component={Dashboard} />
</Router>
</div>
);
祈祷它能完全正常工作。现在,在将逻辑添加到 ProtectedRoute 之前,让我们返回并修复注销按钮。
在App.js中,创建一个handleLogout路由,它看起来与handleLogin路由完全相同,只是它将用户状态切换为 false。然后将其通过 prop 传递给 ProtectedRoute 组件。完整文件现在如下所示:
受保护的路线/src/App.js
import React, { useState } from 'react';
import './App.css';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Landing from './components/Landing';
import Dashboard from './components/Dashboard';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
const [user, setUser] = useState(false)
const handleLogin = e => {
e.preventDefault();
setUser(true);
}
const handleLogout = e => {
e.preventDefault();
setUser(false);
}
return (
<div className="App">
<Router>
<Route exact path='/' handleLogin={handleLogin} render={
props => <Landing {...props} user={user.toString()} handleLogin={handleLogin} />} />
<ProtectedRoute exact path='/dashboard' handleLogout={handleLogout} component={Dashboard} />
</Router>
</div>
);
}
export default App;
打开Dashboard.js并添加一个 onClick 处理程序,当我们点击“注销”按钮时,该处理程序将触发 handleLogout 函数。记得将props参数传递给 Dashboard 函数,之前括号里的内容是空的。
<button onClick={props.handleLogout}>Log Out</button>
现在我们的应用应该能够追踪登录状态了。点击每个按钮,然后使用“返回”按钮来查看实际效果:
重定向页面
让我们再创建一个组件,当用户在未登录的情况下尝试访问我们的/dashboard路由时,我们会将用户重定向到该组件。我们将从 codepen 复制用户@anjanas_dh制作的这个很酷的 403 页面,使这个组件看起来比我们黑白应用程序的其他部分更有趣一些。
在组件目录中,创建一个名为Unauthorized.js的文件并添加以下标记。
受保护的路线/src/Unauthorized.js
import React from 'react';
import { Link } from 'react-router-dom';
import '../Unauthorized.scss';
const Unauthorized = () => {
return (
<div className='container'>
<div class="gandalf">
<div class="fireball"></div>
<div class="skirt"></div>
<div class="sleeves"></div>
<div class="shoulders">
<div class="hand left"></div>
<div class="hand right"></div>
</div>
<div class="head">
<div class="hair"></div>
<div class="beard"></div>
</div>
</div>
<div class="message">
<h1>403 - You Shall Not Pass</h1>
<p>Uh oh, Gandalf is blocking the way!<br />Maybe you have a typo in the url? Or you meant to go to a different location? Like...Hobbiton?</p>
</div>
<p><Link to='/'>Back to Home</Link></p>
</div>
)
}
export default Unauthorized;
在src目录中创建一个名为Unauthorized.scss的新 SCSS 文件,并粘贴这些样式。由于该文件长达 270 行,因此我添加了 pastebin 链接,而不是代码本身。
由于这是一个 Sass 文件,它不能直接使用,但别担心!我们只需要安装node-sass模块,就可以开始我们的向导之旅了🧙♂️。
yarn add node-sass
打开App.js并导入Unauthorized组件并将其添加到我们的Route组件列表中。
import Unauthorized from './comoponents/Unauthorized';
/* omitting some of the other LOC to save space */
return (
<div className="App">
<Router>
<Route exact path='/' handleLogin={handleLogin} render={
props => <Landing {...props} user={user.toString()}
handleLogin={handleLogin} />} />
<ProtectedRoute exact path='/dashboard' handleLogout={handleLogout} component={Dashboard} />
<Route exact path='/unauthorized' component={Unauthorized} />
</Router>
</div>
);
/* omitting some of the other LOC to save space */
如果一切按计划进行,当您导航到“/unauthorized”时,您应该会看到以下页面
保护路线
好了,现在到了最后冲刺阶段!让我们添加一些逻辑,确保我们在查看 Dashboard 组件之前已经登录。首先,我们将用户状态作为 prop 传递给App.js中的ProtectedRoute。
受保护的路线/src/App.js
<ProtectedRoute exact path='/dashboard' user={user} handleLogout={handleLogout} component={Dashboard} />
回到ProtectedRoute.js,我们添加一个条件判断,检查用户状态是否为 true。如果为 true,则渲染组件;否则,重定向到/unauthorized路由。因此,我们还需要从react-router<Redirect>
中导入该组件。最终的ProtectedRoute组件应该如下所示:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const ProtectedRoute = ({ component: Component, user, ...rest }) => {
return (
<Route {...rest} render={
props => {
if (user) {
return <Component {...rest} {...props} />
} else {
return <Redirect to={
{
pathname: '/unauthorized',
state: {
from: props.location
}
}
} />
}
}
} />
)
}
export default ProtectedRoute;
请注意,我们现在将用户指定为传递给ProtectedRoute组件的 props 之一,因为我们稍后会在if语句中引用它来检查我们是否已“登录”。
- 如果用户评估为true,那么我们将正常渲染组件
- 但是,如果将其设置为false,我们将使用
<Redirect>
来自react-router的组件将用户重定向到'/unauthorized'。
好了,现在是关键时刻。我们先尝试在不“登录”的情况下访问“查看仪表盘”链接。我们应该会看到灰袍甘道夫。
现在,让我们点击登录按钮来模拟身份验证。状态变为true,当我们点击“查看仪表盘”链接时,我们的应用应该会渲染仪表盘组件。如果我们点击“注销”,则会立即跳转到“未授权”页面。
概括
我们初步了解了如何使用React-router和<ProtectedRoute>
高阶组件保护私有页面。我们利用useState钩子函数访问 state,并将其作为 props 传递给子组件。正如开篇所述,我们将在后续文章中探讨如何使用 Context API 来避免使用 props 参数。
如果你读到了最后,感谢你的阅读。我非常欢迎任何评论或建议,欢迎在下方留言。祝你一切顺利!
文章来源:https://dev.to/mychal/protected-routes-with-react-function-components-dh