一、项目搭建
采用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配置
- pnpm add react-router-dom@6 --save-dev
- 在根文件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(
);
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 (
<>
} />
} />
</>
);
}
export default RootRoute
💡 Tips:path为路径,element为引入的路由文件。
4.在app.tsx 引入router文件即可
三、引入antd
ant-design 官网镜像地址:
http://ant-design.gitee.io/index-cn
ant-design-pro镜像地址:
http://ant-design-pro.gitee.io/index-cn
antd-mobile镜像地址:
https://antd-mobile.gitee.io/index-cn
ant-design的正常地址https://ant.design/index-cn
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 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(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.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();
// * 序列化参数
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>();
}
}
五、vscode配置
5.1 ts模块的react代码片段
{
"全局React-ts函数组件模板": {
"scope": "javascript,typescript,typescriptreact",
"prefix": "rfc-ts", //快捷命令 可以自己定义
"body": [
"import React,{FC,ReactElement} from 'react';",
"",
"const ${TM_FILENAME_BASE}: FC = ():ReactElement => {",
" return
"};",
"",
"export default ${TM_FILENAME_BASE};",
""
],
"description": "React函数组件默认模版"
}
}
5.2 .es和.prettierrc
💡 Tips:先下载依赖
pnpm add eslint eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser prettier
根目录建立.eslintrc.cjs、.eslintignore、.prettierrc.cjs、.prettierignore
module.exports = {
parser: '@typescript-eslint/parser',
extends:[
'prettier/@typescript-eslint',
'plugin:prettier/recommended'
],
settings: {
"react": {
"pragma": "React",
"version": "detect"
}
},
parserOptions: {
"ecmaVersion": 2019,
"sourceType": 'module',
"ecmaFeatures":{
jsx:true
}
},
env:{
browser: true,
node: true,
}
}
module.exports = {
// 一行最多 100 字符
printWidth: 100,
// 使用 4 个空格缩进
tabWidth: 4,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾不需要逗号
trailingComma: 'none',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// 换行符使用 lf
endOfLine: 'lf'
};
node_modules/
dist/
index.html
//定义忽略文件
*/node_modules/
build
//定义忽略文件