文件上传、下载与断点续传
请求层一旦碰到文件,复杂度就会明显上来。普通 JSON 接口关注的是数据结构,文件链路更常见的问题是:
- 上传进度怎么拿
- 大文件怎么拆
- 下载怎么保留文件名
- 网络断了之后怎么续传
- 同一个文件重复上传怎么去重
这一篇把这几块收在一起。
最基础的上传
浏览器里最常见的是 FormData。
async function uploadFile(file: File) {
const formData = new FormData()
formData.append('file', file)
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
if (!res.ok) {
throw new Error('upload failed')
}
return res.json()
}
如果只求能传上去,这一层已经够了。
上传进度怎么拿
原生 fetch 目前不适合直接拿上传进度,项目里更常见的是交给 axios。
import axios from 'axios'
export function uploadWithProgress(file: File, onProgress?: (percent: number) => void) {
const formData = new FormData()
formData.append('file', file)
return axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress(event) {
const total = event.total || 1
const percent = Math.round((event.loaded / total) * 100)
onProgress?.(percent)
},
})
}
常见的上传分层
src/
api/
file.ts
request/
client.ts
composables/ 或 hooks/
use-upload.ts
大致可以这么分:
api/file.ts:上传、下载、合并分片这些接口函数request/client.ts:底层请求实例use-upload.ts:进度、状态、取消、重试
图片预览怎么接
图片上传通常不会等文件传完才看结果,更多时候会先给一个本地预览。
最常见的方式是 URL.createObjectURL。
function createPreviewUrl(file: File) {
return URL.createObjectURL(file)
}
React 里常见写法:
function ImagePreview({ file }: { file: File }) {
const [url, setUrl] = useState('')
useEffect(() => {
const nextUrl = URL.createObjectURL(file)
setUrl(nextUrl)
return () => URL.revokeObjectURL(nextUrl)
}, [file])
return <img src={url} alt={file.name} />
}
这里最容易漏掉的是回收对象 URL。不回收的话,长时间多图预览会慢慢吃内存。