表单文件
兼容性最好的文件上传方式,也是各类react ui库中「Upload」组件最常见的底层实现
安全性最好,无法拿到文件在本地磁盘的真实路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export default function Main ( ) { const onChange = (ev: any ) => { const target = ev.target console .log ("files are" , target.files ) const reader = new FileReader (); reader.readAsDataURL (target.files [0 ]) reader.onload = () => { console .log ("临时文件URL是:" , reader.result ) } } return <div > <form > <input type ="file" id ="file-input" name ="fileContent" onChange ={onChange} multiple /> </form > </div > };
File对象:
继承自 Blob 对象,所以可以被 FileReader 读取
可以将 Blob 转换成各种形式,比如 base64 编码的 URL、代表文件字节内容ArrayBuffer
FileReader:
它是一个对象,其唯一目的是从 Blob
(因此也从 File
)对象中读取数据
它使用事件来传递数据,因为从磁盘读取数据可能比较费时间。
参考文章:
拖拽文件
H5支持 Drag 拖拽事件,需要用到4个事件控制:
区域外:dragleave,离开范围
区域内:dragenter,用来确定放置目标是否接受放置。
区域内移动:dragover,用来确定给用户显示怎样的反馈信息
完成拖拽(落下)drop:,允许放置对象。
必须监听并且禁用 onDragOver
的默认行为,才能避免浏览器在新Tab上自动打开文件,并且触发自定义的 onDrop
行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 export default function Main ( ) { const handleDrag = (ev: any ) => { ev.preventDefault (); ev.stopPropagation (); console .log ("[handleDrag] type is" , ev.type ) } const handleDrop = (ev: any ) => { ev.preventDefault (); ev.stopPropagation (); console .log ('[handleDrop] type is' , ev.type ) } return <div > <div onDragEnter ={handleDrag} onDragLeave ={handleDrag} onDragOver ={handleDrag} onDrop ={handleDrop} style ={{ border: 'black 2px solid ', width: '400px ', height: '400px '}} > drop your image here </div > </div > };
控制台输出:
在 onDrop
回调中能拿到文件信息
这里也是 File 对象,使用 FileReader
就可以进行操作
疑问:不知道为啥直接查看 event
对象上的属性里的 files ,看到的是 length
为0。而访问 event.dataTransfer.files
就可以拿到 length
不为0,有内容的 files 对象。
参考文章:
粘贴文件
html element 变成可编辑后,可以粘贴文件
粘贴操作在 onpaste
中捕获
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 export default function Main ( ) { const handlePaste = (ev: any ) => { console .log ('[handlePaste] ev is' , ev) console .log ('[handlePaste] files are' , ev.clipboardData .files ) } return <div > <div contentEditable ="true" onPaste ={handlePaste} style ={{ border: 'black 2px solid ', width: '400px ', height: '400px '}} > hello, paste your image here </div > </div > };
控制台输出:
HTML结构变化:
在Chrome中,复制时,还插入了img标签,内容是文件图标的地址(base64格式)
File System Access API:操作本地文件系统
功能最强大,可以直接在浏览器中,读写本地的文件
存在安全风险,因为操作跳出了浏览器这个沙盒,触达了操作系统,需要用户授权
兼容性存在问题,目前主流浏览器均支持。vscode在线版就使用的这套方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 export default function Main ( ) { const readLocalFs = async ( ) => { const dirHandle = await (window as any).showDirectoryPicker () const file = await dirHandle.getFileHandle ("package.json" , { create : true }) const fileData = await file.getFile (); const text = await fileData.text () console .log ('>>> text is' , text) } const writeLocalFs = async ( ) => { const dirHandle = await (window as any).showDirectoryPicker () const file = await dirHandle.getFileHandle ("yuanxin.me.json" , { create : true }) const sampleConfig = JSON .stringify ({ author : 'dongyuanxin' , blog : "https://yuanxin.me" }) const blob = new Blob ([sampleConfig]) const writableStream = await file.createWritable (); await writableStream.write (blob); await writableStream.close (); } return <div > <button onClick ={readLocalFs} > 点我打开文件夹</button > <button onClick ={writeLocalFs} > 点我创建+写入 yuanxin.me.json 文件</button > </div > };
浏览器向用户索要「读」、「写」授权:
在 vscode.dev 上直接编写本地代码,浏览器向用户索要授权:
读取文件的效果:
写入文件的效果:
点击按钮并且授权后,直接向本地文件夹创建了 yuanxin.me.json 文件
将 Blob
对象成功写入文件中
参考文档:
isomorphic-git/ightning-fs:浏览器端同构文件系统 背景:之前在实现低代码编辑器时,需要在浏览器中通过oauth,连接 github,并且将代码上传上去。调研到了 isomorphic-git 这个库,它是一个纯浏览器端的git解决方案,底层是基于 isomorphic-git/lightning-fs 实现的一套浏览器端的同构文件系统,模拟文件系统的增删改查。
特点:
基于 IndexedDB
实现,兼容性好
不会操作本地文件系统,只在浏览器沙盒中运行,安全性好
API 设计上仿照 Node.js 的 fs
官方库,支持文件/文件夹的增删改查,支持 Promise API,使用方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import FS from '@isomorphic-git/lightning-fs' const { promises : fs } = new FS ('yuanxin.me' )export default function App ( ) { const createFileAndRead = async ( ) => { const folderName = '/tmp' fs.mkdir (folderName) console .log (`create folder "${folderName} " success` ) const fileName = folderName + '/test.json' await fs.writeFile (fileName, JSON .stringify ({ site : 'https://yuanxin.me' , boy : true , location : { country : 'cn' , city : 'hangzhou' } })) console .log (`create file "${fileName} " success` ) const fileContent = await fs.readFile (fileName); console .log (`read file content:` , fileContent) } return <div > <div onClick ={createFileAndRead} > 点我创建文件夹和文件,写入内容并且读取</div > </div > }
控制台输出:
成功创建文件夹和文件,并且向文件写入内容
以字节的形式,读出文件内容
IndexedDB:
左侧创建了一个DB,专门用来存储文件系统的内容
右侧是文件系统的具体内容,包括目录结构、文件内容、文件(夹)属性
总结 在之前的工作中,都有实际使用这几种方式来解决具体的业务问题:表单文件是在目前在字节电商团队中频繁使用的;粘贴文件和拖拽文件要追溯到几年前在鹅厂TEG做组件库和富文本编辑时;至于同构和file system access api时在CSIG做微搭低代码时深入使用。整体梳理一遍后,知识脉络确实更加清晰了。