【React】入门Day04 —— 项目搭建及登录与表单校验、token 管理、路由鉴权实现

项目搭建

  • 创建项目

    # 使用npx创建项目
    npx create-react-app my-react-app
    # 进入项目目录
    cd my-react-app
    # 创建项目目录结构
    mkdir -p src/{apis,assets,components,pages,store,utils}
    touch src/{App.js,index.css,index.js}
    • 使用npx create-react-app创建项目,进入项目目录后通过npm start启动。
    • 调整项目目录结构,包括apisassetscomponentspages等多个文件夹。
  • 使用技术

    • 接入scss预处理器,安装sass工具,创建全局样式文件index.scss
      # 安装sass工具
      npm i sass -D
      // 在src/index.scss中设置全局样式
      body {font-family: Arial, sans-serif;background-color: #f4f4f4;
      }
    • 引入组件库antd,安装后在Login页面测试Button组件。
      # 安装antd组件库
      npm i antd
      // 在src/pages/Login/index.jsx中使用Button组件
      import React from 'react';
      import { Button } from 'antd';const Login = () => {return (<div><Button type='primary'>登录</Button></div>);
      };export default Login;
    • 使用react-router-dom配置基础路由,创建LayoutLogin组件并配置路由规则。
      # 安装react-router-dom
      npm i react-router-dom
      // 在src/router/index.js中配置路由
      import { createBrowserRouter } from 'react-router-dom';
      import Login from '../pages/Login';
      import Layout from '../pages/Layout';const router = createBrowserRouter([{path: '/',element: <Layout />,},{path: '/login',element: <Login />,},
      ]);export default router;
    • 通过craco工具包配置别名路径,在craco.config.js中设置webpack别名,并在jsconfig.json中配置VsCode提示。
      # 安装craco工具包
      npm i @craco/craco -D
      // 在craco.config.js中配置别名
      const path = require('path');
      module.exports = {webpack: {alias: {'@': path.resolve(__dirname,'src')}}
      };
      // 在package.json中修改scripts命令
      "scripts": {"start": "craco start","build": "craco build","test": "craco test","eject": "react-scripts eject"
      }
      // 在src/router/index.js中使用别名
      import { createBrowserRouter } from 'react-router-dom';
      import Login from '@/pages/Login';
      import Layout from '@/pages/Layout';const router = createBrowserRouter([{path: '/',element: <Layout />,},{path: '/login',element: <Login />,},
      ]);export default router;
      // 在jsconfig.json中配置VsCode提示
      {"compilerOptions": {"baseUrl": "./","paths": {"@/*": ["src/*"]}}
      }

  • 功能模块实现

    • 登录模块

      • 基本结构搭建

  •         在Login/index.js创建登录页面结构,引入antd组件,使用@/assets路径引入图片,在Login/index.scss中设置样式。
  • import React from 'react';
    import { Card, Form, Input, Button } 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="" /><Form><Form.Item><Input size="large" placeholder="请输入手机号" /></Form.Item><Form.Item><Input size="large" placeholder="请输入验证码" /></Form.Item><Form.Item><Button type="primary" htmlType="submit" size="large" block>登录</Button></Form.Item></Form></Card></div>);
    };export default Login;
  • 表单校验实现

    • Form组件设置validateTrigger,为Form.Item组件设置namerules属性进行表单校验。
      import React from 'react';
      import { Form, Input, Button } from 'antd';const Login = () => {return (<Form validateTrigger={['onBlur']}><Form.Itemname="mobile"rules={[{ required: true, message: '请输入手机号' },{pattern: /^1[3-9]\d{9}$/,message: '手机号码格式不对'}]}><Input size="large" placeholder="请输入手机号" /></Form.Item><Form.Itemname="code"rules={[{ required: true, message: '请输入验证码' },]}><Input size="large" placeholder="请输入验证码" maxLength={6} /></Form.Item><Form.Item><Button type="primary" htmlType="submit" size="large" block>登录</Button></Form.Item></Form>);
      };export default Login;
  • 获取登录表单数据

    • Form组件设置onFinish属性,在点击登录按钮时触发获取表单数据的函数。
      import React from 'react';
      import { Form, Input, Button } from 'antd';const Login = () => {const onFinish = formValue => {console.log(formValue);};return (<Form onFinish={onFinish}><Form.Item><Input size="large" placeholder="请输入手机号" /></Form.Item><Form.Item><Input size="large" placeholder="请输入验证码" /></Form.Item><Form.Item><Button type="primary" htmlType="submit" size="large" block>登录</Button></Form.Item></Form>);
      };export default Login;
  • 封装 request 工具模块

    • 安装axios,在utils/request.js中创建axios实例,配置baseURL、请求拦截器和响应拦截器。
      # 安装axios
      npm i axios
      import axios from 'axios';const http = axios.create({baseURL: 'http://example.com/api',timeout: 5000
      });// 请求拦截器
      http.interceptors.request.use(config => {return config;
      }, error => {return Promise.reject(error);
      });// 响应拦截器
      http.interceptors.response.use(response => {return response.data;
      }, error => {return Promise.reject(error);
      });export { http };
  • 使用 Redux 管理 token

    • 安装react-redux@reduxjs/toolkit,在store中创建userStore切片,设置token初始状态和setUserInforeducers,封装fetchLogin异步方法。
      # 安装react-redux和@reduxjs/toolkit
      npm i react-redux @reduxjs/toolkit
      import { createSlice } from '@reduxjs/toolkit';
      import { http } from '@/utils';const userStore = createSlice({name: 'user',initialState: {token: ''},reducers: {setUserInfo(state, action) {state.token = action.payload;}}
      });const { setUserInfo } = userStore.actions;
      const userReducer = userStore.reducer;const fetchLogin = loginForm => {return async dispatch => {const res = await http.post('/authorizations', loginForm);dispatch(setUserInfo(res.data.token));};
      };export { fetchLogin };
      export default userReducer;
  • 实现登录逻辑

    • Login组件中调用fetchLogin方法,登录成功后跳转到首页并提示。
      import React from 'react';
      import { message } from 'antd';
      import { useDispatch } from 'react-redux';
      import { fetchLogin } from '@/store/modules/user';const Login = () => {const dispatch = useDispatch();const onFinish = async formValue => {await dispatch(fetchLogin(formValue));message.success('登录成功');};return (<div><form onSubmit={onFinish}>{/* 登录表单字段 */}</form></div>);
      };export default Login;
  • token 持久化

    • 封装setTokengetTokenclearToken方法,在userStoresetUserInfo时将token存入本地。
      // 在@/utils/token.js中封装存取方法
      const TOKENKEY = 'token_key';function setToken(token) {return localStorage.setItem(TOKENKEY, token);
      }function getToken() {return localStorage.getItem(TOKENKEY);
      }function clearToken() {return localStorage.removeItem(TOKENKEY);
      }export {setToken,getToken,clearToken
      };
      // 在userStore中使用token持久化方法
      import { createSlice } from '@reduxjs/toolkit';
      import { http } from '@/utils';
      import { getToken, setToken } from '@/utils/token';const userStore = createSlice({name: 'user',initialState: {token: getToken() || ''},reducers: {setUserInfo(state, action) {state.token = action.payload;setToken(state.token);}}
      });export default userStore;
  • 请求拦截器注入 token

    • request.js的请求拦截器中,判断是否有token,有则添加到请求头Authorization中。
      // 在utils/request.js中注入token
      import axios from 'axios';const http = axios.create({baseURL: 'http://example.com/api',timeout: 5000
      });http.interceptors.request.use(config => {const token = getToken();if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;
      }, error => {return Promise.reject(error);
      });http.interceptors.response.use(response => {return response.data;
      }, error => {return Promise.reject(error);
      });export { http };
  • 路由鉴权实现

    • components/AuthRoute/index.jsx中创建路由鉴权高阶组件,判断本地是否有token,决定是否重定向到登录页面。
      import React from 'react';
      import { Navigate } from 'react-router-dom';
      import { getToken } from '@/utils';const AuthRoute = ({ children }) => {const isToken = getToken();if (isToken) {return <>{children}</>;} else {return <Navigate to="/login" replace />;}
      };export default AuthRoute;
      // 在src/router/index.js中使用AuthRoute组件
      import { createBrowserRouter } from 'react-router-dom';
      import Login from '@/pages/Login';
      import Layout from '@/pages/Layout';
      import AuthRoute from '@/components/AuthRoute';const router = createBrowserRouter([{path: '/',element: <AuthRoute><Layout /></AuthRoute>,},{path: '/login',element: <Login />,},
      ]);export default router;
  • Layout 模块

    • 基本结构和样式 reset

      • pages/Layout/index.js中使用antd/Layout组件创建页面结构,引入antdMenuPopconfirm等组件,设置样式并安装normalize.css进行样式 reset。
        import React from 'react';
        import { Layout, Menu, Popconfirm } from 'antd';
        import { HomeOutlined, DiffOutlined, EditOutlined, LogoutOutlined } from '@ant-design/icons';
        import './index.scss';
        import 'normalize.css';const { Header, Sider } = Layout;const items = [{label: '首页',key: '1',icon: <HomeOutlined />,},{label: '文章管理',key: '2',icon: <DiffOutlined />,},{label: '创建文章',key: '3',icon: <EditOutlined />,},
        ];const GeekLayout = () => {return (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">用户名</span><span className="user-logout"><Popconfirm title="是否确认退出?" okText="退出" cancelText="取消"><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"defaultSelectedKeys={['1']}items={items}style={{ height: '100%', borderRight: 0 }}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}>内容</Layout></Layout></Layout>);
        };export default GeekLayout;
    • 二级路由配置

      • pages目录创建HomeArticlePublish页面文件夹,在router/index.js中配置嵌套子路由,在Layout中配置二级路由出口,使用Link修改左侧菜单内容实现路由切换。
        // 在pages目录创建Home.jsx
        import React from 'react';const Home = () => {return <div>首页内容</div>;
        };export default Home;
        // 在pages目录创建Article.jsx
        import React from 'react';const Article = () => {return <div>文章管理内容</div>;
        };export default Article;
        // 在pages目录创建Publish.jsx
        import React from 'react';const Publish = () => {return <div>发布文章内容</div>;
        };export default Publish;
        // 在src/router/index.js中配置二级路由
        import { createBrowserRouter } from 'react-router-dom';
        import Login from '@/pages/Login';
        import Layout from '@/pages/Layout';
        import Publish from '@/pages/Publish';
        import Article from '@/pages/Article';
        import Home from '@/pages/Home';
        import { AuthRoute } from '@/components/AuthRoute';const router = createBrowserRouter([{path: '/',element: (<AuthRoute><Layout /></AuthRoute>),children: [{index: true,element: <Home />,},{path: 'article',element: <Article />,},{path: 'publish',element: <Publish />,},],},{path: '/login',element: <Login />,},
        ]);export default router;
        // 在Layout组件中配置二级路由出口
        import React from 'react';
        import { Outlet } from 'react-router-dom';const GeekLayout = () => {return (<Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout>);
        };export default GeekLayout;
    • 路由菜单点击交互实现

      • Menu组件设置onClick属性实现点击菜单跳转路由,通过useLocation获取当前路由路径实现菜单反向高亮。
        import React from 'react';
        import { Outlet, useNavigate } from 'react-router-dom';
        import { HomeOutlined, DiffOutlined, EditOutlined, LogoutOutlined } from '@ant-design/icons';const items = [{label: '首页',key: '/',icon: <HomeOutlined />,},{label: '文章管理',key: '/article',icon: <DiffOutlined />,},{label: '创建文章',key: '/publish',icon: <EditOutlined />,},
        ];const GeekLayout = () => {const navigate = useNavigate();const menuClick = route => {navigate(route.key);};return (<Layout><Header className="main-header"><div className="logo" /><div className="user-info"><span className="user-name">用户名</span><span className="user-logout"><Popconfirm title="是否确认退出?" okText="退出" cancelText="取消"><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"selectedKeys={['1']}items={items}style={{ height: '100%', borderRight: 0 }}onClick={menuClick}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout></Layout>);
        };export default GeekLayout;
        // 菜单反向高亮实现
        import React from 'react';
        import { Outlet, useLocation } from 'react-router-dom';
        import { HomeOutlined, DiffOutlined, EditOutlined, LogoutOutlined } from '@ant-design/icons';const items = [{label: '首页',key: '/',icon: <HomeOutlined />,},{label: '文章管理',key: '/article',icon: <DiffOutlined />,},{label: '创建文章',key: '/publish',icon: <EditOutlined />,},
        ];const GeekLayout = () => {const location = useLocation();const selectedKey = location.pathname;return (<Layout><Header className="main-header"><div className
    • 展示个人信息

      • store/userStore.js中编写获取用户信息的逻辑,在Layout组件中触发fetchUserInfo方法获取信息并渲染用户名。
        // store/userStore.js
        import { createSlice } from '@reduxjs/toolkit';
        import { http } from '@/utils';
        import { getToken, setToken } from '@/utils';const userStore = createSlice({name: 'user',initialState: {token: getToken() || '',userInfo: {}},reducers: {setUserToken(state, action) {state.token = action.payload;setToken(state.token);},setUserInfo(state, action) {state.userInfo = action.payload;},clearUserInfo(state) {state.token = '';state.userInfo = {};clearToken();}}
        });// 解构出actionCreater
        const { setUserToken, setUserInfo, clearUserInfo } = userStore.actions;
        // 获取reducer函数
        const userReducer = userStore.reducer;const fetchLogin = (loginForm) => {return async (dispatch) => {const res = await http.post('/authorizations', loginForm);dispatch(setUserToken(res.data.token));};
        };const fetchUserInfo = () => {return async (dispatch) => {const res = await http.get('/user/profile');dispatch(setUserInfo(res.data));};
        };export { fetchLogin, fetchUserInfo, clearUserInfo };
        export default userReducer;
    • 退出登录实现

      • Popconfirm添加确认回调事件,在store/userStore.js中新增clearUserInfo方法删除token和用户信息,在回调事件中调用该方法并返回登录页面。
        // pages/Layout/index.js
        import React, { useEffect } from 'react';
        import { Layout, Menu, Popconfirm } from 'antd';
        import {HomeOutlined,DiffOutlined,EditOutlined,LogoutOutlined,
        } from '@ant-design/icons';
        import { useDispatch, useSelector } from 'react-redux';
        import { fetchUserInfo } from '@/store/modules/user';const { Header, Sider } = Layout;const items = [// 菜单配置项
        ];const GeekLayout = () => {const dispatch = useDispatch();const name = useSelector(state => state.user.userInfo.name);useEffect(() => {dispatch(fetchUserInfo());}, [dispatch]);const loginOut = () => {dispatch(clearUserInfo());// 假设这里有合适的导航函数,替换为实际的导航逻辑// navigate('/login'); };return (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">{name}</span><span className="user-logout"><Popconfirmtitle="是否确认退出?"okText="退出"cancelText="取消"onConfirm={loginOut}><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"defaultSelectedKeys={['1']}items={items}style={{ height: '100%', borderRight: 0 }}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}>{/* 页面内容 */}</Layout></Layout></Layout>);
        };export default GeekLayout;
    • 处理 Token 失效

      • http.interceptors.response中判断响应状态码为401时,清除token,跳转到登录页面并刷新页面。
        // 在http.js(假设是配置axios请求相关的文件)中处理Token失效
        import axios from 'axios';const http = axios.create({baseURL: 'http://example.com/api',timeout: 5000
        });http.interceptors.response.use((response) => {return response.data;
        }, (error) => {if (error.response && error.response.status === 401) {// 假设这里有合适的获取和清除token的函数,替换为实际的逻辑const token = getToken();if (token) {clearToken();}// 假设这里有合适的导航函数,替换为实际的导航逻辑// navigate('/login'); window.location.reload();}return Promise.reject(error);
        });export { http };

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/54687.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网站优化门槛低了还是高了?

自从2015年刚接触网站时&#xff0c;从一无所知到现在无人指导&#xff0c;一直跌跌撞撞走过来&#xff0c;当年花了1500元找了广东一个网友用织梦CMS做了一个门户网站&#xff0c;记得那时一星期没下楼&#xff0c;把网站折腾的千疮百孔&#xff0c;而终逐步熟悉网站建设与搜索…

【在Linux世界中追寻伟大的One Piece】DNS与ICMP

目录 1 -> DNS(Domain Name System) 1.1 -> DNS背景 2 -> 域名简介 2.1 -> 域名解析过程 3 -> 使用dig工具分析DNS 4 -> ICMP协议 4.1 -> ICMP功能 4.2 -> ICMP报文格式 4.3 -> Ping命令 4.4 -> traceroute命令 1 -> DNS(Domain Na…

webGL进阶(一)多重纹理效果

效果&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content&q…

Matter蓝牙解析

解析 Matter 蓝牙广播 定义需要解析的字段。 #import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN/// 蓝牙广播服务 ID extern NSString * const MatterBLEAdvServiceID;@interface MatterBLEAdv : NSObject @property (nonatomic, assign) NSInteger opCode; @…

【Unity踩坑】Unity导出的UWP项目编译失败

在Unity中导出了UWP平台的项目后&#xff08;Xaml或D3D&#xff09;&#xff0c;使用Visual Studio编译时发生错误&#xff1a; Error: Unity.IL2CPP.Building.BuilderFailedException: Lump_libil2cpp_vm.cpp 查找后发现是Visual Studio 与Unity兼容的问题 原贴&#xff1a;…

黑神话:仙童,数据库自动反射魔法棒

黑神话&#xff1a;仙童&#xff0c;数据库自动反射魔法棒 Golang 通用代码生成器仙童发布了最新版本电音仙女尝鲜版十一及其介绍视频&#xff0c;视频请见&#xff1a;https://www.bilibili.com/video/BV1ET4wecEBk/ 此视频介绍了使用最新版的仙童代码生成器&#xff0c;将 …

使用YOLOv11进行视频目标检测

使用YOLOv11进行视频目标检测 完整代码 import cv2 from ultralytics import YOLOdef predict(chosen_model, img, classes[], conf0.5):if classes:results chosen_model.predict(img, classesclasses, confconf)else:results chosen_model.predict(img, confconf)return r…

view deign 和 vue2 合并单元格的方法

1.vue版本和view design 版本 {"vue": "^2.6.11","view-design": "^4.7.0", }2.Data中定义数据 spanArr: [], // 某一列下需要合并的行数 pos: 0// 索引// 注意点&#xff1a; 在获取列表前&#xff0c;需要重置 this.spanArr [] 注…

C++-容器适配器- stack、queue、priority_queue和仿函数

目录 1.什么是适配器 2.deque 1.简单了解结构 2.deque的缺陷 3.为什么选择deque作为stack和queue的底层默认容器 3.stack&#xff08;栈&#xff09; 4.queue&#xff08;队列&#xff09; 5.仿函数 6.priority_queue&#xff08;优先级队列&#xff09;&#xff08;堆…

切片辅助超推理-sahi库-get_sliced_prediction源码简析

代码地址&#xff1a;https://github.com/obss/sahi get_sliced_prediction源码中重要是理解nms或nmm。nms经常遇到不说。 其中nmm即Non-Max Merging算法是最重要部分&#xff0c;它其实和nms比较类似。其具体原理我看到了一片博客&#xff0c;感觉讲的很好&#xff0c;如下&a…

Linux C接口编程入门之文件I/O

一切皆文件 "Linux一切皆文件"是Linux操作系统中的一个重要理念和设计原则。在Linux系统中&#xff0c;几乎所有的设备、资源都以文件的形式进行访问和操作。简化了操作系统的设计和管理&#xff0c;提供了一种统一的抽象模型&#xff0c;使得应用程序可以使用相同的…

docker简述

1.安装dockers&#xff0c;配置docker软件仓库 安装&#xff0c;可能需要开代理&#xff0c;这里我提前使用了下好的包安装 启动docker systemctl enable --now docker查看是否安装成功 2.简单命令 拉取镜像&#xff0c;也可以提前下载使用以下命令上传 docker load -i imag…

【gRPC】1—gRPC是什么

gRPC是什么 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; &#x1f4d6;RPC专栏&#xff1a;https://b…

数据工程师岗位常见面试问题-3(附回答)

数据工程师已成为科技行业最重要的角色之一&#xff0c;是组织构建数据基础设施的骨干。随着企业越来越依赖数据驱动的决策&#xff0c;对成熟数据工程师的需求会不断上升。如果您正在准备数据工程师面试&#xff0c;那么应该掌握常见的数据工程师面试问题&#xff1a;包括工作…

脉冲下跳沿提取电路

本例中的电路可将负脉冲转换为正脉冲。尽管这个任务看似简单&#xff0c;但负脉冲的幅度为-5V~-2V。按照不同应用要求&#xff0c;正脉冲也需要不同的脉冲宽度&#xff0c;而负脉冲是梯形的。脉冲必须先经过一个长距离的传输线才能到达某个控制设备。有多个电路可以解决这一问题…

jQuery——解决快速点击翻页的bug

本文分享到此结束&#xff0c;欢迎大家评论区相互讨论学习&#xff0c;下一篇继续分享jQuery中内置动画的学习。

谷歌AI大模型Gemini API快速入门及LangChain调用视频教程

1. 谷歌Gemini API KEY获取及AI Studio使用 要使用谷歌Gemini API&#xff0c;首先需要获取API密钥。以下是获取API密钥的步骤&#xff1a; 访问Google AI Studio&#xff1a; 打开浏览器&#xff0c;访问Google AI Studio。使用Google账号登录&#xff0c;若没有账号&#xf…

大数据ETL数据提取转换和加载处理

什么是 ETL&#xff1f; 提取转换加载&#xff08;英语&#xff1a;Extract, transform, load&#xff0c;简称ETL&#xff09;&#xff0c;用来描述将资料从来源端经过抽取、转置、加载至目的端的过程。ETL一词较常用在数据仓库&#xff0c;但其对象并不限于数据仓库。 ETL&…

C++竞赛初阶—— 石头剪子布

题目内容 石头剪子布&#xff0c;是一种猜拳游戏。起源于中国&#xff0c;然后传到日本、朝鲜等地&#xff0c;随着亚欧贸易的不断发展它传到了欧洲&#xff0c;到了近现代逐渐风靡世界。简单明了的规则&#xff0c;使得石头剪子布没有任何规则漏洞可钻&#xff0c;单次玩法比…

Spring Cloud Netflix Zuul 网关详解及案例示范

1. 引言 在微服务架构中&#xff0c;API 网关作为服务间通信的入口&#xff0c;扮演着重要的角色。Netflix Zuul 是一个提供动态路由、监控、安全等功能的 API 网关服务器&#xff0c;它可以为微服务系统提供统一的入口&#xff0c;简化服务间的交互。在业务系统中&#xff0c…