react自定义应用封装
基于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进行基础构建
1yarn create react-app my-app
2
3# 运行项目, 默认生成一个3000端口的页面
4cd my-app
5yarn start
6
得到一个官方标准的文件结构:
1.
2├── README.md
3├── package.json
4├── public
5├── src
6│ ├── App.css // css可以清空
7│ ├── App.js
8│ ├── App.test.js // 没有单元测试可以删除
9│ ├── index.css
10│ ├── index.js
11│ ├── logo.svg
12│ ├── reportWebVitals.js // 不用也可以删除
13│ └── setupTests.js // 不用也可以删除
14└── yarn.lock
15
官网案例的缺陷
这个文件目录可以看出来,官方案例只能做一个简单的demo,不能作为一个企业标准应用,缺失很多:
- 没有路由模块
- 没有less(或Sass)样式支持
- 没有UI组件库
- 没有封装请求库
- 没有状态共享库
- 配置环境变量
增加路由模块
安装路由
1yarn add react-router-dom
2
修改APP.js,配置路由组件:
1import { BrowserRouter } from "react-router-dom";
2import AppRoutes from "./routes";
3function App() {
4 return (
5 <BrowserRouter>
6 <AppRoutes />
7 </BrowserRouter>
8 );
9}
10export default App;
11
创建routes目录存储路由:
1# 创建目录
2mkdir routes && cd routes
3# 新建index.js文件
4touch index.js
5
路由配置列表和详情页:
1import { Routes, Route, Navigate } from 'react-router-dom';
2import List from "../pages/List";
3import Detail from "../pages/Detail";
4
5const AppRoutes = () => {
6 return (
7 <Routes>
8 <Route path="/" exact element={<Navigate to="/list" replace />} />
9 <Route path="/list" exact Component={List} />
10 <Route path="/detail/:id" exact Component={Detail} />
11 </Routes>
12 );
13};
14
15export default AppRoutes;
16
src下新增page目录存储页面如下:
1# 创建页面根目录
2mkdir pages && cd pages
3# 创建列表和详情页
4mkdir List Detail
5# 创建页面和样式
6cd List
7touch index.jsx
8cd Detail
9touch index.jsx
10
11# 目录结构
12pages
13├── Detail
14│ ├── index.jsx
15└── List
16 ├── index.jsx
17
列表页:
1import { useState, useEffect } from "react";
2import { useNavigate } from 'react-router-dom';
3
4const List = () => {
5 const navigate = useNavigate();
6 const [listData, setListData] = useState([]);
7
8 useEffect(() => {
9 setListData([
10 { id: 1, title: "新闻标题11111", date: "2024-05-30 11:01:00" },
11 { id: 2, title: "新闻标题22222", date: "2024-05-30 12:02:00" },
12 { id: 3, title: "新闻标题33333", date: "2024-05-30 13:03:00" },
13 ]);
14 }, []);
15
16 const goToDetail = (id) => {
17 navigate(`/detail/${id}`)
18 };
19
20 return (
21 <div>
22 {listData.map((item) => (
23 <div key={item.id}>
24 <div style={{ color: "blue" }} onClick={() => goToDetail(item.id)}>
25 {item.title}
26 </div>
27 <div>{item.date}</div>
28 </div>
29 ))}
30 </div>
31 );
32};
33
34export default List;
35
详情页:
1import { useState, useEffect } from "react";
2import { useParams } from "react-router-dom";
3
4const detailData = {
5 1: {
6 id: 1,
7 title: "新闻标题11111",
8 date: "2024-05-30 11:01:00",
9 content: "新闻内容11111",
10 },
11 2: {
12 id: 2,
13 title: "新闻标题22222",
14 date: "2024-05-30 12:02:00",
15 content: "新闻内容22222",
16 },
17 3: {
18 id: 3,
19 title: "新闻标题33333",
20 date: "2024-05-30 13:03:00",
21 content: "新闻内容33333",
22 },
23};
24
25const Detail = () => {
26 const { id } = useParams();
27 const [detail, setDetail] = useState(null);
28
29 useEffect(() => {
30 setDetail(detailData[id] || null);
31 }, [id]);
32
33 return (
34 <div>
35 {detail ? (
36 <div>
37 <div>{detail.title}</div>
38 <div>{detail.date}</div>
39 <div>{detail.content}</div>
40 </div>
41 ) : (
42 <div>新闻不存在</div>
43 )}
44 </div>
45 );
46};
47
48export default Detail;
49
增加样式支持
这里使用less来做样式支持,也支持css modules
1# 使用craco来修改webpack的配置:首先,你需要安装 @craco/craco 包
2yarn add @craco/craco
3
4# 安装 craco-less 插件以支持 Less
5yarn add craco-less less less-loader --dev
6
根目录配置 craco.config.js
文件以支持 craco-less
,同时也配置css modules
1const CracoLessPlugin = require('craco-less');
2
3module.exports = {
4 plugins: [
5 {
6 plugin: CracoLessPlugin,
7 options: {
8 lessLoaderOptions: {
9 lessOptions: {
10 modifyVars: { '@primary-color': '#1DA57A' }, // 自定义Ant Design主题变量,可选
11 javascriptEnabled: true,
12 },
13 },
14 },
15 },
16 ],
17 style: {
18 modules: true,
19 // 其他样式配置,比如postcss等...
20 }
21};
22
安装UI组件库
这里引入antd5和使用刚配置好的less美化页面UI:
1# 引入antd
2yarn add antd
3# 引入craco-alias起别名
4yarn add craco-alias --dev
5
修改 craco.config.js
增加起别名配置
1const CracoAliasPlugin = require('craco-alias');
2
3module.exports = {
4 plugins: [
5 {
6 plugin: CracoAliasPlugin,
7 options: {
8 aliases: {
9 '@': 'src'
10 }
11 },
12 },
13 // ...
14 ],
15 // ...
16};
17
修改 package.json
更新 scripts
部分,使用 craco
替换 react-scripts
:
1{
2 "scripts": {
3 "start": "craco start",
4 "build": "craco build",
5 "test": "craco test",
6 // ...其他脚本保持不变
7 },
8},
9
列表页美化
修改 List/index.jsx
:
1import { Card, Button } from "antd";
2import styles from "./index.module.less";
3// ...引入其他组件
4
5const List = () => {
6 // ...js部分
7 return (
8 <div className={styles.wrapper}>
9 {listData.map((item) => (
10 <Card
11 key={item.id}
12 title={item.title}
13 extra={<div>{item.date}</div>}
14 style={{ marginBottom: 20 }}
15 >
16 {item.desc}...
17 <Button type="link" onClick={() => goToDetail(item.id)}>
18 More
19 </Button>
20 </Card>
21 ))}
22 </div>
23 );
24};
25
26export default List;
27
新建 List/index.module.less
:
.wrapper {
width: 600px;
margin: 100px auto;
}
详情页美化
修改 Detail/index.jsx
:
1import styles from "./index.module.less";
2// ...引入其他组件
3const Detail = () => {
4 // ...js部分
5 return (
6 <div>
7 {detail ? (
8 <div className={styles.container}>
9 <div className={styles.title}>{detail.title}</div>
10 <div className={styles.date}>{detail.date}</div>
11 <div className={styles.content}>{detail.content}</div>
12 </div>
13 ) : (
14 <div>新闻不存在</div>
15 )}
16 </div>
17 );
18};
19
20export default Detail;
21
新建 Detail/index.module.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
1yarn add axios
2
src下新建 http.js
1import axios from "axios";
2import { message } from "antd";
3
4const baseURL = "xxx";
5// 设置基础URL
6axios.defaults.baseURL = baseURL;
7
8// 创建一个 Axios 实例
9const instance = axios.create({
10 // 可以在这里设置默认的 headers、超时时间等
11 timeout: 10000,
12});
13
14// 请求拦截器,可以用来添加token等操作
15instance.interceptors.request.use(
16 (config) => {
17 // 在发送请求之前做些什么,比如添加认证信息
18 // config.headers.Authorization = `Bearer ${yourToken}`;
19 return config;
20 },
21 (error) => {
22 // 对请求错误做些什么
23 return Promise.reject(error);
24 }
25);
26
27// 响应拦截器,统一处理错误
28instance.interceptors.response.use(
29 async (response) => {
30 // 非200的错误处理
31 if (response?.data?.code !== "200") {
32 message.error(response?.data?.msg);
33 return response.data;
34 }
35
36 return response?.data?.result || null;
37 },
38 (error) => {
39 // 处理错误响应,比如弹出错误提示、跳转登录页等
40 const msg = error.response ? error.response.data : error.message;
41 console.error("Axios error:", msg);
42 message.error(msg);
43 return Promise.reject(error);
44 }
45);
46
47// 封装 GET 请求
48export const get = (url, params) => instance.get(url, { params });
49
50// 封装 POST 请求
51export const post = (url, data, config) => instance.post(url, data, config);
52
src新建 service
目录,目录里新建 index.js
:
1import { post } from '../http';
2
3/**
4 * 调用某个接口
5 * @param {*} params
6 * @returns
7 */
8export const getSomeData = (params) => {
9 let url = 'aaa/bbb';
10 return post(url, params);
11}
12
封装共享状态库
使用 mobx
作为我们的共享状态库是因为mobx基于观察者模式,使用简单,几乎无侵入性。通过自动追踪依赖来更新视图,使得状态管理更加直观和容易。特别适合中型项目或对性能要求较高的应用,比 redux
更轻量。
安装mobx
1yarn add mobx mobx-react mobx-react-lite
2
src下面新建 store
文件目录,并创建一个 counterStore.js
文件:
1cd src && mkdir store
2cd store && touch counterStore.js
3
编辑 counterStore.js
文件:
1import { makeObservable, observable, action } from "mobx";
2
3class CounterStore {
4 count = 0;
5
6 constructor() {
7 makeObservable(this, {
8 count: observable,
9 increment: action,
10 decrement: action,
11 });
12 }
13
14 increment() {
15 this.count++;
16 }
17
18 decrement() {
19 this.count--;
20 }
21}
22
23const counterStore = new CounterStore();
24
25export default counterStore;
26
在 List/index.jsx
页面使用
1import { Card, Button } from "antd";
2import { observer } from "mobx-react-lite";
3import counterStore from "@/store/counterStore";
4// ...其他引入
5
6const List = () => {
7 // ...其他逻辑
8 return (
9 <div className={styles.wrapper}>
10 // 其他UI
11 <div>
12 mobx 案例 Count: {counterStore.count}
13 <Button
14 style={{ marginLeft: 5, marginRight: 5 }}
15 type="primary"
16 onClick={() => counterStore.increment()}
17 >
18 +
19 </Button>
20 <Button type="primary" onClick={() => counterStore.decrement()}>
21 -
22 </Button>
23 </div>
24 </div>
25 );
26};
27
28// 这里要注意用observer包一下
29export default observer(List);
30
配置变量环境
我们在实际开发环境一般分为测试、UAT、准生产、生产等多个环境,调用不同的服务地址。通过配置环境变量可以自动打包到对应环境
安装依赖
1yarn add dotenv-webpack --dev
2
修改 craco.config.js
:
1const Dotenv = require('dotenv-webpack');
2
3module.exports = {
4 plugins: [
5 {
6 plugin: Dotenv,
7 // 动态加载 .env 文件
8 options: {
9 path: `.env.${process.env.REACT_APP_ENV}`, // 根据 process.env.NODE_ENV 动态加载 .env 文件
10 },
11 },
12 // ...
13 ],
14};
15
配置.env文件,根目录新建 .env.development
, .env.test
, .env.uat
, .env.production
:
1// .env.development文件里配置
2REACT_APP_ENV=development
3
4// .env.test文件里配置
5REACT_APP_ENV=test
6
7// .env.uat文件里配置
8REACT_APP_ENV=uat
9
10// .env.production文件里配置
11REACT_APP_ENV=production
12
修改 package.json
:
1{
2 "scripts": {
3 "build:test": "REACT_APP_ENV=test craco build",
4 "build:uat": "REACT_APP_ENV=uat craco build",
5 "build:prod": "REACT_APP_ENV=production craco build",
6 // ...其他命令
7 },
8 // ...
9}
10
src下新建 config.js
做baseURL的配置:
1const apiMap = {
2 development: "https://xxx-test.xxx.com",
3 test: "https://xxx-test.xxx.com",
4 uat: "https://xxx-uat.xxx.com",
5 production: "https://xxx-uat.prod.com",
6};
7
8console.log("@@@ process.env.REACT_APP_ENV", process.env.REACT_APP_ENV);
9
10export const env = process.env.REACT_APP_ENV || "test";
11export const baseURL = apiMap[env];
12
修改 http.js
替换掉之前写死的baseURL:
1import { baseURL } from "@/config";
2
3// 设置基础URL
4axios.defaults.baseURL = baseURL;
5
6// ...
7