# cnw-public-react-app
**Repository Path**: cangnaiwen/cnw-public-react-app
## Basic Information
- **Project Name**: cnw-public-react-app
- **Description**: 本文是基于 create-react-app搭建自定义应用,配置路由、less、axios请求封装和变量环境等...
- **Primary Language**: Unknown
- **License**: BSD-3-Clause
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-06-03
- **Last Updated**: 2024-07-05
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 基于create-react-app搭建自定义应用
> 本文是基于 create-react-app搭建自定义应用,配置路由、less、axios请求封装和变量环境等...
> 供自己以后查漏补缺,也欢迎同道朋友交流学习。
## 环境准备
- node版本:v18.12.0
- yarn版本:v1.22.17
## react相关应用版本
- "react":"^18.3.1"
- "react-scripts": "5.0.1"
- "react-router-dom": "^6.23.1"
- "@craco/craco": "^7.1.0"
- "antd": "^5.17.4"
- "mobx": "^6.12.3"
- "mobx-react": "^9.1.1"
## 基础搭建
使用yarn进行基础构建
```bash
yarn create react-app my-app
# 运行项目, 默认生成一个3000端口的页面
cd my-app
yarn start
```
得到一个官方标准的文件结构:
```bash
.
├── README.md
├── package.json
├── public
├── src
│ ├── App.css // css可以清空
│ ├── App.js
│ ├── App.test.js // 没有单元测试可以删除
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js // 不用也可以删除
│ └── setupTests.js // 不用也可以删除
└── yarn.lock
```
## 官网案例的缺陷
这个文件目录可以看出来,官方案例只能做一个简单的demo,不能作为一个企业标准应用,缺失很多:
1. 没有路由模块
2. 没有less(或Sass)样式支持
3. 没有UI组件库
4. 没有封装请求库
5. 没有状态共享库
6. 配置环境变量
## 增加路由模块
安装路由
```bash
yarn add react-router-dom
```
修改APP.js,配置路由组件:
```javascript
import { BrowserRouter } from "react-router-dom";
import AppRoutes from "./routes";
function App() {
return (
);
}
export default App;
```
创建routes目录存储路由:
```bash
# 创建目录
mkdir routes && cd routes
# 新建index.js文件
touch index.js
```
路由配置列表和详情页:
```javascript
import { Routes, Route, Navigate } from 'react-router-dom';
import List from "../pages/List";
import Detail from "../pages/Detail";
const AppRoutes = () => {
return (
} />
);
};
export default AppRoutes;
```
src下新增page目录存储页面如下:
```bash
# 创建页面根目录
mkdir pages && cd pages
# 创建列表和详情页
mkdir List Detail
# 创建页面和样式
cd List
touch index.jsx
cd Detail
touch index.jsx
# 目录结构
pages
├── Detail
│ ├── index.jsx
└── List
├── index.jsx
```
列表页:
```javascript
import { useState, useEffect } from "react";
import { useNavigate } from 'react-router-dom';
const List = () => {
const navigate = useNavigate();
const [listData, setListData] = useState([]);
useEffect(() => {
setListData([
{ id: 1, title: "新闻标题11111", date: "2024-05-30 11:01:00" },
{ id: 2, title: "新闻标题22222", date: "2024-05-30 12:02:00" },
{ id: 3, title: "新闻标题33333", date: "2024-05-30 13:03:00" },
]);
}, []);
const goToDetail = (id) => {
navigate(`/detail/${id}`)
};
return (
{listData.map((item) => (
goToDetail(item.id)}>
{item.title}
{item.date}
))}
);
};
export default List;
```
详情页:
```javascript
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
const detailData = {
1: {
id: 1,
title: "新闻标题11111",
date: "2024-05-30 11:01:00",
content: "新闻内容11111",
},
2: {
id: 2,
title: "新闻标题22222",
date: "2024-05-30 12:02:00",
content: "新闻内容22222",
},
3: {
id: 3,
title: "新闻标题33333",
date: "2024-05-30 13:03:00",
content: "新闻内容33333",
},
};
const Detail = () => {
const { id } = useParams();
const [detail, setDetail] = useState(null);
useEffect(() => {
setDetail(detailData[id] || null);
}, [id]);
return (
{detail ? (
{detail.title}
{detail.date}
{detail.content}
) : (
新闻不存在
)}
);
};
export default Detail;
```
## 增加样式支持
这里使用less来做样式支持,也支持css modules
```bash
# 使用craco来修改webpack的配置:首先,你需要安装 @craco/craco 包
yarn add @craco/craco
# 安装 craco-less 插件以支持 Less
yarn add craco-less less less-loader --dev
```
根目录配置`craco.config.js`文件以支持`craco-less`,同时也配置css modules
```javascript
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' }, // 自定义Ant Design主题变量,可选
javascriptEnabled: true,
},
},
},
},
],
style: {
modules: true,
// 其他样式配置,比如postcss等...
}
};
```
## 安装UI组件库
这里引入antd5和使用刚配置好的less美化页面UI:
```bash
# 引入antd
yarn add antd
# 引入craco-alias起别名
yarn add craco-alias --dev
```
修改`craco.config.js`增加起别名配置
```javascript
const CracoAliasPlugin = require('craco-alias');
module.exports = {
plugins: [
{
plugin: CracoAliasPlugin,
options: {
aliases: {
'@': 'src'
}
},
},
// ...
],
// ...
};
```
修改`package.json`更新`scripts`部分,使用 `craco` 替换 `react-scripts`:
```json
{
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
// ...其他脚本保持不变
},
},
```
**列表页美化**
修改`List/index.jsx`:
```javascript
import { Card, Button } from "antd";
import styles from "./index.module.less";
// ...引入其他组件
const List = () => {
// ...js部分
return (
{listData.map((item) => (
{item.date}
}
style={{ marginBottom: 20 }}
>
{item.desc}...
))}
);
};
export default List;
```
新建`List/index.module.less`:
```less
.wrapper {
width: 600px;
margin: 100px auto;
}
```
**详情页美化**
修改`Detail/index.jsx`:
```javascript
import styles from "./index.module.less";
// ...引入其他组件
const Detail = () => {
// ...js部分
return (
{detail ? (
{detail.title}
{detail.date}
{detail.content}
) : (
新闻不存在
)}
);
};
export default Detail;
```
新建`Detail/index.module.less`:
```less
.container {
width: 800px;
margin: 100px auto;
.title {
font-size: 20px;
font-weight: 600;
padding: 5px 0;
border-bottom: 1px solid #666;
margin-bottom: 20px;
}
.date {
font-size: 12px;
color: #999;
margin-bottom: 20px;
}
}
```
## 封装请求库
安装axios
```bash
yarn add axios
```
src下新建`http.js`
```javascript
import axios from "axios";
import { message } from "antd";
const baseURL = "xxx";
// 设置基础URL
axios.defaults.baseURL = baseURL;
// 创建一个 Axios 实例
const instance = axios.create({
// 可以在这里设置默认的 headers、超时时间等
timeout: 10000,
});
// 请求拦截器,可以用来添加token等操作
instance.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么,比如添加认证信息
// config.headers.Authorization = `Bearer ${yourToken}`;
return config;
},
(error) => {
// 对请求错误做些什么
return error
}
);
// 响应拦截器,统一处理错误
instance.interceptors.response.use(
async (response) => {
// 非200的错误处理
if (response?.data?.code !== "200") {
message.error(response?.data?.msg);
return response.data;
}
return response?.data?.result || null;
},
(error) => {
// 处理错误响应,比如弹出错误提示、跳转登录页等
const msg = error.response ? error.response.data : error.message;
console.error("Axios error:", error);
message.error(msg);
return error
}
);
// 封装 GET 请求
export const get = (url, params) => instance.get(url, { params });
// 封装 POST 请求
export const post = (url, data, config) => instance.post(url, data, config);
```
src新建`service`目录,目录里新建`index.js`:
```javascript
import { post } from '../http';
/**
* 调用某个接口
* @param {*} params
* @returns
*/
export const getSomeData = (params) => {
let url = 'aaa/bbb';
return post(url, params);
}
```
## 封装共享状态库
使用`mobx`作为我们的共享状态库是因为mobx基于观察者模式,使用简单,几乎无侵入性。通过自动追踪依赖来更新视图,使得状态管理更加直观和容易。特别适合中型项目或对性能要求较高的应用,比`redux`更轻量。
安装mobx
```bash
yarn add mobx mobx-react mobx-react-lite
```
src下面新建`store`文件目录,并创建一个`counterStore.js`文件:
```bash
cd src && mkdir store
cd store && touch counterStore.js
```
编辑`counterStore.js`文件:
```javascript
import { makeObservable, observable, action } from "mobx";
class CounterStore {
count = 0;
constructor() {
makeObservable(this, {
count: observable,
increment: action,
decrement: action,
});
}
increment() {
this.count++;
}
decrement() {
this.count--;
}
}
const counterStore = new CounterStore();
export default counterStore;
```
在`List/index.jsx`页面使用
```javascript
import { Card, Button } from "antd";
import { observer } from "mobx-react-lite";
import counterStore from "@/store/counterStore";
// ...其他引入
const List = () => {
// ...其他逻辑
return (
// 其他UI
mobx 案例 Count: {counterStore.count}
);
};
// 这里要注意用observer包一下
export default observer(List);
```
## 配置变量环境
我们在实际开发环境一般分为测试、UAT、准生产、生产等多个环境,调用不同的服务地址。通过配置环境变量可以自动打包到对应环境
安装依赖
```bash
yarn add dotenv-webpack --dev
```
修改`craco.config.js`:
```javascript
const Dotenv = require('dotenv-webpack');
module.exports = {
plugins: [
{
plugin: Dotenv,
// 动态加载 .env 文件
options: {
path: `.env.${process.env.REACT_APP_ENV}`, // 根据 process.env.NODE_ENV 动态加载 .env 文件
},
},
// ...
],
};
```
配置.env文件,根目录新建`.env.development`,`.env.test`,`.env.uat`,`.env.production`:
```javascript
// .env.development文件里配置
REACT_APP_ENV=development
// .env.test文件里配置
REACT_APP_ENV=test
// .env.uat文件里配置
REACT_APP_ENV=uat
// .env.production文件里配置
REACT_APP_ENV=production
```
修改`package.json`:
```json
{
"scripts": {
"build:test": "REACT_APP_ENV=test craco build",
"build:uat": "REACT_APP_ENV=uat craco build",
"build:prod": "REACT_APP_ENV=production craco build",
// ...其他命令
},
// ...
}
```
src下新建`config.js`做baseURL的配置:
```javascript
const apiMap = {
development: "https://xxx-test.xxx.com",
test: "https://xxx-test.xxx.com",
uat: "https://xxx-uat.xxx.com",
production: "https://xxx-uat.prod.com",
};
console.log("@@@ process.env.REACT_APP_ENV", process.env.REACT_APP_ENV);
export const env = process.env.REACT_APP_ENV || "test";
export const baseURL = apiMap[env];
```
修改`http.js`替换掉之前写死的baseURL:
```javascript
import { baseURL } from "@/config";
// 设置基础URL
axios.defaults.baseURL = baseURL;
// ...
```
## 案例地址
地址:https://gitee.com/cangnaiwen/cnw-public-react-app