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> > </span>
<NavLink className="list-group-item" to="/home">
Home
</NavLink>
<span> | </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 中的各位大神。