简单记录一下周五封装uploadImage方法时的一些思考
源码
typescript
import { Message } from 'vue3组件库';
import to from 'await-to-js';
import axios from 'axios';
interface IOption {
max?: number;
validator?: ((file: File) => boolean) | ((file: File) => Promise<boolean>);
}
export async function uploadIcon(file: File) {
// 请求体参数使用FormData对象代替普通对象
const params = new FormData();
params.append('file', file);
return axios({
headers: {
'Content-Type': undefined,
},
method: 'post',
url: 'xxx/upload/image', // 图片上传接口
data: params,
});
}
/**
* @param file 图片文件对象
* @param option 上传配置对象 max为限制文件大小(单位KB) validator为自定义校验函数
* @returns Promise<url>
*/
export async function uploadImage(file: File, option: IOption = {}) {
const { max, validator } = option;
// 如果有max限制就进行文件大小校验
if (max && file.size > max * 1024) {
return Promise.reject(
Message({
message: `图片超过${max}KB的限制大小, 请压缩图片大小`,
type: 'info',
})
);
}
// 如果没有validator 或者 有validator且await validator的结果为true,那么进行图片上传
if (!validator || (await validator(file))) {
const [error, res] = await to(uploadIcon(file));
if (error) {
return Promise.reject(
Message({
message: error.message || '图片上传出错',
type: 'error',
})
);
}
Message({ message: '上传成功', type: 'success' });
return res.data.data;
} else {
return Promise.reject(
Message({
message: '图片未通过校验',
type: 'info',
})
);
}
}
使用示例
typescript
const [error, url] = await to(uploadImage(file, { max: 500, validator }));
if (!error) {
// 消费url
}
为什么使用FormData对象
FormData
对象主要用于发送表单数据,特别是包含文件上传请求的情况。它有以下几个主要优点:
- 可以发送键值对数据,同时也支持文件上传。普通对象无法直接发送文件数据(必要性) 。
- 当发送大量数据时,使用
FormData
可以使数据在传输过程中保持较小的大小,因为它的数据格式比普通的 JSON 对象更紧凑 (请求体积小,性能优) 。 FormData
对象与XMLHttpRequest
和fetch
API 都有很好的兼容性,可以方便地发送异步请求。FormData
对象自动将请求头的Content-Type
字段设置为multipart/form-data
,并生成适当的boundary
参数,这对于文件上传是必需的 (包含文件数据的http请求有格式的要求,即content-type字段与boundary参数,使用FormData对象作为请求体,可以自动设置符合文件上传要求的content-type与boundary) 。
因此,当你需要在前端发送包含文件的 POST
请求时,使用 FormData
对象是一个很好的选择。
结论
如果我们的请求体对象中有文件对象,那么需要用FormData
对象来代替普通对象
为什么设置Content-type为undefined
在使用 axios
发送 POST
请求上传文件时,如果我们设置 Content-Type
为 undefined
,那么浏览器会自动将 Content-Type
设置为 multipart/form-data
,并且会自动添加一个合适的 boundary
参数。
multipart/form-data
是一种编码类型,它允许在一个 POST
请求中发送多部分数据,这对于发送包含文件的表单数据非常有用。boundary
参数用于定义请求中各个部分的边界。
如果我们手动设置 Content-Type
为 multipart/form-data
,但没有添加 boundary
参数,那么请求可能会失败,因为服务器可能无法正确解析请求体。因此,当我们需要发送 multipart/form-data
类型的 POST
请求时,最好将 Content-Type
设置为 undefined
,让浏览器自动处理。
结论
显示设置Content-type
参数为undefined
目的是让浏览器自动修正请求头里的content-type
字段值和boundary
参数。其实使用FormData
对象作为请求体已经将http请求的content-type与boundary参数设置好了,算是双重保险吧
plus:Content-type: undefined
可以省略,但是使用FormData
对象承载文件对象是必须的。
如何支持 同步/异步validator
同步validator就不用说了,直接用if判断其结果是否为true即可,如果用户传入的校验函数为异步方法呢,也很简单,await validator
即可对同步或者异步的validator
方法进行统一处理。(await
后面的值为非Promise对象时会用Promise.resolve
方法对其包装)
如何支持消费组件中await-to-js的错误感知
当uploadImage
上传失败时,return Promise.reject
返回一个拒绝的Promise对象即可。
如何通过自定义validator限制图片宽高一致
思路是validator函数中,通过创建一个Image
对象image,并通过URL.createObjectURL(file)
构造其url
,在image
的onload
回调函数中通过image.width
与image.height
拿到图片宽高。
ts
// 图片上传相关回调
const validator = (file: File) => {
return new Promise<boolean>((resolve, reject) => {
const url = URL.createObjectURL(file);
const image = new Image();
image.onload = function () {
if (image.width !== image.height) {
Message({
message: '图片分辨率宽高不一致, 请设置相同的图片宽高',
type: 'info',
});
reject(false);
}
resolve(true);
};
image.src = url;
});
};