# 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