File Operations in the Browser#
When developing a WebApp, you may encounter file-related operations such as uploading files to a server, downloading files to the local machine, and caching files. Below, we will introduce several different ways to perform file operations.
Tag-based Upload and Download#
The most commonly used method for file upload is to use the input tag. By setting the type="file"
attribute of the input tag, users can select files from their local machine for upload.
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" />
}
File Access API#
The File System Access API is part of the File System API and allows file reading and writing operations to be performed under user interaction.
When using this API for file operations, the following interfaces are used:
- showOpenFilePicker: Used to display a file picker and allow the user to select one or more files, and then return the handles of these files.
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: Used to display a file picker and allow the user to save a file (overwrite or create a new one).
export function PickerFS() {
const handleChooseFile = async () => {
const directoryHandle = await window.showDirectoryPicker();
const keys = directoryHandle.keys();
// Print the names of all files in this directory
for await (const key of keys) {
console.log(key);
}
};
return <Button onClick={handleChooseFile}>Click</Button>
}
- showDirectoryPicker: Used to display a directory picker and allow the user to select a directory.
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>
}
Origin-private File System#
The Origin-private File System is similar to the file access system mentioned above, as they are both part of the File System API. However, the most direct difference between them is whether they are visible to the user. The showXXX interfaces of the origin-private file system require opening a file (or directory) picker, and the user needs to actively select the file (or directory). The saved file also needs to be saved to the path specified by the user. However, the interaction of the origin-private file system is not visible to the user, and the saved file is processed data that the user cannot see the original data.
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()
returns a file handle representing the root directory of the user's local file system. Then, use getFileHandle
to get the handle of the specified file. If the file does not exist, it will be created if create
is set to true. Next, use createWritable
to create a writable stream. Developers can use this writable stream to write data to the specified file, and finally close the writable stream.