React实现大文件分片上传——详解基础版(用hooks进行封装)
1.了解为什么大文件需要分片(面试可能会问)
- 一般来说我们会设置请求时长,如果文件太大可能会超出请求的时长,导致请求超时
- 一般来说在服务端会对客户端请求数据的大小进行限制,文件过大会超出限制,导致上传失败。服务端做出这个限制的原因:是因为防止DDOS攻击,占用你服务器的大量性能导致服务器崩溃
- 将文件分割成多段,进行并发上传,可以极大的提高上传速度,而且当传输出现问题的时候,我们只需要将传错的分片进行上传,不需要重新全部上传
2.如何实现大文件分片上传
1. 思考分片上传需要哪些操作
- 获取上传文件的信息
- 对文件信息进行分割
- 并发的进行上传请求
- 当请求结束的时候,给服务器发送一个合并的请求,告诉服务器你这次上传结束可以进行对切片的合并
2.实现自己的思考
接下来我们根据上述思考一步一步实现,最后拼成一个
useUpload.js
- 获取文件上传信息
const [files, setFiles] = useState()
const handleFileChange = (e) => {
const [file] = e.target.files
setFiles(file)
}
- 对文件信息进行分割
// 生成文件切片
const createFileChunk = (file, size = SIZE) => {
// 临时存储文件的切片的数组,里面是一个一个的切片数据
const fileChunkList = []
// 表示从0的位置开展切片,每一个片的大小和你的SIZE设置有关系
let cur = 0
while (cur < file.size) {
// 每进行一次循环,都会把切片放进临时数组
fileChunkList.push({ file: file.slice(cur, cur + size) })
// 每次循环都会更新起始位置,最终会等于文件的大小,然后退出循环
cur += size
}
return fileChunkList
}
- 并发的进行上传请求, 当请求结束的时候,给服务器发送一个合并的请求,告诉服务器你这次上传结束可以进行对切片的合并
// 上传切片
const uploadChunks = async (newFileChunkList) => {
// 对文件进行循环,提取出我们需要的值,chunk,hash, files.name
// hash的作用是后续服务端需要根据hash值来拼接
// 将所有请求放在requestList中,后续通过 await Promise.all(requestList)并发请求
const requestList = newFileChunkList
.map(({ chunk, hash }) => {
const formData = new FormData()
formData.append('chunk', chunk)
formData.append('hash', hash)
formData.append('filename', files.name)
return { formData }
})
.map(({ formData }) => {
return request.post('api/upload', formData)
})
// 并发请求
await Promise.all(requestList)
// 等待并发请求结束 发送合并切片请求
await mergeRequest()
}
// 合并请求
const mergeRequest = async () => {
const data = { size: SIZE, filename: files.name }
await request.post('api/merge', data)
}
- 将这些方法防止上传事件中
const handleUpload = async () => {
if (!files) return
const fileChunkList = createFileChunk(files)
const newFileChunkList = fileChunkList.map(({ file }, index) => {
return { chunk: file, hash: files.name + '-' + index }
})
await uploadChunks(newFileChunkList)
}
3.将这些方法拼接,变成一个hooks
其中 request.ts
是封装的axios请求
SIZE
是定义的一个常量
如果需要封装的request.ts可以私信我
import { useState } from 'react'
import request from '@/utils/request.ts'
// 如果不想这么麻烦导入,可以直接 const SIZE = 1* 1024 * 1024
import { SIZE } from '@/common/config'
export default function useUpload() {
const [files, setFiles] = useState()
const handleFileChange = (e) => {
const [file] = e.target.files
setFiles(file)
}
// 生成文件切片
const createFileChunk = (file, size = SIZE) => {
// 临时存储文件的切片的数组,里面是一个一个的切片数据
const fileChunkList = []
// 表示从0的位置开展切片,每一个片的大小和你的SIZE设置有关系
let cur = 0
while (cur < file.size) {
// 每进行一次循环,都会把切片放进临时数组
fileChunkList.push({ file: file.slice(cur, cur + size) })
// 每次循环都会更新起始位置,最终会等于文件的大小,然后退出循环
cur += size
}
return fileChunkList
}
// 合并请求
const mergeRequest = async () => {
console.log(2)
const data = { size: SIZE, filename: files.name }
await request.post('api/merge', data)
}
// 上传切片
const uploadChunks = async (newFileChunkList) => {
// 对文件进行循环,提取出我们需要的值,chunk,hash, files.name
// hash的作用是后续服务端需要根据hash值来拼接
const requestList = newFileChunkList
.map(({ chunk, hash }) => {
console.log(1)
const formData = new FormData()
formData.append('chunk', chunk)
formData.append('hash', hash)
formData.append('filename', files.name)
return { formData }
})
.map(({ formData }) => {
return request.post('api/upload', formData)
})
// 并发请求
await Promise.all(requestList)
// 合并切片请求
await mergeRequest()
}
const handleUpload = async () => {
if (!files) return
const fileChunkList = createFileChunk(files)
const newFileChunkList = fileChunkList.map(({ file }, index) => {
return { chunk: file, hash: files.name + '-' + index }
})
await uploadChunks(newFileChunkList)
}
return {
handleFileChange,
handleUpload,
}
}
总结
这样一个简单的分片上传就做好了,当然也有很多不完善,后续需要将hash值利用
spark-md5
这个库进行生成,并且也需要对request进行配置可以实现暂停上传和恢复上传等等,会在后续的文章进行讲解,这样子分开讲解希望各位读者能理解更快,有不足的地方可以私信我