一个基于vue3+vite+ts的项目搭建笔记
# 前言
记录下第一次开始摸索自己搭建项目的坑。
# 操作步骤
# 第一步
# 项目初始化
初始化项目使用了 vite (opens new window) 的官方模板 pnpm create vite {project-name}
。
# 第二步
# 依赖包
常用依赖
- axios (opens new window)
pnpm add axios
- pinia (opens new window)
pnpm add pinia
- vue-router (opens new window)
pnpm add vue-router
- unocss (opens new window)
pnpm add -D unocss
可选依赖
- @types/node (opens new window)
pnpm add -D @types/node
(用于解决vite文件使用 '@' 等字符以绝对路径引入) .
# 第三步
配置项目
# axios
axios在使用时引入即可,我在utils文件夹新建request.ts。 request是请求前,可以做请求前的cookie携带,设置,取消重复请求,参数处理等。 response是请求后,可以做请求错误的全局处理等
import axios from 'axios';
import { requestMountCancel, requestDeleteCancel,repeatConfig } from "@/utils/request/repeat";
import { requestTry, requestTryConfig } from "@/utils/request/retry";
axios.isCancel
const instance = axios.create({
baseURL: '/',
timeout: 5000,
repeatCancel: { ...repeatConfig },
requestTryConfig:{...requestTryConfig}
});
instance.interceptors.request.use((config) => {
// 取消重复请求操作
requestMountCancel(config)
return config;
},(error) => {
console.error(error);
})
instance.interceptors.response.use((response) => {
// 请求成功后删除当前请求的方法
requestDeleteCancel(response)
return Promise.resolve(response.data);
},
(error) => {
if(error.code !== 'ERR_CANCELED'){
// 响应失败删除当前当前请求的Map
requestDeleteCancel(error)
// 失败重试
requestTry(instance, error)
}
return Promise.reject(error);
});
export default instance;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# pinia
createPinia
创建实列,在vue的实列创建时引入。
import { createPinia } from 'pinia'
const store = createPinia()
export default store
2
3
4
5
import { createApp } from 'vue'
import store from '@/stores'
import App from './App.vue'
createApp(App)
.use(store)
.mount('#app')
2
3
4
5
6
7
8
# router
vue-router 模块引入 createRouter 方法创建路由实例。
创建实列有个history属性,他是路由的模式,区别在url中能看出。目前有两种模式 createWebHashHistory
创建后url带#,使用的是浏览器的锚点功能、createWebHistory
就看起来很 "正常"。需要注意因为单页应用,这样做在第一次进来浏览器会直接访问地址,可是服务端并没有这个页面就会404错误。需要服务器上添加回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。
// import { createRouter, createWebHistory } from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import appRoute from './modules/app';
export const asyncRoutes = [{ path: '/:pathMatch(.*)*', name: 'not-found-page', redirect: '/404' }];
const router = createRouter({
// history: createWebHistory(),
history: createWebHashHistory(),
routes: [
{
path: '/',
name: 'Home',
component: Home,
children: [],
},
{
path: '/404',
name: '404',
component: () => import('@/pages/errorPage/404.vue'),
},
{
path: '/401',
name: '401',
component: () => import('@/pages/errorPage/401.vue'),
},
, { path: '/:pathMatch(.*)*', name: 'not-found-page', redirect: '/404' }
],
});
export default router;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
这样一个路由实列就创建完成了,另外可以使用路由守卫,做页面访问的限制或者全局必须的数据请求。
import router from './index'
// 路由守卫
router.beforeEach((to, from, next) => {next()})
router.afterEach((to) => {})
2
3
4
最终使用时在vue的实列创建时引入。
import { createApp } from 'vue'
import router from '@/routes'
import '@/routes/permissions'
import App from './App.vue'
createApp(App)
.use(router)
.mount('#app')
2
3
4
5
6
7
8
9
# unocss
在vite.config下
import Unocss from 'unocss/vite'
export default {
plugins: [
Unocss(),
],
}
2
3
4
5
6
在vue的实列创建时引入
import 'uno.css'
# 其他
# axios取消重复请求
目前只实现了,单个请求取消。
现在有个想法,将router路径放入key中,新增一个方法,传入路径参数,会遍历Map,匹配路由,在执行取消请求方法。实现页面级请求取消。
import type { RepeatCancel } from '@/typings/request';
import axios, { InternalAxiosRequestConfig, Method } from 'axios';
const CancelToken = axios.CancelToken;
// 默认配置
export const repeatConfig: RepeatCancel.Config = {
isRepeatCancel: true
}
/**
* @description 存储请求对应取消方法的Map
*/
export const repeatMap: RepeatCancel.RepeatCancelMap = new Map();
/**
* @description 将请求转为字符用于 repeatMap 的 key
* @param param
*/
function requestToKey(params: RepeatCancel.RepeatCancelMapKey): string {
const { method, url, param } = params;
const paramsString = new URLSearchParams(param)
return [method, url, paramsString.toString()].join('&');
}
/**
* @description 新增 repeat
* @param repeatKey
*/
function repeatMapSet(repeatKey: RepeatCancel.RepeatCancelKey){
return new CancelToken(function executor(cancel) {
if (!repeatMapHas(repeatKey)) {
repeatMap.set(repeatKey, cancel);
}
});
}
/**
* @description 删除 repeat
* @param repeatKey
*/
function repeatMapDelete(repeatKey: RepeatCancel.RepeatCancelKey){
repeatMap.delete(repeatKey);
}
/**
* @description 查询 repeat
* @param repeatKey
*/
function repeatMapHas(repeatKey: RepeatCancel.RepeatCancelKey): boolean {
return repeatMap.has(repeatKey)
}
/**
* @description 执行 repeat
* @param repeatKey
*/
function repeatMapCancel(repeatKey: RepeatCancel.RepeatCancelKey) {
const cancel = repeatMap.get(repeatKey)
if(cancel)
cancel()
}
/**
* @description 挂载取消请求
* @param params
*/
export function requestMountCancel(requestConfig: InternalAxiosRequestConfig){
if(requestConfig.repeatCancel?.isRepeatCancel){
const requestKey = requestToKey({ method:requestConfig.method as Method , url: requestConfig.url || '', param: requestConfig.params || requestConfig.data })
if(repeatMapHas(requestKey)){
repeatMapCancel(requestKey)
repeatMapDelete(requestKey)
}
requestConfig.cancelToken = repeatMapSet(requestKey)
}
}
/**
* @description 删除过时请求
* @param params
*/
export function requestDeleteCancel(request: any){
const {config} = request
const requestKey = requestToKey({ method:config.method as Method , url: config.url || '', param: config.params || config.data })
if(repeatMapHas(requestKey)){
repeatMapDelete(requestKey)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
在 request.ts 中
instance.interceptors.request.use((config) => {
// 获取当前请求字符
const requestKey = requestToKey({ method:config.method as Method , url: config.url || '', params:config.params || config.data })
// 判断是否重复请求,是就取消上次请求
requestMapCancel(requestKey)
// 设置请求的取消token
config.cancelToken = requestMapSet(requestKey)
return config;
},(error) => {
console.error(error);
})
2
3
4
5
6
7
8
9
10
11
# axios错误重试请求
对请求进行计数,失败时修改次数,在重新请求
import type { ReTry } from '@/typings/request'
import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios'
// 默认配置
export const requestTryConfig: ReTry.Config = {
count: 3,
isTry: true
}
/**
* @description 请求尝试
* @param requestConfig
*/
export function requestTry(service:AxiosInstance, request: any) {
const {config:requestConfig } = request as { config: InternalAxiosRequestConfig }
if(requestConfig.requestTryConfig && requestConfig.requestTryConfig.isTry && requestConfig.requestTryConfig.count){
requestConfig.requestTryConfig.count--
service(requestConfig)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# axios拓展AxiosRequestConfig的TS类型定义
在这我顺便将上面两个问题的类型代码放出来,补全下。
参考链接:https://1991421.cn/2021/01/11/7fca5d7a/ (opens new window)
import type { Canceler, Method,AxiosRequestConfig } from 'axios';
// 重复请求取消
declare namespace RepeatCancel {
type RepeatCancelKey = string
type RepeatCancelMap = Map<RepeatCancelKey, Canceler>
interface RepeatCancelMapKey{
method: Method;
url: string;
param: any
}
interface Config {
isRepeatCancel?:Boolean
}
}
// 请求重试
declare namespace ReTry {
type TryCount = number
type IsTry = boolean
interface Config {
count?:TryCount
isTry?:IsTry
}
}
// 使用声明合并拓展AxiosRequestConfig类型定义
declare module 'axios' {
export interface AxiosRequestConfig {
/**
* @description 设置为true,则开启重复请求取消
*/
repeatCancel?: RepeatCancel.Config;
/**
* @description 设置为true,则开启失败重试
*/
requestTryConfig?: ReTry.Config
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# vite 别名
引入文件需要../../../...等,非常麻烦,vite需要手动配置文件路径。
在vite.config.ts 文件中配置。
resolve: {
alias: {
'@': path.resolve('./src'),
},
},
2
3
4
5
在tsconfig.json 文件中配置
{
"compilerOptions":{
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
2
3
4
5
6
7
8
# /
axios取消重复请求axios错误重试请求- 组件、方法自动引入
- ESLint
- Prettier
- husky