目录
前言
一、基础知识准备
1.1 接口请求 (本篇重点内容)
1.1.1 Fetch API
1.1.2 XMLHttpRequest
1.1.3 axios(推荐)
1.1.4 EventSource
1.1.5 WebSocket
1.2 ts 类型定义 (本篇内容)
1.3 svg 雪碧图(本篇内容)
1.4 i18n 多语言(本篇内容)
1.5 公共工具方法(非本篇内容)
1.5.1 本地存储相关
b)localStorage
1.5.2 日期相关
a)格式化和计算
b)时差问题
1.5.3 设备区分
a) userAgent
b)h5 唤起 APP
1.5.4 判断 js 类型 (is)
1.5.5 上传文件
1.5.6 下载文件
a)浏览器能预览的文件
b)浏览器不能预览的文件
1.5.7 防抖和节流
1.6 部署
小结
二、增加接口请求模块
2.1 使用 axios 模块
2.1.1 安装 axios 模块
2.1.2 新建 service 文件夹
2.1.3 新建 request.ts
2.1.4 创建 axios 请求实例
2.1.5 提交代码
2.2 增加错误处理
2.2.1 新建 error.ts
2.2.2 新建 handleError.ts
2.2.3 应用 handleError 方法
2.3 增加 web 请求方法
2.3.1 定义接口列表
2.3.2 封装 API 类
2.3.3 实例化 API 类
2.3.4 测试一下静态函数
2.3.5 测试一下非静态函数
2.4 优化请求模块
2.4.1 优化请求地址
三、ts 类型定义
3.1 新建 typings 文件夹
3.2 新建 index.d.ts
3.3 提交代码
四、增加 svg 雪碧图
4.1 配置插件
4.1.1 安装 vite-plugin-svg-icons
4.1.2 在 vite.config.ts 中配置
4.1.3 在 main.ts 中引入
4.1.4 创建 src/images/svgs 文件夹
4.2 使用插件
4.2.1 新建 SvgIcon.vue
4.2.2 在 login.vue 中使用
4.2.3 运行效果
4.2.4 提交代码
五、i18n 多语言
5.1 安装和配置
5.1.1 安装 vue-i18n
5.1.2 新建 i18n 文件夹
5.1.3 更新 zh.ts & en.ts
5.1.4 更新 index.ts
5.1.5 在 main.ts 注册
5.2 使用多语言
5.2.1 在 login.vue 中使用
5.2.2 运行效果
5.3 提交代码
总结
这篇文章是在之前的这篇小白教程的基础上进行的,建议先完成之前的文章的学习。请去我的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
在 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
get!: >(url: string, config?: AxiosRequestConfig) => Promise
delete!: >(url: string, config?: AxiosRequestConfig) => Promise
head!: >(url: string, config?: AxiosRequestConfig) => Promise
options!: >(url: string, config?: AxiosRequestConfig) => Promise
post!: >(url: string, data?: any, config?: AxiosRequestConfig) => Promise
put!: >(url: string, data?: any, config?: AxiosRequestConfig) => Promise
patch!: >(url: string, data?: any, config?: AxiosRequestConfig) => Promise
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框架
有问题欢迎在评论区指正。