目录
4.1.1 安装 vite-plugin-svg-icons
这篇文章是在之前的这篇小白教程的基础上进行的,建议先完成之前的文章的学习。请去我的gitee仓库拉取代码,代码相对于小白教程的文章有部分优化内容(下图),在教程中没有详细指出,可以参照提交记录学习。
一个完整的 vue 工程化项目,除了这篇文章完成的基础建设之外,还有必不可少的几个模块,分别是:
常见的接口请求方式有
Fetch API 是比较简单的原生接口请求方式,支持 Promise 但是兼容不太好,不推荐整个项目都使用。
XMLHttpRequest 是原生ajax 请求的方法,不支持 Promise,用起来需要自己封装,不推荐使用。
axios 是基于 XMLHttpRequest 封装的比较成熟的前端请求库,推荐使用。本篇文章主要介绍的是使用 axios 封装请求。?
用来建立长链接,接收服务器推送的消息,可以参考这篇文章,请按需使用。
WebSocket 基于 tcp 协议,可以实现客户端和后端双向交换消息,请按需使用。
ts 项目都需要一个 typing.d.ts 进行类型定义
使用插件实现对 svg 图标的压缩和管理,vite 项目可以使用 vite-plugin-svg-icons
关于svg 图标相关知识,可以参考这篇文章。
i18n 是一个成熟的国际化工具,用来实现页面上的语言切换
本地存储包括 cookie、localStorage、indexDB等
对于 cookie 过期时间的处理比较麻烦,建议不要自己写方法,已经有成熟的库 js-cookie 可供我们使用了。
localStorage 存储数据涉及到对对象和数组的存储,为了使用方便,我们一般封装几个获取对象/数组的方法,将使用原生方法 localStorage.getItem 获取的值进行 JSON.parse 再返回对象/数组。
不要要在自己格式化和计算日期了,moment 插件帮你解决各种日期转换问题。
用时间戳彻底解决前端的时差问题,其他的问题丢给后端,请参考这篇文章。
使用 navigator.userAgent 进行设备、浏览器的区分
使用 callapp-lib
可以使用is 库,也可以自己实现常用的方法,因为比较简单
上传图片、文件
pdf、txt 等使用ajax
直接使用 window.location.href 即可
使用?lodash
使用?vercel 部署
全部的内容非常多,所以本篇文章不包含5、6这两部分内容,对于其他内容也不具体解释每个模块的细致原理和功能,只详细介绍写代码的流程步骤。
再次强调,在开始之前,需要在仓库的的这个合并拉一个新的分支 feat-project
流程图如下,建议克隆代码,一步一步的跟着来,本质是对 axios 的封装。
pnpm i axios
在 src 下新建 service 文件夹
在 service 文件夹下新建 request.ts
(1)在 request.ts 中使用创建 axios请求实例
(2)源代码
import axios, { AxiosInstance } from 'axios'
function createRequestInstance(url: string): AxiosInstance {
const instance = axios.create({
timeout: 1000 * 60 * 5, // 超时时间
withCredentials: true, // 允许跨域携带cookie
baseURL: url, // 请求地址
})
return instance
}
export default createRequestInstance
(1)在 service 目录下增加 error.ts 文件,用于axios 请求的错误处理
(2)?在 error.ts 中增加 AxiosRequestError 类,将 axios 原生的错误类型(AxiosError)封装成我们自定义的错误类型,并对一些常见的错误码进行处理
(3)源代码
import { AxiosError } from 'axios'
// 服务器报错返回 Error 的时候的数据结构,可以和后端商量定义,但是所有接口的格式要统一
export type ErrorResponse = {
status: number // http 状态码,这个是必须的
// 其他自定义类型类型
}
class AxiosRequestError extends Error {
data: ErrorResponse | undefined
raw: AxiosError
isUnAuthorized = false // 权限错误 401
isServerError = false // 服务器错误 500 等
constructor(status: number, message: string, raw: AxiosError, data?: ErrorResponse) {
// 调用父类「Error」的构造函数
super(message)
this.data = data // 后端返回的 data
this.raw = raw // axios 返回的原始数据
this.isUnAuthorized = status === 401
this.isServerError = status >= 500
this.message = this.message || '' //给用户展示的错误消息,后续可以自定义
}
}
export default AxiosRequestError
(4)提交代码
(1)在 service 文件夹下增加 handleError.ts
(2)新增 handleError 方法
在 handleError.ts 中调用?AxiosRequestError 类,将?axios 默认的 AxiosError 转成我们处理过的??AxiosRequestError 类
(3) 源代码
import { AxiosError } from 'axios'
import AxiosRequestError, { ErrorResponse } from './error'
// 把 axios 的 错误 转成 我们已经封装的 AxiosRequestError 类,统一处理
export function handleError(error: AxiosError | AxiosRequestError): AxiosRequestError {
const err = error instanceof AxiosRequestError ? error : new AxiosRequestError(error.response?.status || 1, error.message, error, error.response?.data as ErrorResponse)
return err
}
(4)提交代码
(1)在 request.ts 中应用handleError,增加 axios 响应的拦截器,对错误进行处理
(2)?源代码
import axios, { AxiosInstance } from 'axios'
import { handleError } from './handleError'
function createRequestInstance(url: string): AxiosInstance {
const instance = axios.create({
timeout: 1000 * 60 * 5, // 超时时间
withCredentials: true, // 允许跨域携带cookie
baseURL: url, // 请求地址
})
instance.interceptors.response.use(
async res => {
return res
},
async err => {
err = await handleError(err)
return Promise.reject(err)
},
)
return instance
}
export default createRequestInstance
(3)提交代码
(1)在 service 下面增加 apiList.ts 存放系统中所有的请求地址
(2)在 apiList.ts 中随便定义接口
(3)源代码
// 系统中所有请求的接口
export const APIs = {
login: '/login',
// 还可以这样分成功能模块
user: {
Info: '/userinfo',
},
}
?(4)提交代码
(1)增加 requestList.ts
(3)封装 API 类
(3) 源代码
import { AxiosRequestConfig, AxiosResponse } from 'axios'
import createRequestInstance from './request'
class API {
request!: ReturnType<typeof createRequestInstance>
get!: <T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig) => Promise<R>
delete!: <T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig) => Promise<R>
head!: <T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig) => Promise<R>
options!: <T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig) => Promise<R>
post!: <T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig) => Promise<R>
put!: <T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig) => Promise<R>
patch!: <T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig) => Promise<R>
constructor(options: { url: string }) {
const request = createRequestInstance(options.url)
this.request = request
this.post = request.post.bind(this)
this.put = request.put.bind(this)
this.get = request.get.bind(this)
this.delete = request.delete.bind(this)
this.head = request.head.bind(this)
this.options = request.options.bind(this)
this.patch = request.patch.bind(this)
}
}
export default API
(4)提交代码
(1) 增加 webRequest.ts
(2)实例化API类,增加 axio的拦截器
(3)源代码
import API from './requestList'
import AxiosRequestError from './error'
import { handleError } from './handleError'
const $api = new API({
url: 'https://xxx.com/api', // 这个是请求的后台的服务的地址
})
// 请求的拦截器
$api.request.interceptors.request.use((config: any) => {
const headers = config.headers || {}
// 这个地方可以自定义请求头
config.headers = {
...headers,
language: 'en', // 这个是自定义的请求头,还可以加 token 等
}
return config
})
// 响应的拦截器
$api.request.interceptors.response.use(undefined, async (err: AxiosRequestError) => {
err = handleError(err) // 调用我们自定义的 错误处理方法
if (err.isUnAuthorized) {
// 未授权的情况的处理
}
// 还可以自定义其他的情况的处理
return Promise.reject(err)
})
// 在 page 页面就可以直接调用这个 $api 请求接口
export default $api
(4)提交代码
(1)增加API 类的静态函数
在 requestList.ts 中的 API 类中增加静态方法?getUserInfo?
(3)在 App.vue 中调用
(4)npm run dev 运行项目
(5)提交代码
(1)在 App.vue 测试
(2)效果
?(3)提交代码
实际项目中,我们需要区分请求的测试服务地址和正式服务地址,所以在 3.3 的步骤中,webRequest.ts 中 实例化 API 类的过程中 url的地址就不能写成一个固定的字符串。本节中我们对这一部分做优化。
(1)新建 env.ts
在 src 目录下新建 global 文件夹放通用配置文件,在 global 文件夹下面新建 env.ts 用于配置项目的环境变量(用于区分测试环境和正式环境)
(2)定义环境变量和获取环境变量的函数
(3)修改 request.ts
将 service/request.ts 中的函数参数变成一个函数,用于调用获取环境变量的函数
(4)修改 requestList.ts
修改在 API 类构造函数中调用 createRequestInstance 的参数
(5)修改 webRequest.ts?
修改实例化 API 类的地方的参数
(6)测试一下测试环境
运行 npm run dev 测试一下配置的地址是否正确
(7)测试一下正式环境
?(8)还原(7)中修改的 env.ts 中的代码
最终的env.ts的代码如下
// 正式环境
export const PROD_ENV = {
SERVER_URL: 'https://xxx.com/api', // 服务地址
IS_DEV: 'false', // 是否是测试环境
}
// 测试环境
export const DEV_ENV = {
SERVER_URL: 'https://xxx-test.com/api',
IS_DEV: 'true',
}
// 假设测试环境的域名是 https://xxx-test.com
const isDev = process.env.NODE_ENV === 'development' || ['xxx-test.com'].includes(location.host)
export type EnvKey = keyof typeof PROD_ENV
// 调用这个函数获取当前的环境变量
export function getProcessEnv(key: EnvKey): string | void {
if (isDev) {
if (DEV_ENV[key] !== undefined) {
return DEV_ENV[key]
}
return ''
}
if (PROD_ENV[key] !== undefined) {
return PROD_ENV[key]
}
}
(9)提交代码
再确认一下,这个小节,主要的工作:
?
至此,本项目中的接口请求模块结束。
对于ts项目,可以自定义数据的类型,对于一些没有提供类型定义的第三方模块,我们为了解决编辑器报的ts类型错误,也可以自定义ts类型
在src 目录下新建 typings文件夹,存放类型文件
在 typings 文件夹下面新建 index.d.ts 用于类型定义
pnpm i vite-plugin-svg-icons -D
import 'virtual:svg-icons-register'
随便弄个svg 图片放入?src/images/svgs 文件夹下,项目中的那个vue.svg 就是现成的
在 src/components 目录下新建 SvgIcon.vue
pnpm i vue-i18n
在 src/global 下面新建 i18n 文件夹,同时在该文件夹下新建 index.ts 、en.ts、zh.ts
运行 npm run dev ,点击按钮可以切换语言
至此,本片文章内容结束,一个完整的vite 项目还剩两个部分,一个是公共函数一个是部署,这将在下一片文章完成。
其实到这里整个项目基本就完成了,剩下的公共函数可以等你用到的时候再写也来得及,至于部署的部分,每个公司的部署流程也不一样,也看你的需求了。
项目地址是,learn-vite: 搭建简单的vite+ts+vue框架
有问题欢迎在评论区指正。