学习 vite + REACT 全家桶

vite-react-first

结合实际,用 vite 学习 REACT 全家桶,其中很大部分是来源于这篇文章的实践,改为vite创建,并增加了新版本的兼容。

简易的git使用教程:
Git 全局设置:
git config --global user.name "blackunicorn"
git config --global user.email "blackunicorn@163.com"

创建 git 仓库:
mkdir react-first
cd react-first
git init
touch README.md
git add README.md
git commit -m "first commit"
git remote add origin https://gitee.com/blaunicorn/react-first.git
git push -u origin "master"

已有仓库?
cd existing_git_repo
cd react-first
git init
git remote add origin https://gitee.com/blaunicorn/react-first.git
git add .
git push -u origin "master"

一、使用 vite 创建项目

npm create vite@latest  // 创建vite项目
y  //确定
react-first   //选择项目名称和项目类型
React   // 选择开发框架
JavaScript // 选择语言

cd react-first
npm install
 npm run dev

项目目录大概结构

├─ /public
| └─ vite.svg <-- 网页图标
├─ /src
| ├─ /api <-- api 目录
| | └─ index.js <-- api 库
| ├─ /assets <-- 全局公用目录
| | ├─ /fonts <-- 字体文件目录
| | ├─ /images <-- 图片文件目录
| | ├─ /js <-- 公用 js 文件目录
| | └─ /css <-- 公用样式文件目录
| | | ├─ index.css <-- 全部公用样式(import 本目录其他全部 styl)
| | | ├─ reset.css <-- 清零样式(如果使用 Ant Design,就无需此文件)
| | | └─ global.css <-- 全局公用样式
| ├─ /components <-- 公共模块组件目录
| | ├─ /header <-- 头部导航模块
| | | └─ index.js <-- header 主文件
| | └─ ... <-- 其他模块
| ├─ /pages <-- 页面组件目录
| | ├─ /home.jsx <-- home 页目录
| | ├─ /login.jsx <-- login 页目录
| | └─ ... <-- 其他页面
| ├─ /store <-- 全局状态目录
| | ├─ action.js <-- action 的统一管理
| | ├─ constant.js <-- 常量存储 action type 保持项目中 action type 的一致性
| | ├─ index.js <-- 默认 store 入口
| | └─ reducer.js <-- 数据处理方法
| ├─ /utils <-- 工具类文件夹
| | └─ request.js <-- axios 封装
| ├─ App.css <-- 项目主模块
| ├─ App.jxs <-- 项目主模块
| ├─ index.css <-- 项目主模块
| ├─ main.jsx <-- 项目主模块
|─ .gitignore
|─ index.html
|─ package-lock.json
|─ package.json
|─ README.md
└─ vite.config.js

二、安装 Ant Design,并按需加载

npm install --save react-router-dom
npm install --save antd
npm install -D less
npm i vite-plugin-style-import -D --save
npm i consola -D
配置 vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { createStyleImportPlugin, AntdResolve } from "vite-plugin-style-import";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), createStyleImportPlugin({ resolve: [AntdResolve] })],
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true,
      },
    },
  },
});

Antd 的样式使用了 Less 作为开发语言,需要 antd、less,要在 vite.config.js 中指定 css 的预处理器,为了减小 antd 的 css,变全局引入为按需引入,所以需要安装插件 vite-plugin-style-import ,而这个插件又需要 consola,最后就变成需要 4 个插件。

验证是否引入 antd 成功
// src\App.jsx
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import "./App.css";
import { Button } from "antd";

function App() {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      <Button type="primary">antd按钮</Button>
    </div>
  );
}

export default App;

三、构建 login 页面

// src\pages\login.jsx
import reactLogo from "../assets/react.svg";
import "../assets/css/index.css";
import { Input, Button } from "antd";

function Login() {
  return (
    <div className="p-login">
      <img src={reactLogo} alt="" className="logo" />
      <div className="ipt-con">
        <Input placeholder="账号"></Input>
      </div>
      <div className="ipt-con">
        <Input.Password placeholder="密码"></Input.Password>
      </div>
      <div className="ipt-con">
        <Button type="primary" block={true}>
          登录
        </Button>
      </div>
    </div>
  );
}

export default Login;

四、构建 home 页面

// src\pages\home.jsx
import { Button } from "antd";
import "../assets/css/index.css";
function Home() {
  return (
    <div className="p-home">
      <h1>Home Page</h1>
      <div className="ipt-con">
        <Button>返回登录</Button>
      </div>
    </div>
  );
}

export default Home;

五、编写页面样式,在原模板的集成上修改

/* src\assets\css\index.css */
/*清浮动*/
.clearfix {
  display: block;
}
.clearfix:after {
  content: ".";
  display: block;
  height: 0;
  clear: both;
  visibility: hidden;
}
.G-col-error {
  color: #f81d22;
}
.G-col-succ {
  color: #0b8235;
}

/* Login页 */
/* body {
  justify-content: center;
  align-items: flex-start;
} */

.p-login {
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 100vh;
  background-color: #7adbcb;
}
.p-login .logo {
  display: block;
  height: 6em;
  margin: 150px auto 20px;
}

@keyframes logo-spin {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: no-preference) {
  .logo {
    animation: logo-spin infinite 20s linear;
  }
}

.p-login .ipt-con {
  margin: 0px auto 20px;
  width: 300px;
  text-align: center;
}

/* Home页 */
.p-home {
  position: absolute;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  background: linear-gradient(#f48c8d, #f4c58d);
}
.p-home h1 {
  margin-top: 50px;
  text-align: center;
  color: #fff;
}
.p-home .ipt-con {
  margin: 20px auto 0;
  text-align: center;
}

/* 公共组件Header */
.m-header {
  padding-left: 10px;
  text-align: left;
  height: 40px;
  line-height: 40px;
  font-size: 30px;
  color: #fff;
  background-color: #409eff;
  display: flex;
}
.m-header .logo {
  display: inline;
  height: 30px;
  padding: 5px 5px;
  margin: 0;
}
.m-header a {
  line-height: 40px;
  font-size: 26px;
}

并在页面中引入

import "../assets/css/index.css";

六、路由跳转匹配

在项目主模块App.jsx中引入 react-router-dom
// src\App.jsx
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import "./App.css";
import { Button } from "antd";
import { HashRouter, Route, Routes, Navigate } from "react-router-dom";
import Login from "./pages/login";
import Home from "./pages/home";

function App() {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      {/* 修改为路由配置模式 */}
      {/* 修改为路由配置模式 */}
      <HashRouter>
        <Routes>
          {/* 路由精确匹配"/home",跳转Home页面 */}
          <Route exact path="/home" element={<Home />} />
          {/* 路由精确匹配"/news",跳转News页面 */}
          <Route exact path="/news" element={<News />} />
          {/* 路由精确匹配"/login",跳转Login页面 */}
          <Route exact path="/login" element={<Login />} />
          {/* 未匹配,则跳转Login页面 */}
          <Route path="*" element={<Navigate to="/login" />} />
        </Routes>
      </HashRouter>
    </div>
  );
}

export default App;
在“登录”、“返回首页”按钮中增加 click 事件,实现跳转
// src\pages\login.jsx
import reactLogo from "../assets/react.svg";
import "../assets/css/index.css";
import { Input, Button } from "antd";
import { useNavigate } from "react-router-dom";

function Login() {
  const navigate = useNavigate();

  return (
    <div className="p-login">
      <img src={reactLogo} alt="" className="logo" />
      <div className="ipt-con">
        <Input placeholder="账号"></Input>
      </div>
      <div className="ipt-con">
        <Input.Password placeholder="密码"></Input.Password>
      </div>
      <div className="ipt-con">
        <Button
          type="primary"
          block={true}
          onClick={() => {
            navigate("/home");
          }}
        >
          登录
        </Button>
      </div>
    </div>
  );
}

export default Login;
// src\pages\home.jsx
import { Button } from "antd";
import "../assets/css/index.css";
import { useNavigate } from "react-router-dom";
function Home() {
  const navigate = useNavigate();
  return (
    <div className="p-home">
      <h1>Home Page</h1>
      <div className="ipt-con">
        <Button onClick={() => navigate("/login")}>返回登录</Button>
      </div>
    </div>
  );
}

export default Home;
构建主页的 header 公共组件,包含转跳 NavLink
// src\components\header\index.jsx
import "../../assets/css/index.css";
import reactLogo from "../../assets/react.svg";
import { NavLink } from "react-router-dom";

function Header() {
  return (
    <div className="m-header">
      <img src={reactLogo} alt="" className="logo" />
      网站
      <span>&nbsp; &gt; &nbsp;</span>
      <NavLink className="list-group-item" to="/home">
        Home
      </NavLink>
      <span>&nbsp; | &nbsp;</span>
      <NavLink className="list-group-item" to="/news">
        News
      </NavLink>
    </div>
  );
}

export default Header;
用路由在组件传参

在 React 中,如果使用的是 Class 方式定义的组件:
state 是组件内的数据;
props 用来接收父组件传递来的数据。

1.params参数
    路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
    注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
    接收参数:this.props.match.params
2.search参数
    路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
    注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    接收参数:this.props.location.search
    备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
3.state参数
    路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
    注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    接收参数:this.props.location.state
    备注:刷新也可以保留住参数

如果使用的是 function 方式定义的组件(也叫无状态组件):
使用 useState()管理组件内的数据(hook);
使用 props 接收父组件传递来的数据。
Class 组件有明确的声明周期管理,但是代码相对来说不如无状态组件简洁优雅。
无状态组件通过 hook 管理声明周期,效率更高。因此推荐全程使用无状态组件进行讲解。
通过 news 和 home 分别向 Header 组件传递不同的值,并显示在 Header 组件中。

修改 src\pages\news.jsx:

    ...(略)
  <Header title="news" info={()=>{console.log('info:news')}}/>
    ...(略)

修改 src\pages\home.jsx:


    ...(略)

   <Header title="home" info={()=>{console.log('info:home')}}/>
    ...(略)

修改 src\components\header\index.jsx:

...(略)
  function Header(props) {

      // 接收来自父组件的数据
       const { title, info } = props

       // 如果info存在,则执行info()
      info && info()

      return <div className="m-header">Header:{title}</div>
    }

    export default Header
    ...(略)

运行看下已经生效。

七、安装 redux、react-redux、redux-thunk

Redux 是用来管理项目级别的全局变量,而且是可以实时监听变量的变化并改变 DOM 的。当多个模块都需要动态显示同一个数据,并且这些模块从属于不同的父组件,或者在不同的页面中,如果没有 Redux,那实现起来就很麻烦了,问题追踪也很痛苦。因此 Redux 就是解决这个问题的。

npm install --save redux react-redux redux-thunk
写一个 store

为了更好管理 store 了,一般规范独立建立几个文件放在 store 文件夹下

  • 声明 action 的常量
// src\store\constant.js

/* 该模块适用于定义action对象中type类型的常量值
目的只有一个:便于管理的同时防止单词写错  ,有的也写成action-type.js
 */

export const INCREMENT = 'increment'

export const DECREMENT = 'decrement'

export const SET_OPERATOR = 'setOperator'

export const APP_TOKEN = "APP_TOKEN";
  • 定义 action 操作,所有数据只能通过 action 更新
import * as constants from "./constant";

/*
 *该文件专门为Count组件生成求和count_actions对象
 */

import { INCREMENT, DECREMENT, APP_TOKEN } from "./constant";

export const incrementAction = (data) => ({ type: INCREMENT, data }); //同步action
//同步action,就是指action的返回值为Object类型的一般对象
export function decrementAction(data) {
  return {
    type: DECREMENT,
    data,
  }; //返回的是一个对象,普通数据类型,同步action,返回对象为异步
}
//异步action,就是指action的返回值为函数
//异步action中,一般都会调用同步action,异步action不是必须要用的
export const incrementAsyncAction = (data, time) => {
  return (dispatch) => {
    //返回对象为异步action
    setTimeout(() => {
      dispatch(incrementAction(data));
    }, time);
  };
};

// 更新appToken
export const UpdateAppToken = (data) => {
  return (dispatch) => {
    dispatch({
      type: APP_TOKEN,
      data: data,
    });
  };
};

  • 初始化 state 数据和接受触发 action 的数据
// src\store\reducer.js
// 初始化数据和接受触发action的数据;
import * as constant from "./constant";
import { combineReducers } from "redux";

// 初始默认的state
const defaultState = {
  myData: null,
  isLogin: false,
  AppToken: null,
};

const reducer = (state = defaultState, action) => {
  // 由于state是引用型,不能直接修改,否则是监测不到state发生变化的。因此需要先复制一份进行修改,然后再返回新的state。
  let newState = Object.assign({}, state);
  switch (action.type) {
    case constant.SET_DATA:
      newState.myData = action.payload;
      return newState;
    case constant.APP_TOKEN:
      //在这里可以做一下额外处理,如:本地缓存
      console.log("reducer.js:", newState, action.data);
      if (action.data) {
        sessionStorage.setItem(constant.APP_TOKEN, action.data);
        newState.AppToken = action.data;
      } else {
        sessionStorage.removeItem(constant.APP_TOKEN);
        newState.AppToken = null;
      }
      return newState;
    // break;
    default:
      return state;
  }
};

// export default reducer;

//如果有多个,可以合拼在一起导出
export const reducers = combineReducers({ reducer });
  • 整合处理创建 createStore
// src\store\index.js
// 整合处理创建createStore;
import { applyMiddleware, createStore, compose } from "redux";
import thunk from "redux-thunk";
import { reducers } from "./reducer";

// 这里让项目支持浏览器插件Redux DevTool
const composeEnhancers =
  typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
    : compose;

const enhancer = composeEnhancers(applyMiddleware(thunk));

const store = createStore(reducers, enhancer);

export default store;
在根组件中注入我们写的 store
//src/main.jsx  项目入口文件
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
// import App from './pages/login';
// import App from './pages/home';
import "./index.css";

// 增加store
import store from "./store";
import { Provider } from "react-redux";

ReactDOM.createRoot(document.getElementById("root")).render(
  <Provider store={store}>
    <App />
  </Provider>
);
在类组件中使用store
// src\pages\home.jsx
// 在类组件中使用redux
import { Button } from "antd";
import "../assets/css/index.css";
import { Link, useNavigate } from "react-router-dom";

import Header from "../components/header/index";
import { Component } from "react";
import { UpdateAppToken } from "../store/action";
import { connect } from "react-redux";

class NewsInClass extends Component {
  render() {
    console.log("props:", this.props);
    return (
      <div className="p-home">
        <Header
          title="NewsInClass"
          info={() => {
            console.log("info:NewsInClass");
          }}
        ></Header>
        <h1>NewsInClass Page</h1>
        <div className="ipt-con">
          <Link to={`/login`}>
            <Button>返回登录</Button>
          </Link>
        </div>
        <div>
          <button
            onClick={() =>
              this.props.UpdateAppToken("user-token-from-class-001")
            }
          >
            设置token
          </button>
          <button onClick={() => this.props.UpdateAppToken("")}>
            清空token
          </button>

          <div>token值:{this.props.appToken}</div>
        </div>
      </div>
    );
  }
}

// 没使用store 的时候导出
// export default NewsInClass;

/*connect 有两个参数,
第一个是mapStateToProps 函数,返回是一个对象, 其实就是  store/reducer.js 文件声明导出的那些state数据
第二个参数mapDispatchToProps,对应就是store/action.js 的数据
这两个参数的数据都将映射在组件的props 上面
*/
const mapStateToProps = (state) => {
  console.log("mapStateToProps", state);
  return {
    appToken: state.reducer.AppToken,
  };
};
const mapDispatchToProps = { UpdateAppToken };
export default connect(mapStateToProps, mapDispatchToProps)(NewsInClass);

在函数组件使用

用法和类组件一样,就是用 connect 包装组件,示例:

// src\pages\home.jsx
// 函数式组件;
import { Button } from "antd";
import "../assets/css/index.css";
import { useNavigate } from "react-router-dom";

import Header from "../components/header/index";

import { UpdateAppToken } from "../store/action";
import { connect } from "react-redux";

function News(props) {
  const navigate = useNavigate();
  return (
    <div className="p-home">
      {/* <Header></Header> */}
      <Header
        title="home"
        info={() => {
          console.log("info:news");
        }}
      ></Header>
      <h1>News Page</h1>
      <div className="ipt-con">
        <Button onClick={() => navigate("/login")}>返回登录</Button>
      </div>
      <div>
        <button onClick={() => props.UpdateAppToken("user-token-from-news")}>
          设置token
        </button>
        <button onClick={() => props.UpdateAppToken("")}>清空token</button>

        <div>token值:{props.appToken}</div>
      </div>
    </div>
  );
}

// export default News;

// 有store时
/*connect 有两个参数,
第一个是mapStateToProps 函数,返回是一个对象, 其实就是  store/reducer.js 文件声明导出的那些state数据
第二个参数mapDispatchToProps,对应就是store/action.js 的数据
这两个参数的数据都将映射在组件的props 上面
*/
const mapStateToProps = (state) => {
  console.log("mapStateToProps", state);
  return {
    appToken: state.reducer.AppToken,
  };
};
const mapDispatchToProps = { UpdateAppToken };
export default connect(mapStateToProps, mapDispatchToProps)(News);

至此,最简单的使用就完成了,redux 是一个很好用的数据共享处理的方案,react-reducer 更是简化对 reducer 的操作,将 ui 层和逻辑层的分离,

八、axios 安装

Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js。

npm install --save axios

设置开发环境和生产环境,便于配置环境变量

# .env.development 开发环境

ENV = 'development'

VITE_BASE_URL='/api'
# .env.production 开发环境

ENV = 'production'

VITE_BASE_URL = 'http://xxxxxx/api/'

VITE_BASE_URL 是项目上线后需要请求的服务器接口。

在项目中使用全局变量

node 中是 process.env.变量名
vite 中是 import.meta.env.变量名

// 在配置axios时使用全局baseUrl:
const service = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL,  // node中为  process.env.VITE_BASE_URL
  timeout: 5000
})
用ApiPost 模拟后端数据,比mock更直观

https://v7.apipost.cn/打开apipost网页端,账号密码登入。
在“api 设计”中新建 get 接口,并填写 计划使用的get url:

 http://test.tisuba.com/api-test/movieOnInfoList,

生成 Mock Url 并打开云端 mock,复制到 vite.config.js 中,设置反向代理,url如下:

https://console-mock.apipost.cn/mock/606ced6c-93cf-47e6-a53c-6aa243b4ad0e/api-test/movieOnInfoList

在预定义响应期望中,设置 mock json 数据

{
  "code": 200,
  "message": "OK",
  "data": {
    "movieList|11": [
      {
        "id|+1": 1,
        "title": "@ctitle",
        "age|12-80": 12
      }
    ]
  }
}

访问网页https://console-mock.apipost.cn/mock/606ced6c-93cf-47e6-a53c-6aa243b4ad0e/api-test/movieOnInfoList
即可看到模拟的 json 数据:

{"code":200,"message":"OK","data":{"movieList":[{"id":1,"title":"想音管只关采非","age":50},{"id":2,"title":"三政压市","age":52},{"id":3,"title":"价识完","age":62},{"id":4,"title":"美越件动克少","age":40},{"id":5,"title":"团动离共周从","age":52},{"id":6,"title":"相便点者造火且","age":28},{"id":7,"title":"当低江非合团","age":37},{"id":8,"title":"团身发","age":34},{"id":9,"title":"段划你山了史","age":64},{"id":10,"title":"米展当教","age":78},{"id":11,"title":"条约场立积面王","age":62}]}}
在函数式组件中直接使用 axios
// src\pages\home.jsx
// 函数式组件;
import { Button } from "antd";
import "../assets/css/index.css";
import { useNavigate } from "react-router-dom";

import Header from "../components/header/index";

import { UpdateAppToken } from "../store/action";
import { connect } from "react-redux";
import React, { useState, useEffect } from "react";
import reques from "../utils/request";

function News(props) {
  const navigate = useNavigate();
  // 例如,我的属性名叫content,那么调用其set函数时尽量命名为setContent。当然,叫别的也没问题,但不太规范
  let [movies_hot, setMovies_hot] = useState([]);
  //  函数式组件 useEffect 进行数据请求, 需要进行判断,否则会进行多次数据请求
  useEffect(() => {
    if (movies_hot.length !== 0) return;
    reques
      .get("/movieOnInfoList", {
        params: {
          token: "",
        },
      })
      .then((res) => {
        console.log(res.data.movieList);
        setMovies_hot((movies_hot = res.data.movieList));
      });
  });
  function renderHotItem() {
    return movies_hot.map((item) => (
      <div key={item.id} style={{ textAlign: "left" }}>
        {item.id}--
        {item.title}
      </div>
    ));
  }

  return (
    <div className="p-home">
      {/* <Header></Header> */}
      <Header
        title="home"
        info={() => {
          console.log("info:news");
        }}
      ></Header>
      <h1>News Page</h1>
      <div className="ipt-con">
        <Button onClick={() => navigate("/login")}>返回登录</Button>
      </div>
      <div>
        <button onClick={() => props.UpdateAppToken("user-token-from-news")}>
          设置token
        </button>
        <button onClick={() => props.UpdateAppToken("")}>清空token</button>
        <div>token值:{props.appToken}</div>
        <hr />
        <h3>函数式useEffect 请求后端数据</h3>
        {renderHotItem()}
      </div>
    </div>
  );
}

// export default News;

// 有store时
/*connect 有两个参数,
第一个是mapStateToProps 函数,返回是一个对象, 其实就是  store/reducer.js 文件声明导出的那些state数据
第二个参数mapDispatchToProps,对应就是store/action.js 的数据
这两个参数的数据都将映射在组件的props 上面
*/
const mapStateToProps = (state) => {
  console.log("mapStateToProps", state);
  return {
    appToken: state.reducer.AppToken,
  };
};
const mapDispatchToProps = { UpdateAppToken };
export default connect(mapStateToProps, mapDispatchToProps)(News);

在类组件引入 axios
// src\pages\home.jsx
// 在类组件中使用redux
import { Button } from "antd";
import "../assets/css/index.css";
import { Link, useNavigate } from "react-router-dom";

import Header from "../components/header/index";
import { Component } from "react";
import { UpdateAppToken } from "../store/action";
import { connect } from "react-redux";
import request from "../utils/request";

class NewsInClass extends Component {
  constructor(props) {
    super(props);

    this.state = {
      lists: [],
    };
  }
  async componentDidMount() {
    //  数据请求
    //  先发送数据请求,然后获得数据,然后赋值给我们组件的状态
    const result = await request({
      method: "get",
      url: "/movieOnInfoList",
      params: {
        r: "class/category",
        type: 1,
      },
    });
    this.setState({
      lists: result.data.movieList,
    });
  }

  render() {
    console.log("props:", this.props);
    let { lists } = this.state;
    // map循环遍历
    let listResult = this.state.lists.map(function (value, key) {
      return <li key={key}>{value.title}</li>;
    });
    return (
      <div className="p-home">
        <Header
          title="NewsInClass"
          info={() => {
            console.log("info:NewsInClass");
          }}
        ></Header>
        <h1>NewsInClass Page</h1>
        <div className="ipt-con">
          <Link to={`/login`}>
            <Button>返回登录</Button>
          </Link>
        </div>
        <div>
          <button
            onClick={() =>
              this.props.UpdateAppToken("user-token-from-class-001")
            }
          >
            设置token
          </button>
          <button onClick={() => this.props.UpdateAppToken("")}>
            清空token
          </button>
          <div>token值:{this.props.appToken}</div>
          <hr />
          <h3>直接循环数据</h3>
          {this.state.lists.map(function (value, key) {
            return <li key={key}>{value.title}</li>;
          })}
          <hr />
          <h3>定义变量遍历</h3>
          <ul>{listResult}</ul>
        </div>
      </div>
    );
  }
}

// 没使用store 的时候导出
// export default NewsInClass;

/*connect 有两个参数,
第一个是mapStateToProps 函数,返回是一个对象, 其实就是  store/reducer.js 文件声明导出的那些state数据
第二个参数mapDispatchToProps,对应就是store/action.js 的数据
这两个参数的数据都将映射在组件的props 上面
*/
const mapStateToProps = (state) => {
  console.log("mapStateToProps", state);
  return {
    appToken: state.reducer.AppToken,
  };
};
const mapDispatchToProps = { UpdateAppToken };
export default connect(mapStateToProps, mapDispatchToProps)(NewsInClass);


api的统一管理

项目所有 api 进行统一管理。
实现了统一的 token 验证、登录状态失效报错以及请求错误报错等业务逻辑。

// src\api\index.js
// 进行接口API的统一管理;

import { createHashHistory } from "@remix-run/router";
import request from "../utils/request";
let history = createHashHistory();

// 转跳到组件外
export const goto = (path) => {
  history.push(path);
};

/**
 * @description 用户登录
 * @param {string} account - 用户名
 *        {string} password - 密码
 * @return {HttpResponse} result
 */
export const apiLogin = (data) => {
  return request({
    url: "/login",
    method: "post",
    data: data,
  });
};

在登录页面引入 api,这里没有展示密码的加盐加密过程

// src\pages\login.jsx
import reactLogo from "../assets/react.svg";
import "../assets/css/index.css";
import { Input, Button, message } from "antd";
import { useNavigate } from "react-router-dom";
import { apiLogin } from "../api/index";

import Header from "../components/header/index";
import { useState } from "react";
import { notification } from "antd";

function Login() {
  // 创建钩子函数
  const navigate = useNavigate();

  // 组件中自维护的实时数据
  const [account, setAccount] = useState("");
  const [password, setPassword] = useState("");
  // 登录
  const login = () => {
    const data = {
      account,
      password,
    };
    console.log("data:", data);
    apiLogin(data)
      .then((res) => {
        console.log(res.data);
        if (res.code == 200) {
          notification.open({
            type: "success",
            message: res.message,
            description: "This is the content of the notification.",
            duration: 3,
          });
          window.localStorage.setItem(
            "user-token",
            JSON.stringify({
              nickname: res.data.nickname,
              token: res.data.token,
            })
          );
          navigate("/home");
        }
      })
      .catch((err) => {
        console.log(err);
      });
  };
  return (
    <div className="p-login">
      {/* <Header></Header> */}
      <img src={reactLogo} alt="" className="logo" />
      <div className="ipt-con">
        <Input
          placeholder="账号"
          value={account}
          onChange={(e) => {
            setAccount(e.target.value);
            console.log(account);
          }}
        ></Input>
      </div>
      <div className="ipt-con">
        <Input.Password
          placeholder="密码"
          value={password}
          onChange={(e) => {
            setPassword(e.target.value);
          }}
        ></Input.Password>
      </div>
      <div className="ipt-con">
        <Button
          type="primary"
          block={true}
          onClick={() => {
            navigate("/home");
          }}
        >
          直接跳转
        </Button>

        <Button type="primary" block={true} onClick={login}>
          axios登录
        </Button>
      </div>
    </div>
  );
}

export default Login;

九、build 项目

配置相对路径和@号

// vite.config.js;

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { createStyleImportPlugin, AntdResolve } from "vite-plugin-style-import";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), createStyleImportPlugin({ resolve: [AntdResolve] })],
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true,
      },
    },
  },
  // 设置反向,跨域代理
  // axios({
  //     method: 'get',
  //     url: '/api/user/login'
  // })
  // 这个请求就会被发送去http://localhost:8080/api-test/user/login
  server: {
    proxy: {
      "/api": {
        target:
          "https://console-mock.apipost.cn/mock/606ced6c-93cf-47e6-a53c-6aa243b4ad0e",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, "/api-test"),
      },
    },
  },
  resolve: {
    alias: {
      "@": "/src",
    },
  },
  base: "./", //相对路径
});

打包

npm run build

ps:参考借鉴了很多网上的资料,感谢 csdn 中的各位大神。

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值