react+vite+antD+reduce+echarts项目完整记录

时间:2024-03-31 21:20:53

react+vite+antD+reduce+echarts项目完整记录

之前写前端项目,都是用的vue,从最开始的vue2到后来的vue3,断断续续写了3年,打包工具也从webpack转到了vite,全局数据管理工具从vuex转到了pinia。总体而言,vue3对比vue2,有非常明显的提升,vite比webpack打包的速度更是快了无数倍,至于pinia和vuex,因人而异,我更喜欢pinia,组合式api的写法深得我心。总而言之一句话,我是全方面拥抱了vue3的新技术栈,当然,除了TS,TS对后端比较友好,我只能算半个后端,用不用无所谓。时代在前进,技术在发展,如果永远守着一套陈旧的技术,找各种理由为自己辩解,实在是不明智的选择。

一直想学一下react,中途学过几次,因为平时工作事情太多不得不停下来。刚开始接触jsx,我是抵制的,vue把html、js和css进行了严格的区分,并摆脱了原生的dom操作,jsx却又把这些混在一起,写代码的时候让我感觉像吃了si一样难受。学完之后依然觉得很难受,但为啥我还是坚持要学react呢,这么几个原因:

  • 从全球来看,react是最火的前端框架,vue只在国内火,我在看国外的一些项目的源码时,发现自己完全看不懂在写什么,甚至国内,有些开源项目也只出了react的包,比如mapv
  • 换一种框架,扩展一下自己的技能树
  • 熟悉原生js

花了3天速刷了一遍B站黑马前端讲师的课,并跟着完整写了一个非常简单的项目,后端接口也是用的黑马的,感谢黑马,记录一下完整的过程,为自己后面写项目提供参考,也为后来人提供参考

项目最终界面:

  1. 登录界面

image-20240329084425524

  1. 首页

image-20240328115845338

  1. 文章管理

image-20240328115913121

  1. 创建文章

image-20240328115938645

目前就这些了,以下进入正题

〇、代码仓库地址及视频地址

https://gitee.com/hgandzl/react-vite

react+vite+redux+echarts前端练手小项目

一、创建项目并配置基础环境

1. vite创建项目

黑马老师是基于CRA创建项目,应该是和webpack相关的技术,没深入了解,我是用的vite

vite创建前端项目的指令

npm create vite@latest

创建过程如下:

image-20240328120528329

vscode打开创建的项目,执行npm iimage-20240328120722097执行npm run dev,即可打开默认的vite+react项目

2. 整理项目目录

项目src文件夹下依次创建如下文件夹

-src
  -apis           项目接口函数
  -assets         项目资源文件,比如,图片等
  -components     通用组件
  -pages          页面组件
  -router		  路由
  -store          集中状态管理
  -utils          工具,比如,token、axios 的封装等
  -App.jsx        根组件
  -index.scss     全局样式
  -main.jsx       项目入口

删除无关的文件,只保留App.jsx和main.jsx,并删除相关引入

删除main.jsx中的严格节点模式

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";

ReactDOM.createRoot(document.getElementById("root")).render(<App />);

删除App.jsx中无关的代码,保留基础组件

function App() {
  return <>app</>;
}

export default App;

3. 使用scss预处理器

实现步骤

  1. 安装解析 sass 的包:npm i sass -D
  2. 创建全局样式文件:index.scss

index.scss

* {
    margin: 0;
    padding: 0;
}

项目入口文件引入index.scss

4. 使用Ant Design作为UI框架

实现步骤

  1. 安装 antd 组件库:npm i antd
  2. 页面上导入并使用

5. 配置基础路由

实现步骤

  1. 安装路由包 npm i react-router-dom

  2. 准备 LayoutLogin俩个基础组件

    pages目录下新建两个组件,分别是pages/Layout/index.jsx和pages/Login/index.jsx,并同步新建样式文件

    pages/Layout/index.jsx

    const Layout = () => {
      return <div>this is layout</div>
    }
    export default Layout
    

    pages/Login/index.jsx

    const Login = () => {
      return <div>this is login</div>
    }
    export default Login
    
  3. 配置路由

    router文件夹下新建index.jsx文件,并配置如下基础路由

    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
    
  4. 全局挂载路由

    和vue项目类似,路由要全局挂载

    main.jsx

    import React from "react";
    import ReactDOM from "react-dom/client";
    import "./index.scss";
    import router from "./router";
    import { RouterProvider } from "react-router-dom";
    
    ReactDOM.createRoot(document.getElementById("root")).render(
      <RouterProvider router={router} />
    );
    

二、编写登录页面

1. 使用antd搭建基本结构

实现步骤

  1. Login/index.js 中创建登录页面基本结构

    import "./index.scss";
    import { Card, Form, Input, Button } from "antd";
    import logo from "../../assets/global.png";
    
    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;
    
    
  2. 在 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: 400px;
            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;
        }
    }
    

启动项目,地址输入登录页面路由,显示如下

image-20240328140322996

2. 实现表单校验功能

实现步骤

  1. 为 Form 组件添加 validateTrigger 属性,指定校验触发时机的集合
  2. 为 Form.Item 组件添加 name 属性,这是为了能取到表单项里面的值
  3. 为 Form.Item 组件添加 rules 属性,用来添加表单校验规则对象,这与elementplus的验证机制高度相似

整体实现代码

const Login = () => {
  return (
    <Form validateTrigger={['onBlur']}>
      <Form.Item
        name="mobile"
        rules={[
          { required: true, message: '请输入手机号' },
          {
            pattern: /^1[3-9]\d{9}$/,
            message: '手机号码格式不对'
          }
        ]}
      >
        <Input size="large" placeholder="请输入手机号" />
      </Form.Item>
      <Form.Item
        name="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>
  )
}

3. 获取登录form的表单数据

实现步骤

  1. 为 Form 组件添加 onFinish 属性,该事件会在点击登录按钮时触发。其实这个onFinish也是button中的submit绑定的,也就是说点击submit按钮时,就会触发onFinish方法
  2. 创建 onFinish 函数,通过函数参数 values 拿到表单值,onFinish函数传递默认参数,参数就是表单内的每一项数据
const onFinish = (formData) => {
    console.log(formData);
  };
....
        <Form validateTrigger={["onBlur"]} onFinish={onFinish}>
          ....
          <Form.Item>
            <Button type="primary" htmlType="submit" size="large" block>
              登录
            </Button>
          </Form.Item>
        </Form>
     ....

export default Login;

4. aixos二次封装

因为需要向后端发起请求,涉及token认证的地方需要设置请求拦截器,也可能需要设置响应拦截器,所以需要对axios二次封装

在此之前,我曾详细记录过如何使用react+redux完成登录页面及token的存取和登录保持,因此,整个登录不再赘述,只上关键过程和重要代码

实现步骤

  1. 安装 axios 到项目
  2. 创建 utils/http.jsx 文件
  3. 创建 axios 实例,配置 baseURL,请求拦截器,响应拦截器

https.jsx

import axios from "axios";

const http = axios.create({
  baseURL: "http://geek.itheima.net/v1_0",
  timeout: 5000,
});

// axios请求拦截器
http.interceptors.request.use(
  (config) => {
    return config;
  },
  (e) => Promise.reject(e)
);

// axios响应式拦截器
http.interceptors.response.use(
  (res) => res.data,
  (e) => {
    console.log(e);
    return Promise.reject(e);
  }
);

export default http;

5. 引入redux管理全局数据

react中的redux就相当于vue中的vuex,都用于管理全局数据,登录时后端返回的token数据就是全局需要的数据

实现步骤

  1. 安装redux相关包

    npm i react-redux @reduxjs/toolkit
    
  2. 配置redux,配置redux在另一篇博客中有详细记录,不再具体说明

    1. 新建user模块store/moduls/user.jsx,填入以下代码
    import { createSlice } from "@reduxjs/toolkit";
    import http from "../../utils/http";
    const userStore = createSlice({
      name: "user",
      // 数据状态
      initialState: {
        token: "",
      },
      // 同步修改方法
      reducers: {
        setToken(state, action) {
          state.userInfo = action.payload;
        },
      },
    });
    
    // 解构出actionCreater
    const { setToken } = userStore.actions;
    
    // 获取reducer函数
    const userReducer = userStore.reducer;
    
    // 异步方法封装
    const fetchLogin = (loginForm) => {
      return async (dispatch) => {
        const res = await http.post("/authorizations", loginForm);
        dispatch(setToken(res.data.token));
      };
    };
    
    export { fetchLogin };
    
    export default userReducer;
    
    
    1. 在index.jsx中注册子模块,store/index.jsx
    import { configureStore } from "@reduxjs/toolkit";
    import userReducer from "./modules/user";
    
    export default configureStore({
        reducer: {
            user: userReducer
        }
    })
    
    1. 入口文件中全局注册store,main.jsx
    import ReactDOM from "react-dom/client";
    import App from "./App.jsx";
    import "./index.scss";
    import { RouterProvider } from "react-router-dom";
    import router from "./router/index.jsx";
    import { Provider } from "react-redux";
    import store from "./store/index.jsx";
    
    ReactDOM.createRoot(document.getElementById("root")).render(
      <Provider store={store}>
        <RouterProvider router={router} />
      </Provider>
    );
    
    

6. 实现登录逻辑

实现步骤

  1. 收集表单信息,向后端发送登录请求
  2. 登录成功后跳转到首页,提示用户登录成功

主要是修改上面的Login/index.jsx中的onFinish方法

如下:

// 省略其他代码
// .......
import { useDispatch } from "react-redux";
import { fetchLogin } from "../../store/modules/user";
import { useNavigate } from "react-router-dom";

// 省略其他代码
// .......
const onFinish = async (formData) => {
 console.log(formData);
 await dispatch(fetchLogin(formData))
 navigate('/')
 message.success('登录成功')
};
// 省略其他代码
// .......

7. 实现token持久化存储

其实就是登录时把token存到localstorage中去,react+redux完成登录页面及token的存取和登录保持–这篇博客中详细记录了,这里只上关键代码

  1. 首先封装token的存、取、删方法,`utils/token.jsx`
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 };

  1. localstorage中持久化存储token,逻辑就是在redux的同步方法中,存储token,同时,token的初始化不再是空值,当localstorage中有token时,就取出来,没有就是空值

    store/moduls/user.jsx

import { createSlice } from "@reduxjs/toolkit";
import http from "../../utils/http";
import { setToken as _setToken, getToken } from "../../utils/token";
const userStore = createSlice({
name: "user",
// 数据状态
initialState: {
 // 差异1
 token: getToken() || "",
},
// 同步修改方法
reducers: {
  setToken(state, action) {
   state.token = action.payload;
   // 存入本地
   _setToken(state.token);
 },
},
});

8. 请求拦截器中携带token

常规操作,在axios二次封装的http.jsx文件中添加以下代码

// axios请求拦截器
http.interceptors.request.use(
  (config) => {
    // 导入getToken方法
    const token = getToken()
    if (token) {
      // 请求头携带token
      config.headers.Authorization = "Bearer " + token;
    }
    return config;
  },
  (e) => Promise.reject(e)
);

9. 路由守卫

vue中的路由守卫是在router中实现的,react的做法是封装 AuthRoute 路由鉴权高阶组件,然后将需要鉴权的页面路由配置,替换为 AuthRoute 组件渲染

实现步骤

  1. 在 components 目录中,创建 AuthRoute/index.jsx 文件
  2. 登录时,直接渲染相应页面组件
  3. 未登录时,重定向到登录页面
  4. 将需要鉴权的页面路由配置,替换为 AuthRoute 组件渲染

AuthRoute/index.jsx中的代码

import { getToken } from '../../utils/token'
import { Navigate } from 'react-router-dom'

const AuthRoute = ({ children }) => {
  const isToken = getToken()
  if (isToken) {
    return <>{children}</>
  } else {
    return <Navigate to="/login" replace />
  }
}

export default AuthRoute

Layout页面需要鉴权,所以在路由中修改页面的渲染配置,router/index.jsx

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;

10. 封装接口调用的api

因为后面涉及多个后端接口调用,所以好的做法是把后端接口进行统一的封装

新建apis/user.jsx文件,用于处理用户相关的接口

import http from "../utils/http";

export const loginAPI = (data) => {
  return http({
    url: "/authorizations",
    method: "POST",
    data,
  });<