在瀏覽器中進行文件操作#
在開發 WebApp 時可能會遇到文件相關的操作,比如上傳文件到伺服器、下載文件到本地、快取文件等,下面會介紹幾種不同的方式進行文件操作。
基於標籤的上傳和下載#
最常用的文件上傳方式應該是使用 input 標籤,通過設置 input 標籤的 type=”file”
可以允許用戶從本地選擇文件進行上傳。
function InputFile() {
const [file, setFile] = useState<File | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setFile(file);
};
return <input onChange={handleChange} type="file" />
}
文件訪問 API#
文件系統訪問 API(File System Access API)屬於文件系統 API 的一部分,可以通過使用 API 在用戶的操作下完成文件的讀寫。
在使用該 API 進行文件操作時會使用以下接口
- showOpenFilePicker:用於顯示一個文件選擇器並允許用戶選擇一個或多個文件,然後返回這些文件的句柄;
export function PickerFS() {
const [file, setFile] = useState<File | null>(null);
const handleChooseFile = async () => {
const fileHandles = await window.showOpenFilePicker();
const file = await fileHandles[0].getFile();
setFile(file);
};
return <Button onClick={handleChooseFile}>Click</Button>
}
- showSaveFilePicker:用於顯示一個文件選擇器並允許用戶保存一個文件(覆蓋或者新建);
export function PickerFS() {
const handleChooseFile = async () => {
const directoryHandle = await window.showDirectoryPicker();
const keys = directoryHandle.keys();
// 打印該目錄下所有文件的名字
for await (const key of keys) {
console.log(key);
}
};
return <Button onClick={handleChooseFile}>Click</Button>
}
- showDirectoryPicker:用於顯示一個目錄選擇器並允許用戶選擇一個目錄;
export function PickerFS() {
const [file, setFile] = useState<File | null>(null);
const handleDownloadFile= async () => {
const opts = {
suggestedName: "test.txt",
types: [
{
description: "Text file",
accept: { "text/plain": [".txt"] },
},
],
};
const fileHandle = await window.showSaveFilePicker(opts);
const writable = await fileHandle.createWritable();
await writable.write("Hello, world!");
await writable.close();
};
return <Button onClick={handleDownloadFile}>Click</Button>
}
源私有文件系統#
源私有文件系統跟上面的文件訪問系統類似,都是文件系統 API 的一部分,但是它們有個最直接的差異就是是否對用戶可見。showXXX 接口都需要打開文件(目錄)選擇器,並且需要用戶主動選擇文件(目錄),保存的文件也是需要保存到用戶指定的路徑,但是源私有文件系統的交互不會對用戶可見,並且保存的文件是經過處理的數據,用戶無法看到原始數據。
export function OpFs() {
const handleChooseFile = async (event: React.ChangeEvent<HTMLInputElement>) => {
const fileList = event.target.files;
const file = fileList && fileList[0];
if (!file) return;
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle(file.name, { create: true });
const writable = await fileHandle.createWritable();
await writable.write(file);
await writable.close();
};
return <InputFile onChange={handleChooseFile} />;
}
await navigator.storage.getDirectory()
返回一個表示用戶本地文件系統根目錄的文件句柄,然後通過 getFileHandle
獲取指定文件的句柄,create 為 true 表示如果沒有該文件的話就會創建一個,接著使用 createWritable
創建可寫流,開發者可以通過這個可寫流向指定文件寫入數據,最後關閉可寫流。
注意事項#
參見#
- https://web.dev/articles/origin-private-file-system?hl=zh-cn#specifics_of_the_origin_private_file_system
- https://developer.chrome.com/docs/capabilities/web-apis/file-system-access?hl=zh-cn
- https://gine.me/posts/70f8e931bc17426fb54127948bcf4a0e
- https://hughfenghen.github.io/posts/2024/03/14/web-storage-and-opfs/