質問
大容量ファイルをインポートするデスクトップWebアプリケーションがあります。ユーザーはクリックしてファイルを選択したり、ドロップゾーンにファイルをドラッグしたり、showOpenFilePicker()をサポートするブラウザを使用したりできます。機能検出、フォールバック、キャンセル、進捗表示、アクセシビリティ、プライバシー、再試行を含む完全なフロントエンドフローを設計してください。どの機能がベースラインの依存関係ではなくエンハンスメント(拡張機能)であるかも説明してください。
参考回答
単一の入力モデルを使用します(Fileまたは読み取り可能なファイルハンドル)。UIが受け取るべきなのは、候補ファイル、ソース、サイズ、タイプ、状態のみです。ベースラインは、広く利用可能であるため、ラベル付けされた可視の<input type="file">とします。ドラッグ&ドロップは、同じモデルを生成するための別の手段です。セキュアコンテキストにwindow.showOpenFilePickerが存在する場合は、エンハンスメントとしてユーザークリックハンドラーからのみ使用します。ピッカーが失敗した場合、キャンセルされた場合、または権限が拒否された場合は、例外を非サポートとして処理するのではなく、inputパスを利用可能な状態に保ちます。
フローをidle → selected → validating → reading → uploading → completeとしてモデル化し、cancelledとfailedの分岐を設けます。拡張子、宣言されたMIME、実際のサイズ制限、およびビジネスフォーマットを検証します。クライアント側のチェックは迅速なフィードバックを提供しますが、サーバー側でも再度検証する必要があります。大容量ファイルはストリームまたはスライスとして読み取り、AbortControllerで読み取りとアップロードをキャンセルし、再生可能なチャンクのみを再試行して、完了したバイト数と推定値を表示します。
名前、パス、コンテンツはユーザーデータです。必要なメタデータのみを表示し、コンテンツをログから除外します。プレビュー用のオブジェクトURLはURL.revokeObjectURL()で破棄します。ドロップゾーンにはキーボードおよび通常のボタンによる操作パスが必要であり、フォーカス、エラー、進捗状況はセマンティック要素とライブリージョンを通じて公開します。ドラッグ操作を唯一の操作方法にしてはいけません。データがいつアップロードされるかをユーザーに伝え、キャンセル後はローカル参照と未完了の処理をクリーンアップします。
主な難所
showOpenFilePicker()は一時的なユーザーアクティベーションを必要とするため、タイマー、ページ読み込み、または遅延したバックグラウンドタスクから呼び出すことはできません。これはハンドルを返しますが、永続的な読み取り権限を意味するものではありません。機能検出は権限を保証しません。inputのacceptはヒントに過ぎず、セキュリティチェックではないため、File.typeは空であるかクライアントから提供されたものである可能性があります。ドロップハンドラーはデフォルトのナビゲーションを防ぎ、ディレクトリ、複数ファイル、および非常に大きなリストを適切に処理する必要があります。
よくある間違い
- File System Access APIをベースラインの依存関係として扱うこと。
- 拡張子やMIME値をファイルのセキュリティ検証として信頼すること。
- ラベル、キーボードパス、およびスクリーンリーダーパスを維持せずにinputを非表示にすること。
- アップロード前に大容量ファイル全体をメモリに読み込むこと。
- キャンセル、フォーマットエラー、ネットワーク障害のすべてに対して一様に「権限が拒否されました」と表示すること。
トレードオフの分析
inputは最も適用範囲が広い信頼できるベースラインですが、ハンドルレベルの操作が不足しています。ピッカーは繰り返しの選択を減らし、よりリッチなハンドル操作を提供できますが、ブラウザのサポート、HTTPS、ユーザーアクティベーション、および権限に依存します。ドラッグ&ドロップはデスクトップのスループットを向上させますが、キーボードの代替手段が必要です。クライアント側の検証はフィードバック時間を短縮し、サーバー側の検証はセキュリティの境界となります。チャンク化と再開機能は状態の複雑さを増しますが、大容量転送の再試行コストを削減します。
実行可能なコード例
async function chooseFiles(input) {
if (window.showOpenFilePicker && window.isSecureContext) {
try {
const handles = await window.showOpenFilePicker({ multiple: true });
return Promise.all(handles.map((handle) => handle.getFile()));
} catch (error) {
if (error?.name !== "AbortError") throw error;
}
}
return Array.from(input.files ?? []);
}これをユーザークリックハンドラーから呼び出します。本番環境のコードでは、タイプ、数、サイズを制限し、失敗をユーザー向けの状態にマッピングする必要があります。ドロップハンドラーはevent.dataTransfer.filesを同じ配列に変換し、検証およびアップロードの状態マシンを再利用できます。
面接官の追加質問
なぜページ読み込み中にshowOpenFilePicker()を呼び出さないのですか?このAPIには一時的なユーザーアクティベーションが必要なため、遅延した呼び出しやバックグラウンドでの呼び出しは拒否される可能性があります。acceptはマルウェアを防ぎますか?いいえ。これはピッカーのヒントであり、セキュリティチェックはサーバー側で行うべきです。なぜフォールバックを維持するのですか?ブラウザのサポート状況や権限は異なる可能性があるため、ベースラインとして広く利用可能なinputで動作させる必要があるからです。
追加質問への回答
フォールバックパスはどのようにテストしますか?
非対応ブラウザ、非セキュアコンテキスト、キャンセル、権限拒否を個別にテストし、inputパスで引き続きインポートが完了することを確認します。
メモリの枯渇をどのように防ぎますか?
File.slice()またはストリームを使用し、同時実行数を制限し、チャンク単位でアップロードし、ユーザーがキャンセルしたときにコントローラーをアボートします。
フローをアクセシブルにするにはどうすればよいですか?
実際のラベルとボタンを維持し、ドロップゾーンをフォーカス可能にし、キーボードでinputをトリガーできるようにして、エラーと進捗状況をテキストおよびライブリージョンに関連付けます。
どのメトリクスで動作を検証できますか?
ファイルコンテンツを記録することなく、ブラウザごとの成功率、キャンセル率、検証失敗率、再試行後の最終的な完了率、最初の進捗までの時間、フォールバック率を追跡します。