Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!
2013年7月28日,尤雨溪第一次在 GItHub 上为 Vue.js 提交代码;2015年10月26日,Vue.js 1.0.0版本发布;2016年10月1日,Vue.js 2.0发布。
最早的 Vue.js 只做视图层,没有路由, 没有状态管理,也没有官方的构建工具,只有一个库,放到网页里就可以直接用了。
后来,Vue.js 慢慢开始加入了一些官方的辅助工具,比如路由(Router)、状态管理方案(Vuex)和构建工具(Vue-cli)等。此时,Vue.js 的定位是:The Progressive Framework。翻译成中文,就是渐进式框架。
Vue.js2.0 引入了很多特性,比如虚拟 DOM,支持 JSX 和 TypeScript,支持流式服务端渲染,提供了跨平台的能力等。Vue.js 在国内的用户有阿里巴巴、百度、腾讯、新浪、网易、滴滴出行、360、美团等等。
Vue 已是一名前端工程师必备的技能,现在就让我们开始深入学习 Vue.js 内部的核心技术原理吧!
文件上传
在前端中,对于文件上传是一个很常见的功能,我们可以上传图片、文件等到服务器,对于一般的小型的文件, 例如小图片,我们可以选择基于 formdata 进行文件上传,也可以基于 base64 格式去上传,直接发送表单请求即可。
但是对于大文件来说,比如几个G甚至几十G的文件来说,我们如果还是按小文件来处理的话,就显得不是很恰当了。本篇文章就是告诉大家如何实现大文件上传的,包括切片上传、断点续传、进度把控等方面,废话不多说,开始吧。
大文件上传思路
这里先简单说一下整体的思路:
-
针对文件内容生成的唯一的hash值,作为文件名称。确保相同文件,不同的文件名的情况下,生成的hash值是一样的。
-
将大文件切成若干个小的分片,并且每一个分片的命名格式为 “文件hash值+当前切片索引值”,方便后端合并这些切片。
-
上传之前,将当前文件的hash值传递给后台,拿到当前文件在数据库里已经存在的切片,如果有切片已经存在,那么前端就不传递这些了,这就是断点续传的原理。
-
上传过程中,通过监听当前传递的切片数除以切片总数,获取当前上传进度。当进度条百分百时,自动通知后端将这些文件合并。
-
上传完成。
下面进行代码展示:
-
生成文件名
这里我们使用一个第三方库:spark-md5,根据文件二进制流,生成hash值。
在vue中,通过:npm i spark-md5 安装
import SparkMD5 from 'spark-md5'
/**
* @description: 拿到文件的hash值
* @Author: hfunteam
* @param {*} file 文件,File类型
* @return {
* buffer: 文件的二进制流
* hash: 生成的文件hash值
* suffix:文件后缀名
* filename: 文件名称 hash + suffix
* }
*/
function changeBuffer(file: File) {
return new Promise(resolve => {
let fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = ev => {
let buffer: ArrayBuffer = ev.target!.result as ArrayBuffer,
spark = new SparkMD5.ArrayBuffer(),
HASH,
suffix;
spark.append(buffer);
// 拿到文件的hash值
hash = spark.end();
// 匹配文件后缀名
suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)![1];
resolve({
buffer,
hash,
suffix,
filename: `${HASH}.${suffix}`
});
};
});
};
-
分片上传+断点续传
// html 部分
<input type="file" id="input" >
// js 部分
import axios from 'axios'
// 监听input按钮事件
document.getElementById('input').addEventListener('change', async function () {
// 获取文件的HASH
let alreadyUploadChunks = [], // 当前已经上传的切片
{
hash,
suffix
} = changeBuffer(file);
// 获取已经上传的切片信息
let data = await axios.get('/upload_already', {
params: {
hash
}
});
// 从后端拿到已经上传的切片列表
alreadyUploadChunks = data.fileList
// 实现文件切片处理 「固定数量 或者 固定大小」
let max = 1024 * 100, // 切片大小
count = Math.ceil(file.size / max), // 切片总数
index = 0, // 当前上传的切片索引值
chunks = []; // 存放切片的数组
if (count > 100) {
max = file.size / 100;
count = 100;
}
// 存放切片,注意此处的切片名称,hash+index+suffix
while (index < count) {
chunks.push({
file: file.slice(index * max, (index + 1) * max),
filename: `${HASH}_${index+1}.${suffix}`
});
index++;
}
// 把每一个切片都上传到服务器上
chunks.forEach(chunk => {
// 这里进行断点续传:已经上传的无需在上传
if (alreadyUploadChunks.length > 0 && alreadyUploadChunks.includes(chunk.filename)) {
// 管控进度条
manageProgress();
return;
}
let formdata = new FormData();
formdata.append('file', chunk.file);
formdata.append('filename', chunk.filename);
instance.post('/upload_chunk', formdata).then(data => {
// 管控进度条
manageProgress();
}).catch(() => {
alert('当前切片上传失败,请您稍后再试');
});
});
})
-
进度条控制+通知合并
当进度条到达百分之百时,调用通知后台合并切片接口,合并成功,则上传成功。
async function manageProgress(){
// 管控进度
document.getElementById('progress').style.width = `${index/count*100}%`;
// 当所有切片都上传成功,我们合并切片
if (index < count) return;
document.getElementById('progress').style.width = `100%`;
try {
data = await instance.post('/upload_merge', {
hash,
count
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if (data.code === 0) {
alert(`恭喜您,文件上传成功`);
return;
}
throw data.codeText;
} catch (err) {
alert('切片合并失败,请您稍后再试');
}
};
Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!
叶阳辉
HFun 前端攻城狮
往期精彩:
-
Vue 进阶系列丨Object 的变化侦测
-
Vue 进阶系列丨Array 的变化侦测
-
Vue 进阶系列丨虚拟DOM和VNode
-
Vue 进阶系列丨Patch 和模板编译
-
Vue 进阶系列丨事件相关的实例方法
-
Vue 进阶系列丨生命周期相关的实例方法
-
Vue 进阶系列丨生命周期
-
Vue 进阶系列丨自定义指令
-
Vue 进阶系列丨最佳实践
-
Vue 进阶系列丨Mixin 混入
-
Vue 进阶系列丨权限控制 addRoute()
-
Vue 进阶系列丨npm发布vue组件
-
Vue 进阶系列丨Vuex+TS 代码提示
-
Vue 进阶系列丨自定义指令实现按钮权限功能
-
Vue 进阶系列丨Pinia 的基本使用
-
Vue 进阶系列丨vue2和vue3定义插件的区别
-
Vue 进阶系列丨vuex持久化
-
Vue 进阶系列丨webWorker 多线程