React中如何正确判断上传对象是文件而非目录?
可靠判断上传对象是否为目录的实现方案
原有方案的问题
原有判断逻辑存在三处明显缺陷:
- 依赖
File.type !== ''作为文件判定依据,大量合法文件(包括.psd、.dwg等专业格式、无后缀二进制文件、部分小众压缩包)无法被浏览器识别MIME类型,type属性返回空字符串,会被误判为目录 - FileReader读取逻辑为异步写法但未返回结果,实际无法拿到判断返回值,且读取整个文件的方式在大文件场景下性能极差,错误类型硬编码也存在兼容性问题
- 给input添加
directory/webkitdirectory属性的方案仅对点击选择文件生效,完全无法覆盖拖拽、粘贴上传场景,确实不适用。
优先实现:利用浏览器原生Entry API(准确率100%)
所有现代浏览器均支持通过DataTransferItem暴露的文件系统元信息判断类型,是拖拽、粘贴场景下的最优方案,不需要读取文件内容:
/** * 从拖拽/粘贴的DataTransfer对象中提取所有合法文件,自动过滤目录 * @param {DataTransfer} dataTransfer 拖拽/粘贴事件中拿到的dataTransfer对象 * @returns {Promise<File[]>} 合法文件列表 */ async function extractValidFiles(dataTransfer) { const fileList = []; for (const item of dataTransfer.items) { // 兼容不同浏览器的Entry获取方法 const entry = item.webkitGetAsEntry?.() || item.getAsEntry?.(); // 无法获取Entry时走兜底逻辑 if (!entry) { const file = item.getAsFile(); if (file) { const isDir = await checkIsDirectoryFallback(file); if (!isDir) fileList.push(file); } continue; } // 直接通过原生属性判断是否为目录,无误差 if (entry.isDirectory) continue; // 是文件则读取对应File实例 const file = await new Promise(resolve => entry.file(resolve)); fileList.push(file); } return fileList; }
兜底方案:针对已拿到File实例的场景
如果已经拿到File实例(比如input选择的文件、经过多层传递后丢失Entry信息的文件),可以通过读取极小片段的方式判断,性能远高于全量读取,且不会误判无MIME的合法文件:
/** * 兜底判断File实例是否为目录 * @param {File} file * @returns {Promise<boolean>} true=是目录,false=是合法文件 */ async function checkIsDirectoryFallback(file) { try { // 仅读取文件前1个字节,大文件场景下也几乎无性能消耗 // 合法文件(包括0字节空文件)调用该方法不会抛错,仅目录会触发读取异常 await file.slice(0, 1).arrayBuffer(); return false; } catch (err) { return true; } }
避坑说明
- 不要通过
file.size === 0判断目录:合法的空文本、空配置文件、空占位文件大小均为0,会产生大量误判 - 不要硬编码读取错误的name值:不同浏览器、不同操作系统下读取目录抛出的错误类型存在差异,只要读取切片失败即可判定为非合法文件
- 不要全量读取文件内容判断:通过
slice读取极小片段即可完成判断,全量读取会导致大文件场景下页面卡顿、内存占用过高
内容的提问来源于stack exchange,提问作者gnd20032
相关产品推荐
相关产品推荐

