这几天我正在学习 React,目前已经掌握了如何结合 Webpack 搭建出一个 React 前端开发环境,以及配合 React router 来实现基于前端的路由和页面 js 按需加载功能。为了能在实际项目中使用 React,我还需要解决最后一个问题:用户登录认证功能。
关于搭建 React 基础开发环境和使用 React router 的文章,可以通过下面的链接查看了解:
对于还不熟悉 React 的朋友,相信上面两篇文章对你入门 React 会有所帮助。本文是在以上两篇文章的基础之上继续的探索,所以如果你有兴趣想要尝试本文的内容,请先完成以上两篇文章中所记录的流程。
登录的用途
我准备使用 React 来完成一个企业内部系统前端功能的重写。作为一个内部系统,在用户还没有登陆的前提下,是不允许访问系统中任何页面内容的。所以这次我想要做的就是提供一个登录页面,在用户没有登录时访问系统只能看到这个登陆页面的内容。
另外为了避免刷新页面后登陆状态重置,最好还要保存一下用户的登陆信息。这需要用到浏览器的 Cookie 功能。
实现登录功能
登陆离不开输入帐号和密码的界面,先完成登陆界面的 React 组件。
进入项目目录,在 src/pages/
目录下创建 login.js 文件,并输入以下代码:
import React, { useState } from 'react';
export default ({ saveToken }) => {
const [account, setAccount] = useState();
const [password, setPassword] = useState();
const handleSubmit = (e) => {
e.preventDefault();
// 判断输入帐号和密码
if (account == 'zzxworld' && password == '123456') {
saveToken(account);
} else {
alert('Invalid account or password!');
}
}
return (
<div>
<h2>Login</h2>
<form onSubmit={ handleSubmit }>
<label>
<p>Account:</p>
<input type="text" onChange={ e => setAccount(e.target.value) } />
</label>
<label>
<p>Password:</p>
<input type="password" onChange={ e => setPassword(e.target.value) } />
</label>
<p>
<button type="submit">Submit</button>
</p>
</form>
</div>
);
}
和之前创建的两个页面:「首页」和「关于页面」组件代码相比,这次登录页面组件的代码就丰富多了。
首先是使用了 useState,这是 React 从 16.8 这个版本开始提供的新功能:Hook。可以让函数的 React 组件获得和类组件一样的能力。这里使用的 useState 是其中的一个 Hook 函数,可以在函数式组件中定义一个状态绑定变量和设置其值的方法。因为需要获取并设置用户输入的帐号密码,所以使用了两次 useState。
React 提供了不同场景下使用的 Hook,本文的目的不是介绍 Hook,所以这里先不介绍其他 Hook,有兴趣可以前往官网了解学习。
另外是 handleSubmit
方法,它定义在 form 表单提交的事件上。当点击 Submit 提交按钮时会执行这个方法。你可能注意到了,这个方法里我「写死」了正确的帐号和密码。原因是这是一个试验项目,为了突出本文的目的,我省略了和后端的交互过程。在正式项目里,这个方法会调用后端登录接口并取得登录凭证和用户信息。
最后是这个登录组件有了传参,会从外部提供一个 saveToken
方法,它用来保存登录成功的凭证。此处简单起见,直接把帐号作为了登录凭证。
有了登录页面组件,接下来就是在 src/main.js
项目入口代码中添加登录相关的处理逻辑。修改后的 src/main.js
代码如下:
import React, { Suspense, lazy, useState } from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Routes, Route, Link } from 'react-router-dom';
import PageLogin from './pages/login';
const PageHome = lazy(() => import('./pages/home'))
const PageAbout = lazy(() => import('./pages/about'))
function AppLayout() {
const [token, setToken] = useState();
const handleLogout = (e) => {
e.preventDefault();
setToken(null);
}
if (!token) {
return <PageLogin saveToken={ setToken } />;
}
return (
<HashRouter>
<nav>
<Link to="/">Home</Link> | <Link to="/about">Abount</Link>
</nav>
{ token ? <p>Welcome, { token }。<a href="#" onClick={ handleLogout }>Logout</a></p> : '' }
<Routes>
<Route path="/" element={
<Suspense fallback={<div>Loading...</div>}>
<PageHome />
</Suspense>
} />
<Route path="/about" element={
<Suspense fallback={<div>Loading...</div>}>
<PageAbout />
</Suspense>
} />
</Routes>
</HashRouter>
);
}
ReactDOM.render(<AppLayout />, document.getElementById('app'))
这次的调整主要如下:
- 同样引用了 useState 来保存登录凭证。
- 在 AppLayout 组件方法中,添加了登录凭证的判断。如果没有值就会直接渲染登录页面组件。
- 提供了
handleLogout
退出方法,只有在有登录凭证时才会在 Logout 链接被点击时调用。它会删除当前的登录凭证。
执行 yarn start
运行项目看看效果。这是现在打开首页时的界面:
可以看到地址栏虽然是首页地址,但显示的是登录界面,这说明权限逻辑起作用了。输入代码中定义的正确帐号和密码后登录:
地址栏没变,但内容已经是首页内容了。中间还多了一项欢迎信息。点击后面的 Logout 链接,又会回到登录界面。
保存登录状态
上面虽然完成了使用 React 制作登录认证的功能,但有点小问题。当登录成功后,即便不点击后面的退出链接,只是刷新一下页面,依然会回到登录页面。
这是因为在刚才的逻辑中,只是通过 setToken
把登录凭证保存在变量中。这个变量只会跟当前这次请求的环境相关。当刷新页面时,浏览器会重新发起请求,之前的登录凭证也就跟随页面一起消失了,所以这意味着又要重新进行一次登录操作。在实际的业务系统中,这样的情况是不能允许发生的。解决这个问题的方案就是把登录凭证存储到一个脱离会话的环境中。比如浏览器提供的 Cookie。
关于浏览器 Cookie 的介绍网上有很多,这里不做过多解释。从需求来说,它可以让我在同一个网址下,跨请求的存放并使用数据。把登录凭证保存到 Cookie,只要登录成功,在不主动推出,或者是数据过期的前提下,这个登录状态就会一直保持着。这正是我需要达成的目的。
首先安装一个 Cookie 操作的扩展库:
yarn add js-cookie
然后再次调整下 src/main.js
文件中的功能,调整后的代码如下:
import React, { Suspense, lazy, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Routes, Route, Link } from 'react-router-dom';
import Cookies from 'js-cookie';
import PageLogin from './pages/login';
const PageHome = lazy(() => import('./pages/home'))
const PageAbout = lazy(() => import('./pages/about'))
function AppLayout() {
const [token, setToken] = useState();
useEffect(() => {
setToken(Cookies.get('token'))
}, [])
const saveToken = (token) => {
Cookies.set('token', token, {
expires: 7,
sameSite: 'strict'
})
setToken(token)
}
const handleLogout = (e) => {
e.preventDefault();
Cookies.remove('token', {
path: ''
})
setToken(null);
}
if (!token) {
return <PageLogin saveToken={ saveToken } />;
}
return (
<HashRouter>
<nav>
<Link to="/">Home</Link> | <Link to="/about">Abount</Link>
</nav>
{ token ? <p>Welcome, { token }。<a href="#" onClick={ handleLogout }>Logout</a></p> : '' }
<Routes>
<Route path="/" element={
<Suspense fallback={<div>Loading...</div>}>
<PageHome />
</Suspense>
} />
<Route path="/about" element={
<Suspense fallback={<div>Loading...</div>}>
<PageAbout />
</Suspense>
} />
</Routes>
</HashRouter>
);
}
ReactDOM.render(<AppLayout />, document.getElementById('app'))
这次使用了一个新的 React Hook:useEffect。它会用来在每次加载页面时自动从 Cookie 中获取保存的登录凭证,以判断当前用户有没有登录。
另外在保存登录凭证和退出方法中,都添加了 Cookie 的处理功能。具体的逻辑可以通过以上代码来了解。
现在任凭我怎么刷新,只要不点退出,登录状态都不会丢失了。