原生 input[type="file"] 的 files 是只读 filelist,无法局部删除;必须用 map 管理可变文件集合,以唯一 key 关联预览项,手动构建 formdata 提交。

原生 input[type="file"] 的 files 是只读的,无法局部删除某一项——点“删除”只删 DOM 预览项,不删文件数据,提交时照样全发出去。必须手动维护一份可变的文件集合,再用它驱动渲染和提交。
为什么不能直接操作 input.files 删除某张图
因为 HTMLInputElement.files 返回的是只读 FileList,不是数组;它没有 splice、filter 或 delete 方法。试图用 input.files[0] = null 或 delete input.files[0] 都无效。设 input.value = "" 会清空全部,不是“单独”删除。
- 浏览器控制台打印
input.files看起来像数组,但它是类数组对象,原型链上没有修改方法 - 每次触发
change或input事件,input.files都是全新对象,缓存引用没意义 - IE11 和旧 Safari 连
URL.createObjectURL(file)都不支持,更别提动态管理
用 Map 存文件 + data-id 绑定预览项
核心是把「用户当前真正想上传的文件」从 DOM 解耦出来,存在一个可增可删的 Map 里,每个条目用唯一 ID 关联预览容器。
- 生成预览时:对每个
File计算唯一 key(推荐`id${file.lastModified}${file.size}`),存入images = new Map(),同时给<div class="preview-item"> 加 <code>data-id="id123456789" - 点击删除按钮时:取
event.target.closest('[data-id]').dataset.id,然后images.delete(id),再调用renderPreview()全量重绘 - 不要用
file.name当 key——重名文件、中文名、特殊字符都会撞;也不要用索引,因为多次选择后顺序不可控 for (const fileObj of images.values()) { formData.append('images', fileObj.file, fileObj.name) }- 如果预览用了
URL.createObjectURL(),记得在renderPreview()里先URL.revokeObjectURL(oldSrc),否则内存泄漏 - 若需兼容老浏览器(如 Safari showPicker() 不可用,仍得靠
input.click()触发弹窗
提交时绕过 input.files,手组 FormData
表单提交不依赖原生 input 的自动打包逻辑,而是遍历 Map,把每个 File 手动 append 进 FormData。
最容易被忽略的是:删除操作后必须显式调用重绘函数,且重绘逻辑要能处理空 Map(清空预览区);还有就是每次选新文件前,建议先 input.value = "",避免重复触发同一堆文件。











