一键重传的关键是缓存 event.target.files 引用而非操作 input.value;上传函数应接收 filelist 参数,失败时直接复用;formdata.append 必须传第三个参数 file.name;仅对 5xx 错误或服务端声明 retryable 的情况重试,并加入退避机制。

失败后不能“重新选择文件”,只能复用原始 FileList 对象——这是唯一可靠的一键重传前提。
为什么点重试按钮后 input[type="file"] 会变空?
浏览器禁止 JS 赋值 input.value,哪怕赋成原来的字符串也不行。一旦触发提交、或代码里写了 input.value = "",input.files 就永久清空,无法恢复。
- 别在上传失败回调里写
fileInput.value = ""或fileInput.value = fileInput.value - 不要指望用
click()激活文件选择框来“重选”——现代浏览器静默忽略非用户手势的 click - 真正要保存的是
event.target.files这个 FileList 引用,不是路径也不是 name
如何实现“一键重传”逻辑?
把上传动作抽成纯函数,接收 FileList 作为参数,不依赖 DOM 状态。失败时直接调用它,传入之前存好的列表。
- 监听
change事件时立刻缓存:let pendingFiles = e.target.files; - 重试按钮的
onclick不操作 input,只调用uploadFiles(pendingFiles) - 如果用了封装请求库(如 axios),检查拦截器是否无意执行了
formData.delete("file")或替换了原始File对象
FormData.append 为什么服务端收不到文件?
漏传第三个参数(文件名)是最常见原因。后端 multipart 解析器(如 Express multer)依赖它来还原文件元信息。
- 必须写成:
formData.append("file", file, file.name),不能只写前两个参数 - 别对
File对象做JSON.stringify()再塞进 FormData——FormData 只认原生File或Blob - 用
fetch时绝不能手动设置headers: { "Content-Type": "multipart/form-data" }——浏览器会自动生成带 boundary 的正确头,手动设反而导致 400
哪些失败状态才该允许重试?
不是所有报错都适合点一下就重来。400、401、413 这类客户端错误重试只会重复失败;500、502、503、504 才是服务端问题,值得重试。
- 优先信任服务端返回的
retryable: true字段,比硬编码状态码更灵活 - 自己加退避:第一次失败等 500ms,第二次等 1s,最多试 2 次——避免瞬间连发压垮接口
- fetch 场景下,
response.ok === false且response.status >= 500才进入重试分支;AbortError也要捕获,它是网络中断的信号
真正的难点不在“怎么点”,而在“点之前有没有把 files 安全地拎出来并稳稳攥住”。一旦这个引用丢失,所谓一键重传就只剩刷新页面这一个选项了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











