likes
comments
collection
share

react自定义应用封装

作者站长头像
站长
· 阅读数 61

基于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进行基础构建

yarn create react-app my-app

# 运行项目, 默认生成一个3000端口的页面
cd my-app
yarn start

得到一个官方标准的文件结构:

.
├── 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. 配置环境变量

增加路由模块

安装路由

yarn add react-router-dom

修改APP.js,配置路由组件:

import { BrowserRouter } from "react-router-dom";
import AppRoutes from "./routes";
function App() {
  return (
    <BrowserRouter>
      <AppRoutes />
    </BrowserRouter>
  );
}
export default App;

创建routes目录存储路由:

# 创建目录
mkdir routes && cd routes
# 新建index.js文件
touch index.js

路由配置列表和详情页:

import { Routes, Route, Navigate } from 'react-router-dom';
import List from "../pages/List";
import Detail from "../pages/Detail";

const AppRoutes = () => {
  return (
    <Routes>
      <Route path="/" exact element={<Navigate to="/list" replace />} />
      <Route path="/list" exact Component={List} />
      <Route path="/detail/:id" exact Component={Detail} />
    </Routes>
  );
};

export default AppRoutes;

src下新增page目录存储页面如下:

# 创建页面根目录
mkdir pages && cd pages
# 创建列表和详情页
mkdir List Detail
# 创建页面和样式
cd List
touch index.jsx
cd Detail
touch index.jsx

# 目录结构
pages
├── Detail
│   ├── index.jsx
└── List
  ├── index.jsx

列表页:

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 (
    <div>
      {listData.map((item) => (
        <div key={item.id}>
          <div style={{ color: "blue" }} onClick={() => goToDetail(item.id)}>
            {item.title}
          </div>
          <div>{item.date}</div>
        </div>
      ))}
    </div>
  );
};

export default List;

详情页:

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 (
    <div>
      {detail ? (
        <div>
          <div>{detail.title}</div>
          <div>{detail.date}</div>
          <div>{detail.content}</div>
        </div>
      ) : (
        <div>新闻不存在</div>
      )}
    </div>
  );
};

export default Detail;

增加样式支持

这里使用less来做样式支持,也支持css modules

# 使用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

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:

# 引入antd
yarn add antd
# 引入craco-alias起别名
yarn add craco-alias --dev

修改craco.config.js增加起别名配置

const CracoAliasPlugin = require('craco-alias');

module.exports = {
  plugins: [
    {
      plugin: CracoAliasPlugin,
      options: {
        aliases: { 
          '@': 'src'
        }
      },
    },
    // ...
  ],
  // ...
};

修改package.json更新scripts部分,使用 craco 替换 react-scripts

{
    "scripts": {
       "start": "craco start",
       "build": "craco build",
       "test": "craco test",
       // ...其他脚本保持不变
    },
},

列表页美化 修改List/index.jsx:

import { Card, Button } from "antd";
import styles from "./index.module.less";
// ...引入其他组件

const List = () => {
  // ...js部分
  return (
    <div className={styles.wrapper}>
      {listData.map((item) => (
        <Card
          key={item.id}
          title={item.title}
          extra={<div>{item.date}</div>}
          style={{ marginBottom: 20 }}
        >
          {item.desc}...
          <Button type="link" onClick={() => goToDetail(item.id)}>
            More
          </Button>
        </Card>
      ))}
    </div>
  );
};

export default List;

新建List/index.module.less:

.wrapper {
  width: 600px;
  margin: 100px auto;
}

详情页美化 修改Detail/index.jsx:

import styles from "./index.module.less";
// ...引入其他组件
const Detail = () => {
  // ...js部分
  return (
    <div>
      {detail ? (
        <div className={styles.container}>
          <div className={styles.title}>{detail.title}</div>
          <div className={styles.date}>{detail.date}</div>
          <div className={styles.content}>{detail.content}</div>
        </div>
      ) : (
        <div>新闻不存在</div>
      )}
    </div>
  );
};

export default Detail;

新建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

yarn add axios

src下新建http.js

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 Promise.reject(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:", msg);
    message.error(msg);
    return Promise.reject(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:

import { post } from '../http';

/**
 * 调用某个接口
 * @param {*} params 
 * @returns 
 */
export const getSomeData = (params) => {
  let url = 'aaa/bbb';
  return post(url, params);
}

封装共享状态库

使用mobx作为我们的共享状态库是因为mobx基于观察者模式,使用简单,几乎无侵入性。通过自动追踪依赖来更新视图,使得状态管理更加直观和容易。特别适合中型项目或对性能要求较高的应用,比redux更轻量。

安装mobx

yarn add mobx mobx-react mobx-react-lite

src下面新建store文件目录,并创建一个counterStore.js文件:

cd src && mkdir store
cd store && touch counterStore.js

编辑counterStore.js文件:

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页面使用

import { Card, Button } from "antd";
import { observer } from "mobx-react-lite";
import counterStore from "@/store/counterStore";
// ...其他引入

const List = () => {
  // ...其他逻辑
  return (
    <div className={styles.wrapper}>
      // 其他UI
      <div>
        mobx 案例 Count: {counterStore.count}
        <Button
          style={{ marginLeft: 5, marginRight: 5 }}
          type="primary"
          onClick={() => counterStore.increment()}
        >
          +
        </Button>
        <Button type="primary" onClick={() => counterStore.decrement()}>
          -
        </Button>
      </div>
    </div>
  );
};

// 这里要注意用observer包一下
export default observer(List);

配置变量环境

我们在实际开发环境一般分为测试、UAT、准生产、生产等多个环境,调用不同的服务地址。通过配置环境变量可以自动打包到对应环境

安装依赖

yarn add dotenv-webpack --dev

修改craco.config.js

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:

// .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:

{
    "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的配置:

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:

import { baseURL } from   "@/config";

// 设置基础URL
axios.defaults.baseURL = baseURL;

// ...

案例地址

地址:gitee.com/cangnaiwen/…

转载自:https://juejin.cn/post/7375810931097976895
评论
请登录