前端实战|React18极客园——登陆模块(token持久化路由拦截mobx封装axios)
Posted codeMak1r.小新
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端实战|React18极客园——登陆模块(token持久化路由拦截mobx封装axios)相关的知识,希望对你有一定的参考价值。
欢迎来到我的博客
📔博主是一名大学在读本科生,主要学习方向是前端。
🍭目前已经更新了【Vue】、【React–从基础到实战】、【TypeScript】等等系列专栏
🛠目前正在学习的是🔥 R e a c t 框架 React框架 React框架🔥,中间穿插了一些基础知识的回顾
🌈博客主页👉codeMak1r.小新的博客😇本文目录😇
本文被专栏【React–从基础到实战】收录
🕹坚持创作✏️,一起学习📖,码出未来👨🏻💻!
最近在学习React过程中,找到了一个实战小项目,在这里与大家分享。
本文遵循项目开发流程,逐步完善各个需求
前文——《项目前置准备》
登陆模块
1.基本结构模块
本节目标:
能够使用antd搭建基础布局
实现步骤
- 在 Login/index.js 中创建登录页面基本结构
- 在 Login 目录中创建 index.scss 文件,指定组件样式
- 将 logo.png 和 login.png 拷贝到 assets 目录中
代码实现
pages/Login/index.js
import Card from 'antd'
import logo from '@/assets/logo.png'
import './index.scss'
const Login = () =>
return (
<div className="login">
<Card className="login-container">
<img className="login-logo" src=logo alt="" />
/* 登录表单 */
</Card>
</div>
)
export default Login
pages/Login/index.scss
.login
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background: center/cover url('~@/assets/login.png');
.login-logo
width: 200px;
height: 60px;
display: block;
margin: 0 auto 20px;
.login-container
width: 440px;
height: 360px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 50px rgb(0 0 0 / 10%);
.login-checkbox-label
color: #1890ff;
2. 创建表单结构
本节目标:
能够使用antd的Form组件创建登录表单
实现步骤
- 打开 antd Form 组件文档
- 找到代码演示的第一个示例(基本使用),点击
<>
(显示代码),并拷贝代码到组件中 - 分析 Form 组件基本结构
- 调整 Form 组件结构和样式
代码实现
pages/Login/index.js
import Form, Input, Button, Checkbox from 'antd'
const Login = () =>
return (
<Form>
<Form.Item>
<Input size="large" placeholder="请输入手机号" />
</Form.Item>
<Form.Item>
<Input size="large" placeholder="请输入验证码" />
</Form.Item>
<Form.Item>
<Checkbox className="login-checkbox-label">
我已阅读并同意「用户协议」和「隐私条款」
</Checkbox>
</Form.Item>
<Form.Item>
<!-- 渲染Button组件为submit按钮 -->
<Button type="primary" htmlType="submit" size="large" block>
登录
</Button>
</Form.Item>
</Form>
)
3. 表单校验实现
本节目标:
能够为手机号和密码添加表单校验
实现步骤
- 为 Form 组件添加
validateTrigger
属性,指定校验触发时机的集合 - 为 Form.Item 组件添加 name 属性,这样表单校验才会生效
- 为 Form.Item 组件添加
rules
属性,用来添加表单校验
代码实现
page/Login/index.js
const Login = () =>
return (
<Form validateTrigger=['onBlur', 'onChange']>
<Form.Item
name="mobile"
rules=[
pattern: /^1[3-9]\\d9$/,
message: '手机号码格式不对',
validateTrigger: 'onBlur'
,
required: true, message: '请输入手机号'
]
>
<Input size="large" placeholder="请输入手机号" />
</Form.Item>
<Form.Item
name="code"
rules=[
len: 6, message: '验证码6个字符', validateTrigger: 'onBlur' ,
required: true, message: '请输入验证码'
]
>
<Input size="large" placeholder="请输入验证码" maxLength=6 />
</Form.Item>
<Form.Item name="remember" valuePropName="checked">
<Checkbox className="login-checkbox-label">
我已阅读并同意「用户协议」和「隐私条款」
</Checkbox>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" size="large" block>
登录
</Button>
</Form.Item>
</Form>
)
4. 获取登录表单数据
本节目标:
能够拿到登录表单中用户的手机号码和验证码
实现步骤
- 为 Form 组件添加
onFinish
属性,该事件会在点击登录按钮时触发 - 创建 onFinish 函数,通过函数参数 values 拿到表单值
- Form 组件添加
initialValues
属性,来初始化表单值
代码实现
pages/Login/index.js
// 点击登录按钮时触发 参数values即是表单输入数据
const onFinish = values =>
console.log(values)
<Form
onFinish= onFinish
initialValues=
mobile: '13911111111',
code: '246810',
remember: true
>...</Form>
5. 封装http工具模块
本节目标:
封装axios,简化操作
实现步骤
- 创建 utils/http.js 文件
- 创建 axios 实例,配置 baseURL,请求拦截器,响应拦截器
- 在 utils/index.js 中,统一导出 http
代码实现
utils/http.js
import axios from 'axios'
const http = axios.create(
baseURL: 'http://geek.itheima.net/v1_0',
timeout: 5000
)
// 添加请求拦截器
http.interceptors.request.use((config)=>
return config
, (error)=>
return Promise.reject(error)
)
// 添加响应拦截器
http.interceptors.response.use((response)=>
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response
, (error)=>
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
)
export http
utils/index.js
import http from './http'
export http
6. 配置登录Mobx
本节目标:
基于mobx封装管理用户登录的store
store/login.Store.js
// 登录模块
import makeAutoObservable from "mobx"
import http from '@/utils'
class LoginStore
token = ''
constructor()
makeAutoObservable(this)
// 登录
login = async ( mobile, code ) =>
const res = await http.post('http://geek.itheima.net/v1_0/authorizations',
mobile,
code
)
this.token = res.data.token
export default LoginStore
store/index.js
import React from "react"
import LoginStore from './login.Store'
class RootStore
// 组合模块
constructor()
this.loginStore = new LoginStore()
// 导入useStore方法供组件使用数据
const StoresContext = React.createContext(new RootStore())
export const useStore = () => React.useContext(StoresContext)
7. 实现登录逻辑
本节目标:
在表单校验通过之后通过封装好的store调用登录接口
实现步骤
- 使用useStore方法得到loginStore实例对象
- 在校验通过之后,调用loginStore中的login函数
- 登录成功之后跳转到首页
代码实现
import useStore from '@/store'
const onFinish = async (values) =>
// 存储登录成功的token
try
await loginStore.setToken(values)
navigate('/', replace: true )
message.success('At Your Service, Sir!', 2)
catch (error)
message.error(error.response?.data?.message || '登录失败')
;
const onFinishFailed = (errorInfo) =>
const [name] = errorInfo.errorFields[0].name
if (name === "captcha") message.error('登录失败,请检查验证码是否有误!', 2);
if (name === "tel") message.error('登录失败,请检查手机号是否有误!', 2);
return (...)
8. token持久化
封装工具函数
本节目标:
能够统一处理 token 的持久化相关操作,确保刷新后 token 不丢失。
实现步骤
- 创建 utils/token.js 文件
- 分别提供 getToken/setToken/clearToken/isAuth 四个工具函数并导出
- 创建 utils/index.js 文件,统一导出 token.js 中的所有内容,来简化工具函数的导入
- 将登录操作中用到 token 的地方,替换为该工具函数
代码实现
utils/token.js
const TOKEN_KEY = 'geek_pc'
const getToken = () => localStorage.getItem(TOKEN_KEY)
const setToken = token => localStorage.setItem(TOKEN_KEY, token)
const clearToken = () => localStorage.removeItem(TOKEN_KEY)
export getToken, setToken, clearToken
持久化设置
本节目标:
使用token函数持久化配置
实现步骤
- 拿到token的时候一式两份,存本地一份
- 初始化的时候优先从本地取,取不到再初始化为控制
代码实现
store/login.Store.js
// 登录模块
import makeAutoObservable from "mobx"
import setToken, getToken, clearToken, http from '@/utils'
class LoginStore
// 这里哦!!
token = getToken() || ''
constructor()
makeAutoObservable(this)
// 登录
login = async ( mobile, code ) =>
const res = await http.post('http://geek.itheima.net/v1_0/authorizations',
mobile,
code
)
this.token = res.data.token
// 还有这里哦!!
setToken(res.data.token)
export default LoginStore
9. axios请求拦截器注入token
《Vue/React项目实现axios请求拦截器注入token》
本节目标:
把token通过请求拦截器注入到请求头中
拼接方式:config.headers.Authorization = Bearer $token
utils/http.js
http.interceptors.request.use(config =>
const token = getToken('pc-key')
if (token)
config.headers.Authorization = `Bearer $token`
return config
)
第一次发起请求,是登录请求,此时,localStorage中没有token,getToken获取不到,不走下面这个if函数体,直接return config;
后面再发请求时,由于已经登录了,此时,localStorage中有token,getToken获取到了,走if中的函数体,在发起请求前自动进行预处理,追加一个token,以便于访问需要权限的页面
为请求头对象(headers)中添加token验证的自定义字段(Authorization),作用是为了让需要验证才能使用的API能够使用(请求头中携带了token值则可通过验证)
在最后必须return config
10. 路由导航守卫
【Vue/React实现路由鉴权/导航守卫/路由拦截(react-router v6)】
本节目标:
能够实现未登录时访问拦截并跳转到登录页面(路由鉴权实现)
实现思路
自己封装 AuthRoute
路由鉴权高阶组件,实现未登录拦截,并跳转到登录页面
思路为:判断本地是否有token,如果有,就返回子组件,否则就重定向到登录Login
实现步骤
- 在 components 目录中,创建 AuthRoute/index.js 文件
- 判断是否登录
- 登录时,直接渲染相应页面组件
- 未登录时,重定向到登录页面
- 将需要鉴权的页面路由配置,替换为 AuthRoute 组件渲染
代码实现
components/AuthRoute/index.js
// 路由鉴权
// 1. 判断token是否存在
// 2. 如果存在 直接正常渲染
// 3. 如果不存在 重定向到登录路由
import Navigate from "react-router-dom";
import getToken from "@/utils";
// 高阶组件:把一个组件当成另外一个组件的参数传入 然后通过一定的判断 返回新的组件
// 这里的AuthRoute就是一个高阶组件
function AuthRoute( children )
// 获取token
const tokenStr = getToken()
// 如果token存在 直接正常渲染
if (tokenStr)
return <>children</>
// 如果token不存在,重定向到登录路由
else
return <Navigate to='/login' replace />
/*
<AuthRoute> <Layout /> </AuthRoute>
登录:<> <Layout /> </>
非登录:<Navigate to="/login" replace />
*/
export AuthRoute
注:utils工具函数
getToken
如下// 从localstorage中取token const getToken = () => return window.localStorage.getItem(key)
src/routes/index.js路由表文件
import Layout from "@/pages/Layout";
import Login from "@/pages/Login";
import AuthRoute from "@/components/AuthRoute";
// eslint-disable-next-line
export default [
// 不需要鉴权的组件Login
path: "/login",
element: <Login />
,
// 需要鉴权的组件Layout
path: "/",
element: <AuthRoute>
<Layout />
</AuthRoute>
]
下篇文章:Layout布局模块的实现
专栏订阅入口【React–从基础到实战】
前端实战|React18极客园——布局模块(useRoutes路由配置处理Token失效退出登录)
欢迎来到我的博客
📔博主是一名大学在读本科生,主要学习方向是前端。
🍭目前已经更新了【Vue】、【React–从基础到实战】、【TypeScript】等等系列专栏
🛠目前正在学习的是🔥 R e a c t / 小程序 React/小程序 React/小程序🔥,中间穿插了一些基础知识的回顾
🌈博客主页👉codeMak1r.小新的博客😇本文目录😇
本文被专栏【React–从基础到实战】收录
🕹坚持创作✏️,一起学习📖,码出未来👨🏻💻!
最近在学习React过程中,找到了一个实战小项目,在这里与大家分享。
本文遵循项目开发流程,逐步完善各个需求
gitee完整项目地址:极客园完整代码
Layout模块
1. 基本结构搭建
本节目标:
能够使用antd搭建基础布局
实现步骤
- 打开 antd/Layout 布局组件文档,找到示例:顶部-侧边布局-通栏
- 拷贝示例代码到我们的 Layout 页面中
- 分析并调整页面布局
代码实现
pages/Layout/index.js
import Layout, Menu, Popconfirm from 'antd'
import
HomeOutlined,
DiffOutlined,
EditOutlined,
LogoutOutlined
from '@ant-design/icons'
import './index.scss'
const Header, Sider = Layout
const GeekLayout = () =>
return (
<Layout>
<Header className="header">
<div className="logo" />
<div className="user-info">
<span className="user-name">user.name</span>
<span className="user-logout">
<Popconfirm title="是否确认退出?" okText="退出" cancelText="取消">
<LogoutOutlined /> 退出
</Popconfirm>
</span>
</div>
</Header>
<Layout>
<Sider width=200 className="site-layout-background">
<Menu
mode="inline"
theme="dark"
defaultSelectedKeys=['1']
style= height: '100%', borderRight: 0
>
<Menu.Item icon=<HomeOutlined /> key="1">
数据概览
</Menu.Item>
<Menu.Item icon=<DiffOutlined /> key="2">
内容管理
</Menu.Item>
<Menu.Item icon=<EditOutlined /> key="3">
发布文章
</Menu.Item>
</Menu>
</Sider>
<Layout className="layout-content" style= padding: 20 >内容</Layout>
</Layout>
</Layout>
)
export default GeekLayout
pages/Layout/index.scss
.ant-layout
height: 100%;
.header
padding: 0;
.logo
width: 200px;
height: 60px;
background: url('~@/assets/logo.png') no-repeat center / 160px auto;
.layout-content
overflow-y: auto;
.user-info
position: absolute;
right: 0;
top: 0;
padding-right: 20px;
color: #fff;
.user-name
margin-right: 20px;
.user-logout
display: inline-block;
cursor: pointer;
.ant-layout-header
padding: 0 !important;
2. 二级路由配置
本节目标:
能够在右侧内容区域展示左侧菜单对应的页面内容
使用步骤
- 在 pages 目录中,分别创建:Home(数据概览)/Article(内容管理)/Publish(发布文章)页面文件夹
- 分别在三个文件夹中创建 index.js 并创建基础组件后导出
- 在app.js中配置嵌套子路由,在layout.js中配置二级路由出口
- 使用 Link 修改左侧菜单内容,与子路由规则匹配实现路由切换
代码实现
pages/Home/index.js
const Home = () =>
return <div>Home</div>
export default Home
pages/Article/index.js
const Article = () =>
return <div>Article</div>
export default Article
pages/Publish/index.js
const Publish = () =>
return <div>Publish</div>
export default Publish
src/routes/index.js
export default [
// 不需要鉴权的组件Login
path: "/login",
element: <Login />
,
// 需要鉴权的组件Layout
path: "/",
element: <AuthRoute>
<Layout />
</AuthRoute>,
children: [
path: "home",
element: <AuthRoute>
<Home />
</AuthRoute>
,
path: "article",
element: <AuthRoute>
<Article />
</AuthRoute>
,
path: "publish",
element: <AuthRoute>
<Publish />
</AuthRoute>
,
path: "",
element: <Navigate to="home" replace />
]
]
pages/Layout/index.js
// 配置Link组件
<Menu
mode="inline"
theme="dark"
defaultSelectedKeys=['1']
style= height: '100%', borderRight: 0
>
<Menu.Item icon=<HomeOutlined /> key="1" onClick=() => navigate('home')>
数据概览
</Menu.Item>
<Menu.Item icon=<DiffOutlined /> key="2" onClick=() => navigate('article')>
内容管理
</Menu.Item>
<Menu.Item icon=<EditOutlined /> key="3" onClick=() => navigate('publish')>
发布文章
</Menu.Item>
</Menu>
<Layout className="layout-content" style= padding: 20 ><Outlet /></Layout>
3. 菜单高亮显示
本节目标:
能够在页面刷新的时候保持对应菜单高亮
思路
- Menu组件的selectedKeys属性与Menu.Item组件的key属性发生匹配的时候,Item组件即可高亮
- 页面刷新时,将
当前访问页面的路由地址
作为 Menu 选中项的值(selectedKeys)即可
实现步骤
- 将 Menu 的
key
属性修改为与其对应的路由地址 - 获取到当前正在访问页面的路由地址
- 将当前路由地址设置为
selectedKeys
属性的值
代码实现
pages/Layout/index.js
import useLocation from 'react-router-dom'
const GeekLayout = () =>
const pathname: selectedKey = useLocation()
console.log(selectedKey)
return (
// ...
<Menu
mode="inline"
theme="dark"
selectedKeys=[selectedKey]
style= height: '100%', borderRight: 0
>
<Menu.Item icon=<HomeOutlined /> key="/home" onClick=() => navigate('home')>
数据概览
</Menu.Item>
<Menu.Item icon=<DiffOutlined /> key="/article" onClick=() => navigate('article')>
内容管理
</Menu.Item>
<Menu.Item icon=<EditOutlined /> key="/publish" onClick=() => navigate('publish')>
发布文章
</Menu.Item>
</Menu>
)
4. 展示个人信息
本节目标:
能够在页面右上角展示登录用户名
实现步骤
- 在store中新增user.Store.js模块,在其中定义获取用户信息的mobx代码
- 在store的入口文件中组合新增的userStore模块
- 在Layout组件中调用action函数获取用户数据
- 在Layout组件中获取个人信息并展示
代码实现
store/user.Store.js
// 用户模块
import makeAutoObservable from "mobx"
import http from '@/utils'
class UserStore
userInfo =
constructor()
makeAutoObservable(this)
async getUserInfo()
const res = await http.get('/user/profile')
this.userInfo = res.data
export default UserStore
store/index.js
import React from "react"
import LoginStore from './login.Store'
import UserStore from './user.Store'
class RootStore
// 组合模块
constructor()
this.loginStore = new LoginStore()
this.userStore = new UserStore()
const StoresContext = React.createContext(new RootStore())
export const useStore = () => React.useContext(StoresContext)
pages/Layout/index.js
import useEffect from 'react'
import observer from 'mobx-react-lite'
const GeekLayout = () =>
const userStore = useStore()
// 获取用户数据
useEffect(() =>
try
userStore.getUserInfo()
catch
, [userStore])
return (
<Layout>
<Header className="header">
<div className="logo" />
<div className="user-info">
<span className="user-name">userStore.userInfo.name</span>
</div>
</Header>
/* 省略无关代码 */
</Layout>
)
export default observer(GeekLayout)
5. 退出登录实现
本节目标:
能够实现退出登录功能
实现步骤
- 为气泡确认框添加确认回调事件
- 在
store/login.Store.js
中新增退出登录的action函数,在其中删除token - 在回调事件中,调用loginStore中的退出action
- 退出后,返回登录页面
代码实现
store/login.Store.js
class LoginStore
// 退出登录
loginOut = () =>
this.token = ''
clearToken()
export default LoginStore
clearToken()是utils/token.js中定义好的清除token的工具函数。
pages/Layout/index.js
// login out
const navigate = useNavigate()
const onLogout = () =>
loginStore.loginOut()
navigate('/login')
<span className="user-logout">
<Popconfirm title="是否确认退出?" okText="退出" cancelText="取消" onConfirm=onLogout>
<LogoutOutlined /> 退出
</Popconfirm>
</span>
6. 处理Token失效
本节目标:
能够在响应拦截器中处理token失效
说明:为了能够在非组件环境下拿到路由信息,需要我们安装一个history包
实现步骤
- 安装history包:
yarn add history
- 创建
utils/history.js
文件 - 在app.js中使用我们新建的路由并配置history参数
- 通过响应拦截器处理 token 失效,如果发现是401跳回到登录页
代码实现
utils/history.js
// https://github.com/remix-run/react-router/issues/8264
import createBrowserHistory from 'history'
const history = createBrowserHistory()
export history
index.js入口文件
...省略无关代码
import unstable_HistoryRouter as HistoryRouter from "react-router-dom";
import history from "./utils/history";
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<HistoryRouter history=history>
<App />
</HistoryRouter>
)
utils/http.js
import history from './history'
http.interceptors.response.use(
response =>
return response.data
,
error =>
if (error.response.status === 401)
// 清除失效的token
removeToken()
// 跳转到登录页
history.push('/login')
return Promise.reject(error)
)
7. 首页Home图表展示
本节目标:
实现首页echart图表封装展示
需求描述:
- 使用eharts配合react封装柱状图组件Bar
- 要求组件的标题title,横向数据xData,纵向数据yData,样式style可定制
代码实现
components/Bar/index.js
// 封装图表bar组件
// 思路:
// 1. 看官方文档 把echarts加入项目
// 如何在react中获取dom => useRef
// 在什么地方获取dom节点 => useEffect
// 2. 不抽离定制化参数 先把最小化的demo跑起来
// 3. 按照需求:哪些参数需要自定义 抽象出来
import useRef, useEffect from 'react';
import * as echarts from 'echarts'
export default function Bar( title, xData, yData, style )
const domRef = useRef()
// 执行这个初始化的函数
useEffect(() =>
const chartInit = () =>
// 基于准备好的dom,初始化echarts实例
const myChart = echarts.init(domRef.current);
// 绘制图表
myChart.setOption(
title:
text: title
,
tooltip: ,
xAxis:
data: xData
,
yAxis: ,
series: [
name: '框架',
type: 'bar',
data: yData
]
);
chartInit()
, [title, xData, yData])
return (
<div>
/* 为echart准备一个dom节点 */
<div ref=domRef style=style></div>
</div>
)
pages/Home/index.js
import React from 'react'
import Bar from '@/components/Bar'
export default function Home()
return (
<div>
<Bar
title='主流框架使用满意度'
xData=['React', 'Vue', 'Angular']
yData=[40, 50, 30]
style= width: '500px', height: '400px'
/>
<Bar
title='主流框架使用满意度2'
xData=['React', 'Vue', 'Angular']
yData=[70, 80, 40]
style= width: '300px', height: '200px'
/>
</div>
)
pages/Home/index.scss
.home
width: 100%;
height: 100%;
align-items: center;
下篇文章:内容管理模块的实现
专栏订阅入口【React–从基础到实战】
以上是关于前端实战|React18极客园——登陆模块(token持久化路由拦截mobx封装axios)的主要内容,如果未能解决你的问题,请参考以下文章
2019大前端热门技术流之React服务器端渲染NextJS实战
这篇只需要你有一些前端基础就可以上手开发HarmonyOS应用