# 处理文件

> 一份分步指南：如何在 Web App Manifest 中声明 file_handlers，将已安装的 PWA 注册为操作系统级别的文件处理程序；如何接入 launchQueue consumer；以及该 API 不可用时如何回退。

import CompatTable from '@components/CompatTable.astro';

**一句话概括：** 在 Web App Manifest 中声明 `file_handlers`，会将已安装的 PWA 注册为
操作系统对特定文件类型的处理程序，这样双击或使用"打开方式"打开匹配的文件时会直接以该
文件启动你的应用，而无需你自己搭建一套单独的导入流程。

## 1. 在 manifest 中声明 file_handlers

根据 Chrome for Developers 的说明，每个文件处理程序都需要一个位于应用作用域内的
`action` URL、一个将 MIME 类型映射到文件扩展名的 `accept` 对象，以及一个可选的
`icons` 数组，使操作系统能够展示特定文件类型的图标而不只是应用图标：

```json
{
  "file_handlers": [
    {
      "action": "/open-csv",
      "accept": { "text/csv": [".csv"] },
      "icons": [
        { "src": "/icons/csv-icon.png", "sizes": "256x256", "type": "image/png" }
      ],
      "launch_type": "single-client"
    }
  ]
}
```

`launch_type` 决定同时打开多个匹配文件时是复用同一个应用窗口（`"single-client"`，
默认值），还是使用 `"multiple-clients"` 为每个文件各启动一次应用，每次启动的
`LaunchParams.files` 数组仅包含该文件。

## 2. 用 launchQueue 接收被启动的文件

根据 MDN 的说明，一旦操作系统以匹配的文件启动应用，应用会通过
`window.launchQueue.setConsumer()` 接收该文件：

```js
window.launchQueue.setConsumer(async (launchParams) => {
  if (!launchParams.files.length) {
    return;
  }
  const [fileHandle] = launchParams.files;
  const file = await fileHandle.getFile();
  const text = await file.text();
  renderCsv(text);
});
```

## 支持情况

<CompatTable feature="manifest-file-handlers" />

根据 Chrome for Developers 的说明，文件处理在 Chromium 的实现中"仅限于桌面操作系统"——
不要指望它能在 Android 上把文件与应用关联起来。

## 3. 检测支持情况并提供回退

根据 Chrome for Developers 的说明，请同时使用 `"launchQueue" in window` 与
`"files" in LaunchParams.prototype` 两项特性检测，因为仅有 `launchQueue` 并不能确认
consumer 所依赖的 `files` 参数形态。当任意一项检测失败时，为用户提供一个不依赖操作
系统注册的应用内打开方式：

```js
function supportsFileHandling() {
  return 'launchQueue' in window && 'files' in LaunchParams.prototype;
}

if (supportsFileHandling()) {
  window.launchQueue.setConsumer(async (launchParams) => {
    if (!launchParams.files.length) return;
    const file = await launchParams.files[0].getFile();
    renderCsv(await file.text());
  });
} else {
  // 此处不支持 File Handling——改用界面中手动的
  // <input type="file" accept=".csv"> 选择器。
  document.getElementById('open-file-input').hidden = false;
}
```

## 常见问题

- [ ] 根据 Chrome for Developers 的说明，`file_handlers` 注册在 Chromium 中仅限桌面端——
      务必始终提供一个应用内文件选择器，作为 Android 端或回退路径。
- [ ] 请同时使用 `"launchQueue" in window` 与 `"files" in LaunchParams.prototype`
      两项检测（根据 Chrome for Developers 的说明）——仅检测 `launchQueue` 无法确认
      `setConsumer()` 回调所依赖的 `files` 参数形态。
- [ ] 根据 Chrome for Developers 的说明，`launchQueue` 会将启动事件排队，直到有 consumer
      被注册，且无论何时注册，每次启动都会被处理且仅处理一次——但 consumer 函数本身仍
      必须先运行才能读到被启动的文件，因此不要把它的注册挂在其他异步启动逻辑之后。
- [ ] Firefox 与 Safari 均未实现 `file_handlers`——不要把操作系统级别的文件关联当作进入
      该功能的唯一方式。

## 下一步

- [manifest: file_handlers 支持情况](/zh/compatibility/manifest-file-handlers/) —— 完整
  的兼容性数据与 `file_handlers` manifest 参考。
- [接收分享的内容（share target）](/zh/guides/share-target/) —— 一篇相关指南，介绍如何
  通过操作系统分享面板接收其他应用分享的文件与数据。