欢迎来到我的博客
博主是一名大学在读本科生,主要学习方向是前端。
目前已经更新了【Vue】、【React–从基础到实战】、【TypeScript】等等系列专栏
目前正在学习的是 R e a c t 框架 React框架 React框架,中间穿插了一些基础知识的回顾
博客主页codeMak1r.小新的博客本文目录
- 登陆模块
- 1.基本结构模块
- 2. 创建表单结构
- 3. 表单校验实现
- 4. 获取登录表单数据
- 5. 封装http工具模块
- 6. 配置登录Mobx
- 7. 实现登录逻辑
- 8. token持久化
- 封装工具函数
- 持久化设置
- 9. axios请求拦截器注入token
- 10. 路由导航守卫
本文被专栏【React–从基础到实战】收录
坚持创作✏️,一起学习,码出未来!
最近在学习React过程中,找到了一个实战小项目,在这里与大家分享。
本文遵循项目开发流程,逐步完善各个需求
前文——《项目前置准备》
本节目标:
能够使用antd搭建基础布局
实现步骤
代码实现
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;
}
}
本节目标:
能够使用antd的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>
)
}
本节目标:
能够为手机号和密码添加表单校验
实现步骤
validateTrigger
属性,指定校验触发时机的集合rules
属性,用来添加表单校验代码实现
page/Login/index.js
const Login = () => {
return (
<Form validateTrigger={['onBlur', 'onChange']}>
<Form.Item
name="mobile"
rules={[
{
pattern: /^1[3-9]\d{9}$/,
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>
)
}
本节目标:
能够拿到登录表单中用户的手机号码和验证码
实现步骤
onFinish
属性,该事件会在点击登录按钮时触发initialValues
属性,来初始化表单值代码实现
pages/Login/index.js
// 点击登录按钮时触发 参数values即是表单输入数据
const onFinish = values => {
console.log(values)
}
<Form
onFinish={ onFinish }
initialValues={{
mobile: '13911111111',
code: '246810',
remember: true
}}
>...</Form>
本节目标:
封装axios,简化操作
实现步骤
代码实现
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 }
本节目标:
基于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)
本节目标:
在表单校验通过之后通过封装好的store调用登录接口
实现步骤
代码实现
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 (...)
}
本节目标:
能够统一处理 token 的持久化相关操作,确保刷新后 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函数持久化配置
实现步骤
代码实现
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
《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
【Vue/React实现路由鉴权/导航守卫/路由拦截(react-router v6)】
本节目标:
能够实现未登录时访问拦截并跳转到登录页面(路由鉴权实现)
实现思路
自己封装 AuthRoute
路由鉴权高阶组件,实现未登录拦截,并跳转到登录页面
思路为:判断本地是否有token,如果有,就返回子组件,否则就重定向到登录Login
实现步骤
代码实现
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 />
}
}
{/*
登录:<> >
非登录:
*/ }
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–从基础到实战】