ファイルアップロード
15.4.1. FormDataの構成
ファイルアップロードはFormDataオブジェクトを使用し、Content-Typeはmultipart/form-dataで送信します。
typescript
async function uploadFile(file: File, metadata: { category: string }) {
const formData = new FormData()
formData.append('file', file)
formData.append('category', metadata.category)
const response = await axios.post('/api/files', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
return response.data
}- ファイルとメタデータを1つの
FormDataに含めて単一リクエストで送信します。 Content-Typeヘッダーは明示的にmultipart/form-dataを指定します。- 複数ファイルアップロード時は同一キーで
appendを繰り返し呼び出します。
15.4.2. 進捗率の表示
大容量ファイルアップロード時、AxiosのonUploadProgressコールバックを活用して進捗率を表示します。
vue
<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'
const progress = ref(0)
const isUploading = ref(false)
async function upload(file: File) {
isUploading.value = true
progress.value = 0
const formData = new FormData()
formData.append('file', file)
try {
await axios.post('/api/files', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress(event) {
if (event.total) {
progress.value = Math.round((event.loaded / event.total) * 100)
}
},
})
} finally {
isUploading.value = false
}
}
</script>
<template>
<div v-if="isUploading" class="progress-bar">
<div class="progress-fill" :style="{ width: `${progress}%` }" />
<span>{{ progress }}%</span>
</div>
</template>event.totalがundefinedになる可能性があるため、必ず存在有無を確認します。- 進捗率は0~100の整数に変換して表示します。
15.4.3. ドラッグアンドドロップ
ファイルのドラッグアンドドロップは@dragover.preventと@drop.preventイベントで処理します。
vue
<script setup lang="ts">
import { ref } from 'vue'
const isDragOver = ref(false)
const selectedFile = ref<File | null>(null)
function handleDrop(event: DragEvent) {
isDragOver.value = false
const files = event.dataTransfer?.files
if (files && files.length > 0) {
selectedFile.value = files[0]
}
}
function handleFileSelect(event: Event) {
const target = event.target as HTMLInputElement
if (target.files && target.files.length > 0) {
selectedFile.value = target.files[0]
}
}
</script>
<template>
<div
class="drop-zone"
:class="{ 'drag-over': isDragOver }"
@dragover.prevent="isDragOver = true"
@dragleave="isDragOver = false"
@drop.prevent="handleDrop"
>
<p>ファイルをドラッグするか、クリックして選択してください。</p>
<input type="file" @change="handleFileSelect" />
</div>
<p v-if="selectedFile">選択されたファイル: {{ selectedFile.name }}</p>
</template>@dragover.preventはブラウザのデフォルト動作(ファイルを開く)をブロックするために必須で適用します。- ドラッグ状態(
isDragOver)を追跡して視覚的フィードバックを提供します。 <input type="file">を併設してクリック方式もサポートします。
15.4.4. クライアント検証
ファイルアップロード前にクライアントで拡張子、サイズ、MIMEタイプを検証します。
typescript
// src/utils/fileValidation.ts
interface FileValidationResult {
valid: boolean
error: string | null
}
const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'docx']
const MAX_FILE_SIZE = 50 * 1024 * 1024 // 50MB
const ALLOWED_MIME_TYPES = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
]
export function validateFile(file: File): FileValidationResult {
const extension = file.name.split('.').pop()?.toLowerCase() ?? ''
if (!ALLOWED_EXTENSIONS.includes(extension)) {
return { valid: false, error: `許可されていないファイル形式です。(${ALLOWED_EXTENSIONS.join(', ')})` }
}
if (file.size > MAX_FILE_SIZE) {
return { valid: false, error: 'ファイルサイズは最大50MBまで許可されます。' }
}
if (!ALLOWED_MIME_TYPES.includes(file.type)) {
return { valid: false, error: 'MIMEタイプが許可リストに含まれていません。' }
}
return { valid: true, error: null }
}| 検証項目 | 基準 | 備考 |
|---|---|---|
| 拡張子 | 許可リストベース | バックエンドの許可リストと同期 |
| ファイルサイズ | 最大50MB | プロジェクト要件に応じて調整可能 |
| MIMEタイプ | 許可リストベース | 拡張子偽装防止のための二重検証 |
- 許可拡張子リストはバックエンドの標準と同一に維持しなければなりません。
- クライアント検証はユーザーの利便性のための事前検証であり、サーバー側の検証を代替するものではありません。