一个基于vue3+vite+ts的项目搭建笔记

6/9/2023 vitevue

# 前言

记录下第一次开始摸索自己搭建项目的坑。

# 操作步骤

# 第一步

# 项目初始化

初始化项目使用了 vite (opens new window) 的官方模板 pnpm create vite {project-name}

# 第二步

# 依赖包

常用依赖

可选依赖

# 第三步

配置项目

# 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;
1
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
1
2
3
4
5
import { createApp } from 'vue'
import store from '@/stores'

import App from './App.vue'

createApp(App)
.use(store)
.mount('#app')
1
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;

1
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) => {})
1
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')
1
2
3
4
5
6
7
8
9

# unocss

在vite.config下

import Unocss from 'unocss/vite'
export default {
  plugins: [
    Unocss(),
  ],
}
1
2
3
4
5
6

在vue的实列创建时引入

import 'uno.css'
1

# 其他

# 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)
    }
}
1
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);
})
1
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)
    }
}

1
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
  }
}
1
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'),
		},
	},
1
2
3
4
5

在tsconfig.json 文件中配置

{
    "compilerOptions":{
        "baseUrl": ".",
        "paths": {
        "@/*": ["src/*"]
        }
    }
}
1
2
3
4
5
6
7
8

# /

  • axios取消重复请求
  • axios错误重试请求
  • 组件、方法自动引入
  • ESLint
  • Prettier
  • husky
Last Updated: 11/21/2023, 11:22:14 AM