# 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 构建项目目录 ![markdown picture](./assets/img1.jpg) ### 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 ```