# react-frame
**Repository Path**: wlz-00/react-frame
## Basic Information
- **Project Name**: react-frame
- **Description**: 本项目框架基于react18 + vite3 + tailwindcss3 + ahooks + antd 搭建的
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 3
- **Forks**: 1
- **Created**: 2022-09-21
- **Last Updated**: 2025-08-04
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 日语桑
本项目基于react18 + vite3 + tailwindcss3 + ahooks + antd 搭建的
## 使用教程
- [vitejs](https://cn.vitejs.dev/guide/)
- [tailwindcss](https://www.tailwindcss.cn/docs/)
- [eslint](https://github.com/eslint/eslint)
- [ahooks](https://ahooks.js.org/zh-CN)
- [antd](https://ant.design/index-cn)
- [prettier](https://prettier.io/docs/en/index.html)
# 搭建步骤
## 1. 用 Vite 生成一个 React + TypeScript 项目
```
pnpm create vite my-react-app --template react-ts
```
### 1.1 修改vite.config配置文件 配置别名
vite.config.ts:
```tsx
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias:{
"@":path.resolve(__dirname,'./src')//配置@别名
}
},
})
```
### 1.2 为了ts不报错需要配置 tsconfig.json
```json
{
"compilerOptions": {
"target": "ESNext",// 指定ECMAScript目标版本
"useDefineForClassFields": true,//此标志用作迁移到即将推出的类字段标准版本的一部分
"lib": ["DOM", "DOM.Iterable", "ESNext"],//用于指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载
"allowJs": false, // 允许 ts 编译器编译 js 文件
"skipLibCheck": true, // 跳过声明文件的类型检查
"esModuleInterop": false,// es 模块互操作,屏蔽 ESModule和CommonJS之间的差异
"allowSyntheticDefaultImports": true, // 允许通过import x from 'y' 即使模块没有显式指定 default 导出
"strict": true,//true => 同时开启 alwaysStrict, noImplicitAny, noImplicitThis 和 strictNullChecks
"forceConsistentCasingInFileNames": true, // 对文件名称强制区分大小写
"module": "ESNext",// 指定生成哪个模块系统代码
"moduleResolution": "Node",// 模块解析(查找)策略
"resolveJsonModule": true,// 防止 ts文件中引入json文件,会报如下红色波浪线
"isolatedModules": true,// 是否将没有 import/export 的文件视为旧(全局而非模块化)脚本文件。
"noEmit": true, // 编译时不生成任何文件(只进行类型检查)
"jsx": "react-jsx", // 指定将 JSX 编译成什么形式
"baseUrl": "./src",//配置paths前先配置baseUrl
"paths": {
"@/*": ["*"], // 模块名到基于 baseUrl的路径映射的列表
},
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
```
> 如找不到path或找不到__dirname等
> Tips:如图报node自带模块的错误,需要安装 @types/node
```
pnpm add @types/node --save-dev
```
## 2.路由 react-router-dom@6配置
### 2.1 安装
```
pnpm add react-router-dom@6 --save-dev
```
### 2.2 使用
在根文件main.tsx里面 修改 在app外层用BrowserRouter包裹
```tsx
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
);
```
### 2.3 在router文件夹下创建index.tsx
```tsx
import { Routes, Route } from "react-router-dom";
import {lazy} from "react";
const Home = lazy(() => import("@/pages/home"))
const Login = lazy(() => import("@/pages/login"))
function RootRoute() :JSX.Element{
return (
<>
} />
} />
>
);
}
export default RootRoute
```
### 2.4 在app.tsx 引入router文件即可
```tsx
import RootRoute from './router'
import './App.css'
function App() {
return (
)
}
export default App
```
## 3. 安装 Ant Design 相关依赖
```
pnpm add antd @ant-design/icons
pnpm add -D less vite-plugin-imp # 用于按需引入组
```
### 3.1 修改 vite.config.ts 为如下内容:
```ts
import { defineConfig } from 'vite';
import vitePluginImp from 'vite-plugin-imp';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
vitePluginImp({
optimize: true,
libList: [
{
libName: 'antd',
style: (name) => `antd/es/${name}/style`,
},
],
}),
],
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
// 如需定制 antd 主题,请取消以下内容注释 https://ant.design/docs/react/customize-theme
// modifyVars: {
// hack: `true; @import "./src/theme.less";`,
// },
},
},
},
});
```
## 4 封装axios
```
pnpm add axios -S
```
### 4.1 构建项目目录

### 4.2 新建.env 区分环境
```
.env.development
ENV = "development";
VITE_APP_BASE_API = 'http://xxx'
.env.production
ENV = "production";
VITE_APP_BASE_API = 'http://xxx'
```
### 4.3 修改package.json 启动命令
```json
"scripts": {
"dev": "vite serve --mode development",
"build": "tsc && vite build --mode production",
}
```
### 4.4 编写index文件
```tsx
import axios, {
AxiosInstance,
AxiosError,
AxiosRequestConfig,
AxiosResponse,
} from "axios";
import { AxiosCanceler } from "./helper/axiosCancel";
import { checkStatus } from "./helper/checkStatus";
import { message } from "antd";
enum ResultEnum {
SUCCESS = 200,
ERROR = 500,
OVERDUE = 10001,
TIMEOUT = 6000,
TYPE = "success",
}
interface Result {
code: number;
message: string;
}
// * 请求响应参数(包含data)
interface ResultData extends Result {
data?: T;
}
const axiosCanceler = new AxiosCanceler();
const config = {
// 默认地址请求地址,可在 .env 开头文件中修改
baseURL: import.meta.env.VITE_APP_BASE_API as string,
// 设置超时时间(10s)
timeout: ResultEnum.TIMEOUT as number,
// 跨域时候允许携带凭证
withCredentials: true,
};
class RequestHttp {
service: AxiosInstance;
constructor(config: AxiosRequestConfig) {
// 实例化axios
this.service = axios.create(config);
/**
* @description 请求拦截器
*/
this.service.interceptors.request.use(
(config: AxiosRequestConfig) => {
axiosCanceler.addPending(config);
// * 需要添加的token 自行设置
const token: string | null = "";
return { ...config, headers: { token: token } };
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
/**
* @description 响应拦截器
*/
this.service.interceptors.response.use(
(response: AxiosResponse) => {
const { data, config } = response;
// * 在请求结束后,移除本次请求
axiosCanceler.removePending(config);
// * 登陆失效操作
if (data.code == ResultEnum.OVERDUE) {
message.error(data.message);
return Promise.reject(data);
}
// * 全局错误信息拦截(防止下载文件得时候返回数据流,没有code,直接报错)
if (data.code && data.code !== ResultEnum.SUCCESS) {
return Promise.reject(data);
}
// * 成功请求
return data;
},
async (error: AxiosError) => {
const { response } = error;
// 根据响应的错误状态码,做不同的处理
if (response) return checkStatus(response.status);
// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
if (!window.navigator.onLine) return;
return Promise.reject(error);
}
);
}
// * 常用请求方法封装
get(url: string, params?: any, _object = {}): Promise> {
return this.service.get(url, { params, ..._object });
}
post(url: string, params?: object, _object = {}): Promise> {
return this.service.post(url, params, _object);
}
put(url: string, params?: object, _object = {}): Promise> {
return this.service.put(url, params, _object);
}
delete(url: string, params?: any, _object = {}): Promise> {
return this.service.delete(url, { params, ..._object });
}
}
export default new RequestHttp(config);
```
### 4.5 编写checkStatus文件
```tsx
import { message } from 'antd'
/**
* @description: 校验网络请求状态码
* @param {Number} status
* @return void
*/
export const checkStatus = (status: number): void => {
switch (status) {
case 400:
message.error("请求失败!请您稍后重试");
break;
case 401:
message.error("登录失效!请您重新登录");
break;
case 403:
message.error("当前账号无权限访问!");
break;
case 404:
message.error("你所访问的资源不存在!");
break;
case 405:
message.error("请求方式错误!请您稍后重试");
break;
case 408:
message.error("请求超时!请您稍后重试");
break;
case 500:
message.error("服务异常!");
break;
case 502:
message.error("网关错误!");
break;
case 503:
message.error("服务不可用!");
break;
case 504:
message.error("网关超时!");
break;
default:
message.error("请求失败!");
}
};
```
### 4.6 编写axiosCancel文件
> 先下载qs模块
```
pnpm add qs
如确认下载qs模块后 遇见qs报错 则需要 下载@types/qs
pnpm add @types/qs -D
```
axiosCancel.ts
```ts
import axios, { AxiosRequestConfig, Canceler } from "axios";
import qs from "qs";
const isFunction(val: unknown) {
return toString.call(val) === `[object Function]`;
}
// * 声明一个 Map 用于存储每个请求的标识 和 取消函数
let pendingMap = new Map();
// * 序列化参数
export const getPendingUrl = (config: AxiosRequestConfig) =>
[config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join("&");
export class AxiosCanceler {
/**
* @description: 添加请求
* @param {Object} config
* @return void
*/
addPending(config: AxiosRequestConfig) {
// * 在请求开始前,对之前的请求做检查取消操作
this.removePending(config);
const url = getPendingUrl(config);
config.cancelToken =
config.cancelToken ||
new axios.CancelToken(cancel => {
if (!pendingMap.has(url)) {
// 如果 pending 中不存在当前请求,则添加进去
pendingMap.set(url, cancel);
}
});
}
/**
* @description: 移除请求
* @param {Object} config
*/
removePending(config: AxiosRequestConfig) {
const url = getPendingUrl(config);
if (pendingMap.has(url)) {
// 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
const cancel = pendingMap.get(url);
cancel && cancel();
pendingMap.delete(url);
}
}
/**
* @description: 清空所有pending
*/
removeAllPending() {
pendingMap.forEach(cancel => {
cancel && isFunction(cancel) && cancel();
});
pendingMap.clear();
}
/**
* @description: 重置
*/
reset(): void {
pendingMap = new Map();
}
}
```
## 5 安装 Tailwind CSS
```
pnpm add -D tailwindcss postcss autoprefixer
npx tailwindcss init
```
### 5.1 按照 Tailwind CSS 官方指南配置
postcss.config.cjs
```js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
```
tailwind.config.cjs
```js
module.exports = {
content: ['./src/**/*.{html,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
```
### 5.2 App.css 中添加
```
@tailwind base;
@tailwind components;
@tailwind utilities;
```
## 6 安装 ESLint 相关依赖
```
pnpm add -D eslint eslint-config-react-app
```
### 6.1 添加 .eslintrc.cjs
```
module.exports = {
extends: ['react-app', 'prettier'],
};
```
### 6.2 修改package.json 命令
```json
"scripts": {
"lint": "eslint -c .eslintrc.cjs --fix --ext .tsx src/"
}
```
## 7 安装 Prettier 相关依赖
```
pnpm add -D prettier eslint-config-prettier @trivago/prettier-plugin-sort-imports
```
### 7.1 添加 .prettierrc.cjs
```js
module.exports = {
singleQuote: true,
// 以下为 @trivago/prettier-plugin-sort-imports 配置,若未使用可删去
// importOrder 中的文件顺序规范,可依据项目实际情况自行更改
plugins: [require.resolve('@trivago/prettier-plugin-sort-imports')],
importOrder: [
'^vite',
'^react',
'^antd',
'',
'components/',
'pages/',
'hooks/',
'utils/',
'^[./]',
],
importOrderSortSpecifiers: true,
importOrderGroupNamespaceSpecifiers: true,
importOrderCaseInsensitive: true,
};
```
## 8 完成
```
pnpm run dev
```