react+ts+vite+router6+antd 保姆级搭建

一、项目搭建

采用vite方式 ,根据选择 react-ts

pnpm create vite

1.1 修改初始结构,删除多余文件

1.2 修改vite.config配置文件 配置别名

vite.config:

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')//配置@别名
    }
  },
})

💡 Tips:为了ts不报错需要配置 tsconfig.json

tsconfig.json:

具体tsconfig配置参数详解 

{
  "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

二、路由 react-router-dom@6配置

  1. pnpm add react-router-dom@6 --save-dev

  2. 在根文件main.tsx里面 修改 在app外层用BrowserRouter包裹
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

3.在router文件夹下创建index.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 (
    <>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
      </Routes>
    </>
  );
}
export default RootRoute

💡 Tips:path为路径,element为引入的路由文件。

4.在app.tsx 引入router文件即可

三、引入antd

1.下载

pnpm add antd

2.引入antd样式

💡 Tips:在入口文件 引入的css文件里如app.css(注意在最上方)

@import'antd/dist/antd.css';

3使用

💡 Tips:根据antd文档 在使用的地方子回家引入 如:

import { Button } from 'antd';

四、封装axios

1.下载

pnpm add axios -S

2.构建项目目录 如图 配置正式测试环境地址

2.1 新增http文件夹 在其中生产如下文件

2.2 新建.env 区分环境

根据实际情况配置

修改package.json 启动命令

  "scripts": {
    "dev": "vite serve --mode development",
    "build:pro": "tsc && vite build --mode production",
  },

3.编写index文件

具体逻辑可以新增

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<T = any> 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<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
      return this.service.get(url, { params, ..._object });
}
post<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
  return this.service.post(url, params, _object);
}
put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
  return this.service.put(url, params, _object);
}
delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
  return this.service.delete(url, { params, ..._object });
}
}

export default new RequestHttp(config);

4.checkStatus.ts

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("请求失败!");
	}
};

5.axiosCancel.ts

💡 Tips:先下载qs模块

pnpm add qs

如确认下载qs模块后 遇见qs报错 则需要 下载@types/qs

pnpm add @types/qs -D
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<string, Canceler>();

// * 序列化参数
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<string, Canceler>();
	}
}

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

街头炒河粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值