Skip to main content

文件处理

不同于文件上传,这里介绍的是本地到浏览器的文件处理过程

表单文件

  • 兼容性最好的文件上传方式,也是各类 react ui 库中「Upload」组件最常见的底层实现
  • 安全性最好,无法拿到文件在本地磁盘的真实路径
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 行为

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>
);
}

控制台输出:

粘贴文件

  • html element 变成可编辑后,可以粘贴文件
  • 粘贴操作在 onpaste 中捕获
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>
);
}

控制台输出:

  • 同样是 File 对象以及关键的信息

HTML 结构变化:

  • 在 Chrome 中,复制时,还插入了 img 标签,内容是文件图标的地址(base64 格式)

File System Access API:操作本地文件系统

  • 功能最强大,可以直接在浏览器中,读写本地的文件
  • 存在安全风险,因为操作跳出了浏览器这个沙盒,触达了操作系统,需要用户授权
  • 兼容性存在问题,目前主流浏览器均支持。vscode 在线版 就使用的这套方案
export default function Main() {

const readLocalFs = async () => {
const dirHandle = await (window as any).showDirectoryPicker()
const file = await dirHandle.getFileHandle("package.json", {
create: true
}) // 获取到一个 File 对象
const fileData = await file.getFile(); // 获取到 File 的数据
const text = await fileData.text() // 拿到File数据的文本形式
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]) // 创造blob对象
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 上直接编写本地代码,浏览器向用户索要授权: 读取文件的效果: 写入文件的效果:

isomorphic-git/lightning-fs:浏览器端同构文件系统

背景:之前在实现低代码编辑器时,需要在浏览器中通过 oauth,连接 github,并且将代码上传上去。调研到了 isomorphic-git 这个库,它是一个纯浏览器端的 git 解决方案,底层是基于 isomorphic-git/lightning-fs 实现的一套浏览器端的同构文件系统,模拟文件系统的增删改查。 特点:

  • 基于 IndexedDB 实现,兼容性好
  • 不会操作本地文件系统,只在浏览器沙盒中运行,安全性好
  • API 设计上仿照 Node.js 的 fs 官方库,支持文件/文件夹的增删改查,支持 Promise API,使用方便
import FS from "@isomorphic-git/lightning-fs";

const { promises: fs } = new FS("yuanxin.me");

export default function App() {
const createFileAndRead = async () => {
const folderName = "/tmp";
await 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 做微搭低代码时深入使用。整体梳理一遍后,知识脉络更清晰了。