前端校验是第一道筛子,核心在于与后端严密衔接:change事件中用file.size(字节)和file.type(需后端magic number二次验证)做即时检查,失败时清空input.value并精准反馈;校验通过不等于上传成功,还需应对Nginx/PHP等网关层限制导致的413等静默失败。

文件上传验证失败,核心问题不在“怎么拦”,而在于“拦在哪”和“拦完怎么收场”。前端校验只是第一道筛子,真正决定成败的是它和后端的衔接是否严密。
input[type="file"] 的 change 事件里做基础校验
这是用户选完文件后最自然的拦截点,适合检查大小、类型等可即时获取的信息。但要注意:它不保证提交一定成功,也不能替代后端校验。
-
input.files[0].size单位是字节,别直接比 MB 值;建议用1024 * 1024 * 2这种写法避免歧义 -
input.files[0].type可被伪造,仅作辅助判断;必须配合后端用文件头(magic number)二次确认 - 校验失败时清空
input.value是安全操作,但别在 submit 事件里再读input.files——此时已是空列表 - 别依赖
input.value显示的路径做逻辑判断,它只是浏览器伪造的只读字符串
form 的 submit 事件里调用 checkValidity() 并 preventDefault()
HTML 原生验证(required、accept)只在表单提交时触发,且必须由 JS 主动接管才能阻止页面刷新或错误提示丢失。
-
event.preventDefault()必须是 submit 处理器的第一行,否则验证失败时页面会刷新,错误提示根本来不及显示 - 仅靠
input.setCustomValidity("xxx")不会自动触发 UI 更新,必须配合form.checkValidity()才能激活:invalid伪类和错误气泡 - 多个文件输入项时,别只查第一个:
Array.from(form.elements).every(el => el.checkValidity()) - 移动端 Safari 对
checkValidity()时机敏感,建议在blur或change时就调用并同步更新aria-invalid
后端返回 400 错误后如何精准映射字段级提示
前端收到错误响应后,不是简单弹个 toast,而是要把错误信息塞回对应 input 后的提示区域。这一步卡住,90% 的用户不知道哪错了。
立即学习“前端免费学习笔记(深入)”;
- 服务端返回的字段名(如
{"avatar": ["文件类型不支持"]})必须和前端input[name]完全一致,包括大小写和下划线 - 每个
input后应紧跟一个<span class="error-message"></span>,不要用全局浮层替代定位 - 渲染新错误前,先清空所有旧提示:
form.querySelectorAll(".error-message").forEach(el => el.remove()) - 出错字段要自动聚焦:
input.focus(),否则用户可能还在看别的地方
上传失败后保持文件选择状态并支持重试
浏览器禁止 JS 给 input[type="file"] 赋值,所以“重试”不是恢复文件,而是复用原始 FileList 对象。
- 在
change事件中把event.target.files存到变量里,比如let pendingFiles = event.target.files - 重试按钮点击后,直接调用封装好的上传函数:
uploadFiles(pendingFiles),别试图触发input.click() - FormData append 时务必传第三个参数:
formData.append("file", file, file.name),漏掉会导致某些后端(如 Express multer)解析失败 - 区分可重试与不可重试错误:500/503 可重试;400/413 重试无意义,应提示用户修改操作
最容易被忽略的一点是:前端校验通过 ≠ 文件能成功抵达后端。Nginx 的 client_max_body_size、PHP 的 post_max_size、云 WAF 的请求体限制,都可能在网关层就把请求干掉了——此时 Network 面板里连请求都看不到,只有空白响应或 413 错误。



















