文件系统访问 API 是一种网页 API, 允许访问和修改用户本地的文件。它为构建强大的网页应用打开了新的可能,例如文本编辑器、IDE、图像处理软件,也可以轻松实现导入与导出功能,所有这些都可以在网页前端实现。下面让我们一起来看看如何开始使用这个 API。
使用文件系统访问 API 读取文件
在我们深入研究从用户系统中读取文件的代码之前,有一个很重要的细节需要牢记:调用文件系统访问 API 必须通过用户操作触发,并在安全的上下文中进行。 在下面的例子中,我们将在点击事件中调用该 API。
读取单个文件
从一个文件中读取数据,代码不到 10 行就可以实现。下面是一个示例:
ini
let fileHandle;
document.querySelector(".pick-file").onclick = async () => {
[fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const content = await file.text();
return content;
};
假设我们在 HTML 中有一个 class 为 .pick-file
的按钮。当点击这个按钮时,我们通过调用 window.showOpenFilePicker()
启动文件选择器,并将返回结果存储在一个名为 fileHandle 的变量中。
调用 showOpenFilePicker()
返回的是表示所选文件的 FileSystemFileHandle 对象数组。由于这个例子只选择单个文件,我们会解构这个结果。关于选择多个文件我稍后会介绍。
这些对象包含 kind 和 name 属性。如果打印 console.log(fileHandle)
, 你会看到如下对象:
css
FileSystemFileHandle {kind: 'file', name: 'data.txt'}
kind 属性的值可以是 file 或者 directory。
在 fileHandle 对象上,我们可以调用 getFile () 方法来获取文件详情。调用这个方法会返回一个对象,包含文件最后修改时间、文件名、大小和类型等属性。
最后,我们可以在文件对象上调用 text () 方法获取文件内容。
读取多个文件
如果要读取多个文件,我们需要向 showOpenFilePicker () 传入一个 options 对象。
例如:
dart
let fileHandles;
const options = {
multiple: true,
};
document.querySelector(".pick-file").onclick = async () => {
fileHandles = await window.showOpenFilePicker(options);
// The rest of the code will be shown below
};
默认情况下,multiple 属性为 false。我们还可以通过选项指明可选择的文件类型。
例如,如果只希望选择 .jpeg 文件,options 对象会包含以下内容:
css
const options = {
types: [
{
description: "Images",
accept: {
"image/jpeg": ".jpeg",
},
},
],
excludeAcceptAllOption: true,
};
在这个例子中,fileHandles 是一个包含多个文件的数组,获取内容的方法如下:
ini
let fileHandles;
const options = {
multiple: true,
};
document.querySelector(".pick-file").onclick = async () => {
fileHandles = await window.showOpenFilePicker(options);
const allContent = await Promise.all(
fileHandles.map(async (fileHandle) => {
const file = await fileHandle.getFile();
const content = await file.text();
return content;
})
);
console.log(allContent);
};
使用文件系统访问 API 写入文件
文件系统访问 API 也允许您向文件写入内容。首先,让我们看一下如何保存新文件。
写入新文件
向新文件写入也可以用很少的代码实现!
dart
document.querySelector(".save-file").onclick = async () => {
const options = {
types: [
{
description: "Test files",
accept: {
"text/plain": [".txt"],
},
},
],
};
const handle = await window.showSaveFilePicker(options);
const writable = await handle.createWritable();
await writable.write("Hello World");
await writable.close();
return handle;
};
假设我们有第二个 class 为 save-file 的按钮,点击时,通过 showSaveFilePicker()
方法打开文件选择器,并在 options 对象中指明要保存的文件类型,这里是 .txt
文件。
调用这个方法同样会返回一个 FileSystemFileHandle 对象,就像第一部分一样。在这个对象上调用 createWritable()
方法会返回一个 FileSystemWritableFileStream 对象。然后我们可以通过 write()
方法往这个流里写入内容。
最后,需要调用 close()
方法来关闭文件,完成内容写入。
例如,如果要写入 HTML 代码到文件,只需要在 options 对象里将内容类型设为 "text/html"
, 调用 write () 方法传入 HTML 内容即可。
编辑现有文件
如果要导入一个文件并用文件系统访问 API 编辑,示例代码如下:
ini
let fileHandle;
document.querySelector(".pick-file").onclick = async () => {
[fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const writable = await fileHandle.createWritable();
await writable.write("This is a new line");
await writable.close();
};
如果你已经阅读过前面的内容,可能会注意到我们首先通过 showOpenFilePicker()
和 getFile()
方法读取一个文件,然后用 createWritable()
、write()
和 close()
方法写入同一个文件。
如果导入的文件已经包含一些内容,这个代码会用传给 write()
方法的新内容替换原有的文件内容。
文件系统访问 API 的其他功能
这里不详细展开,文件系统访问 API 还可以列出目录中的文件,以及删除文件和目录。
读取目录
读取目录只需要很少的代码:
dart
document.querySelector(".read-dir").onclick = async () => {
const directoryHandle = await window.showDirectoryPicker();
for await (const entry of directoryHandle.values()) {
console.log(entry.kind, entry.name);
}
};
如果我们添加一个新的 class 为 .read-dir 的按钮,点击时调用 showDirectoryPicker () 方法会打开文件选择器,选择计算机上的一个目录后,这段代码会列出该目录中的文件。
删除文件
可以用以下代码删除一个目录中的文件:
dart
document.querySelector(".pick-file").onclick = async () => {
const [fileHandle] = await window.showOpenFilePicker();
await fileHandle.remove();
};
如果您想删除一个文件夹,只需对上述代码示例做一个小修改即可:
dart
document.querySelector(".read-dir").onclick = async () => {
const directoryHandle = await window.showDirectoryPicker();
await directoryHandle.remove();
};
最后,如果您在选择文件夹时想要删除一个特定文件,可以这样写:
dart
// Delete a single file named data.txt in the selected folder
document.querySelector(".pick-folder").onclick = async () => {
const directoryHandle = await window.showDirectoryPicker();
await directoryHandle.removeEntry("data.txt");
};
如果想删除整个文件夹,需要以下代码:
dart
// Recursively delete the folder named "data"
document.querySelector(".pick-folder").onclick = async () => {
const directoryHandle = await window.showDirectoryPicker();
await directoryHandle.removeEntry('data', { recursive: true });
};
File System Access API 浏览器支持情况
目前,IE 和 Firefox 浏览器似乎都不支持 File System Access API。不过,存在一个名为 browser-fs-access 的腻子 (polyfill)。
总结
如果你想要尝试 File System Access API, 可以查看谷歌工程师开发的这个在线演示文本编辑器。另外,如果你想学习更多关于这个 API 及其所有功能的信息,这里有一些资源:
- File System Access API (W3C 规范)
- File System Access API (MDN)
- 对比度范围,replaceAll 方法,原生文件系统 API (Šime Vidas)
- File System Access API: 简化访问本地文件的操作 (web.dev)
- 使用 browser-fs-access 库读取和写入文件与目录 (web.dev)
- browser-fs-access 仓库 (GitHub)