FormDataの使い方 - JavaScriptでフォームデータを扱う基礎
JavaScriptのFormDataオブジェクトを使ったフォームデータの操作方法を初心者向けに解説。ファイルアップロードから複雑なフォーム処理まで、実用的なサンプルコードを紹介します。
FormDataの使い方 - JavaScriptでフォームデータを扱う基礎
みなさん、JavaScriptでフォームデータを扱う際に「ファイルアップロードってどうやるの?」って困ったことはありませんか?
「複雑なデータ送信が難しい」 「ファイル送信の方法が分からない」
こんな悩みを感じたことはありませんか?
この記事では、JavaScriptのFormDataオブジェクトの基本的な使い方から実用的な応用例まで、初心者にも分かりやすく解説します。 現代的なWeb開発に欠かせない知識を一緒に身につけていきましょう。
FormDataって何のこと?
FormDataは、HTMLフォームのデータをJavaScriptで操作するためのWebAPIです。
簡単に言うと、フォームから送信するデータを簡単に扱えるようにしてくれる便利な機能です。 通常のテキストデータだけでなく、ファイルや画像なども一緒に送信できます。
// 基本的な使い方const formData = new FormData();formData.append('name', '田中太郎');formData.append('email', 'tanaka@example.com');
console.log(formData.get('name')); // 田中太郎
この例では、FormDataオブジェクトを作成して、名前とメールアドレスを追加しています。
new FormData()
でFormDataオブジェクトを作成します。
const formData = new FormData();
append
メソッドでデータを追加できます。
formData.append('name', '田中太郎');
get
メソッドで追加したデータを取得できます。
FormDataの基本的な使い方
FormDataオブジェクトの作成方法
FormDataオブジェクトは2つの方法で作成できます。
// 空のFormDataを作成const formData = new FormData();
// 既存のHTMLフォームからFormDataを作成const form = document.getElementById('myForm');const formDataFromForm = new FormData(form);
1つ目の方法は、空のFormDataオブジェクトを作成する方法です。
const formData = new FormData();
この後、手動でデータを追加していきます。
2つ目の方法は、既存のHTMLフォームからFormDataを作成する方法です。
const formDataFromForm = new FormData(form);
HTMLフォームの全ての入力値が自動的にFormDataに追加されます。
データの追加と操作方法
FormDataには様々なメソッドが用意されています。
const formData = new FormData();
// データを追加formData.append('username', 'user123');formData.append('email', 'user@example.com');formData.append('age', 25);
// 複数の値を同じキーで追加formData.append('hobby', 'プログラミング');formData.append('hobby', '読書');formData.append('hobby', '映画鑑賞');
// データを取得console.log(formData.get('username')); // user123console.log(formData.getAll('hobby')); // ['プログラミング', '読書', '映画鑑賞']
// データの存在確認console.log(formData.has('email')); // true
// データを更新(既存の値を置き換え)formData.set('age', 26);
// データを削除formData.delete('age');
各メソッドについて詳しく説明します。
append
メソッドでデータを追加します。
formData.append('username', 'user123');
同じキーで複数回追加すると、配列のように複数の値を保持できます。
get
メソッドで最初の値を取得します。
console.log(formData.get('username')); // user123
getAll
メソッドで同じキーの全ての値を配列で取得します。
console.log(formData.getAll('hobby')); // ['プログラミング', '読書', '映画鑑賞']
FormDataの内容を確認しよう
FormDataの中身を確認する便利な関数を作ってみましょう。
function displayFormData(formData) { console.log('FormData の内容:'); for (const [key, value] of formData.entries()) { if (value instanceof File) { console.log(`${key}: ファイル - ${value.name} (${value.size} bytes)`); } else { console.log(`${key}: ${value}`); } }}
// 使用例const formData = new FormData();formData.append('name', '田中太郎');formData.append('message', 'こんにちは');
displayFormData(formData);
この関数について詳しく説明します。
entries()
メソッドでFormDataの全ての要素を取得できます。
for (const [key, value] of formData.entries()) { // キーと値のペアを処理}
ファイルかどうかを判定して、適切な情報を表示します。
if (value instanceof File) { console.log(`${key}: ファイル - ${value.name} (${value.size} bytes)`);} else { console.log(`${key}: ${value}`);}
ファイルアップロードを実装してみよう
基本的なファイルアップロード
ファイルアップロード機能を作ってみましょう。
<!DOCTYPE html><html><head> <title>ファイルアップロード</title></head><body> <form id="uploadForm"> <div> <label>ファイルを選択:</label> <input type="file" id="fileInput" accept="image/*" multiple> </div> <div> <label>説明:</label> <textarea id="description" placeholder="ファイルの説明を入力"></textarea> </div> <button type="submit">アップロード</button> </form>
<script> document.getElementById('uploadForm').addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(); const fileInput = document.getElementById('fileInput'); const description = document.getElementById('description').value; // 複数ファイルの追加 for (let i = 0; i < fileInput.files.length; i++) { formData.append('files', fileInput.files[i]); } // 説明文を追加 formData.append('description', description); try { const response = await fetch('/upload', { method: 'POST', body: formData // Content-Typeは自動設定される }); if (response.ok) { alert('アップロード成功!'); } else { alert('アップロード失敗'); } } catch (error) { console.error('エラー:', error); alert('ネットワークエラー'); } }); </script></body></html>
このコードの流れを詳しく説明します。
まず、フォームの送信イベントをキャッチします。
document.getElementById('uploadForm').addEventListener('submit', async (e) => { e.preventDefault(); // デフォルトの送信を防ぐ
preventDefault()
でブラウザのデフォルト送信動作を止めます。
ファイル選択要素から選択されたファイルを取得します。
const fileInput = document.getElementById('fileInput');
for (let i = 0; i < fileInput.files.length; i++) { formData.append('files', fileInput.files[i]);}
複数のファイルが選択されている場合は、全てFormDataに追加します。
fetchを使ってサーバーに送信します。
const response = await fetch('/upload', { method: 'POST', body: formData // Content-Typeは自動設定される});
重要: Content-Typeヘッダーは自動設定されるので、手動で設定する必要はありません。
プレビュー機能付きアップロード
より実用的なファイルアップロード機能を作ってみましょう。
class FileUploader { constructor(containerId) { this.container = document.getElementById(containerId); this.files = []; this.init(); } init() { this.createUI(); this.attachEvents(); } createUI() { this.container.innerHTML = ` <div class="upload-area" id="uploadArea"> <input type="file" id="fileInput" multiple accept="image/*" style="display: none;"> <div class="upload-text"> <p>ファイルをドラッグ&ドロップまたはクリックして選択</p> </div> </div> <div id="previewArea" class="preview-area"></div> <button id="uploadBtn" class="upload-button" style="display: none;">アップロード</button> `; } attachEvents() { const uploadArea = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput'); const uploadBtn = document.getElementById('uploadBtn'); // クリックでファイル選択 uploadArea.addEventListener('click', () => fileInput.click()); // ファイル選択時の処理 fileInput.addEventListener('change', (e) => { this.handleFiles(e.target.files); }); // ドラッグ&ドロップ uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('drag-over'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('drag-over'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('drag-over'); this.handleFiles(e.dataTransfer.files); }); // アップロードボタン uploadBtn.addEventListener('click', () => this.uploadFiles()); } handleFiles(fileList) { this.files = Array.from(fileList); this.createPreviews(); const uploadBtn = document.getElementById('uploadBtn'); uploadBtn.style.display = this.files.length > 0 ? 'block' : 'none'; } createPreviews() { const previewArea = document.getElementById('previewArea'); previewArea.innerHTML = ''; this.files.forEach((file, index) => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; if (file.type.startsWith('image/')) { const img = document.createElement('img'); img.src = URL.createObjectURL(file); img.style.maxWidth = '200px'; img.style.maxHeight = '200px'; previewItem.appendChild(img); } const info = document.createElement('div'); info.innerHTML = ` <p><strong>${file.name}</strong></p> <p>サイズ: ${(file.size / 1024).toFixed(2)} KB</p> <button onclick="uploader.removeFile(${index})">削除</button> `; previewItem.appendChild(info); previewArea.appendChild(previewItem); }); } async uploadFiles() { if (this.files.length === 0) return; const formData = new FormData(); this.files.forEach((file) => { formData.append('files', file); }); // 追加の情報 formData.append('uploadTime', new Date().toISOString()); formData.append('fileCount', this.files.length); try { const response = await fetch('/api/upload', { method: 'POST', body: formData }); if (response.ok) { const result = await response.json(); console.log('アップロード成功:', result); this.files = []; this.createPreviews(); alert('アップロードが完了しました!'); } else { throw new Error('アップロードに失敗しました'); } } catch (error) { console.error('アップロードエラー:', error); alert('アップロードに失敗しました'); } }}
// 使用例const uploader = new FileUploader('file-uploader');
このクラスの主要な機能について説明します。
ドラッグ&ドロップでファイルを選択できます。
uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('drag-over'); this.handleFiles(e.dataTransfer.files);});
画像ファイルの場合はプレビューを表示します。
if (file.type.startsWith('image/')) { const img = document.createElement('img'); img.src = URL.createObjectURL(file);}
URL.createObjectURL()
で一時的なURLを作成してプレビューを表示します。
複雑なフォームデータの処理
ネストした構造のデータを扱う
複雑なデータ構造もFormDataで扱えます。
function createComplexFormData(userData) { const formData = new FormData(); // 基本情報 formData.append('name', userData.name); formData.append('email', userData.email); // 配列データ userData.hobbies.forEach((hobby, index) => { formData.append(`hobbies[${index}]`, hobby); }); // オブジェクトデータ Object.entries(userData.address).forEach(([key, value]) => { formData.append(`address[${key}]`, value); }); // ファイルデータ if (userData.profileImage) { formData.append('profileImage', userData.profileImage); } return formData;}
// 使用例const userData = { name: '田中太郎', email: 'tanaka@example.com', hobbies: ['プログラミング', '読書', '映画鑑賞'], address: { prefecture: '東京都', city: '渋谷区', zipCode: '150-0001' }, profileImage: fileInput.files[0] // ファイルオブジェクト};
const formData = createComplexFormData(userData);
この関数について詳しく説明します。
配列データは、インデックス付きでFormDataに追加します。
userData.hobbies.forEach((hobby, index) => { formData.append(`hobbies[${index}]`, hobby);});
オブジェクトデータは、キーを使ってFormDataに追加します。
Object.entries(userData.address).forEach(([key, value]) => { formData.append(`address[${key}]`, value);});
サーバー側でhobbies[0]
、address[prefecture]
のような形でデータを受け取れます。
動的フォームの処理を自動化しよう
フォームの内容を自動的にFormDataに変換するクラスを作ってみましょう。
class DynamicFormHandler { constructor(formId) { this.form = document.getElementById(formId); this.init(); } init() { this.form.addEventListener('submit', (e) => { e.preventDefault(); this.handleSubmit(); }); } async handleSubmit() { const formData = this.gatherFormData(); await this.submitForm(formData); } gatherFormData() { const formData = new FormData(); const formElements = this.form.elements; for (let element of formElements) { if (this.shouldIncludeElement(element)) { this.addElementToFormData(formData, element); } } return formData; } shouldIncludeElement(element) { return element.name && !element.disabled && element.type !== 'submit' && element.type !== 'button'; } addElementToFormData(formData, element) { switch (element.type) { case 'checkbox': if (element.checked) { formData.append(element.name, element.value); } break; case 'radio': if (element.checked) { formData.append(element.name, element.value); } break; case 'file': for (let file of element.files) { formData.append(element.name, file); } break; case 'select-multiple': for (let option of element.selectedOptions) { formData.append(element.name, option.value); } break; default: formData.append(element.name, element.value); } } async submitForm(formData) { try { // FormDataの内容をログ出力 console.log('送信データ:'); for (const [key, value] of formData.entries()) { console.log(`${key}:`, value); } const response = await fetch(this.form.action || '/submit', { method: 'POST', body: formData }); if (response.ok) { const result = await response.json(); this.handleSuccess(result); } else { this.handleError('サーバーエラーが発生しました'); } } catch (error) { console.error('送信エラー:', error); this.handleError('ネットワークエラーが発生しました'); } } handleSuccess(result) { alert('フォームの送信が完了しました!'); this.form.reset(); } handleError(message) { alert(`エラー: ${message}`); }}
// 使用例const formHandler = new DynamicFormHandler('contact-form');
このクラスの特徴について説明します。
フォーム要素の種類に応じて適切にFormDataに追加します。
switch (element.type) { case 'checkbox': if (element.checked) { formData.append(element.name, element.value); } break; case 'file': for (let file of element.files) { formData.append(element.name, file); } break;}
チェックボックスはチェックされている場合のみ、ファイル要素は全ての選択ファイルを追加します。
進捗表示付きアップロード
プログレスバーの実装
アップロード進捗を表示する機能を作ってみましょう。
async function uploadWithProgress(formData, progressCallback) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); // アップロード進捗の監視 xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percentComplete = (e.loaded / e.total) * 100; progressCallback(percentComplete); } }); xhr.addEventListener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText)); } else { reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`)); } }); xhr.addEventListener('error', () => { reject(new Error('ネットワークエラー')); }); xhr.open('POST', '/upload'); xhr.send(formData); });}
// 使用例async function handleFileUpload() { const fileInput = document.getElementById('fileInput'); const progressBar = document.getElementById('progressBar'); const progressText = document.getElementById('progressText'); if (fileInput.files.length === 0) { alert('ファイルを選択してください'); return; } const formData = new FormData(); for (let file of fileInput.files) { formData.append('files', file); } try { const result = await uploadWithProgress(formData, (progress) => { progressBar.style.width = `${progress}%`; progressText.textContent = `${Math.round(progress)}%`; }); console.log('アップロード完了:', result); alert('アップロードが完了しました!'); } catch (error) { console.error('アップロードエラー:', error); alert('アップロードに失敗しました'); }}
進捗表示の実装について詳しく説明します。
XMLHttpRequestのprogress
イベントでアップロード進捗を監視します。
xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percentComplete = (e.loaded / e.total) * 100; progressCallback(percentComplete); }});
e.loaded
が送信済みバイト数、e.total
が全体のバイト数です。
進捗に応じてプログレスバーを更新します。
progressBar.style.width = `${progress}%`;progressText.textContent = `${Math.round(progress)}%`;
視覚的にアップロードの進捗をユーザーに伝えることができます。
よくある注意点とベストプラクティス
Content-Typeの自動設定
FormDataを使う場合、Content-Typeヘッダーの設定には注意が必要です。
// ❌ 間違い:手動でContent-Typeを設定fetch('/upload', { method: 'POST', headers: { 'Content-Type': 'multipart/form-data' // これは設定してはいけない }, body: formData});
// ✅ 正しい:Content-Typeは自動設定されるfetch('/upload', { method: 'POST', body: formData});
重要: FormDataを使う場合、Content-Typeヘッダーは自動設定されます。 手動で設定すると、境界値(boundary)が正しく設定されずエラーになります。
ファイルサイズとセキュリティ
ファイルアップロード時は、セキュリティを考慮した検証が重要です。
function validateFileUpload(file) { const MAX_SIZE = 10 * 1024 * 1024; // 10MB const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif']; if (file.size > MAX_SIZE) { throw new Error('ファイルサイズが大きすぎます'); } if (!ALLOWED_TYPES.includes(file.type)) { throw new Error('許可されていないファイル形式です'); } // ファイル名の検証 if (!/^[a-zA-Z0-9._-]+$/.test(file.name)) { throw new Error('ファイル名に無効な文字が含まれています'); } return true;}
セキュリティ検証のポイントについて説明します。
ファイルサイズの制限を設定します。
if (file.size > MAX_SIZE) { throw new Error('ファイルサイズが大きすぎます');}
許可するファイル形式を限定します。
if (!ALLOWED_TYPES.includes(file.type)) { throw new Error('許可されていないファイル形式です');}
ファイル名に危険な文字が含まれていないかチェックします。
エラーハンドリングを充実させよう
堅牢なフォーム送信のためのエラーハンドリング例です。
async function robustFormSubmit(formData, url) { const maxRetries = 3; let attempt = 0; while (attempt < maxRetries) { try { const response = await fetch(url, { method: 'POST', body: formData }); if (response.ok) { return await response.json(); } else if (response.status >= 500) { // サーバーエラーの場合はリトライ throw new Error(`Server error: ${response.status}`); } else { // クライアントエラーの場合はリトライしない const error = await response.text(); throw new Error(`Client error: ${response.status} - ${error}`); } } catch (error) { attempt++; if (attempt >= maxRetries) { throw error; } // 指数バックオフでリトライ await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000) ); } }}
エラーハンドリングの工夫について説明します。
サーバーエラー(500番台)の場合はリトライします。
if (response.status >= 500) { throw new Error(`Server error: ${response.status}`);}
クライアントエラー(400番台)の場合はリトライしません。
指数バックオフでリトライ間隔を調整します。
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
1回目は1秒、2回目は2秒、3回目は4秒待ってからリトライします。
まとめ
JavaScriptのFormDataオブジェクトについて、基礎から実践的な応用まで詳しく解説しました。
重要なポイント:
- FormDataはmultipart/form-data形式でのデータ送信に最適
- ファイルアップロードには欠かせない機能
- Content-Typeヘッダーは自動設定される
- 適切なバリデーションとエラーハンドリングが重要
実践のコツ:
- ファイルサイズとファイル形式の検証を実装
- プログレスバーでユーザビリティを向上
- セキュリティを考慮したファイル処理
- 適切なエラーハンドリングとリトライ機能
FormDataは現代のWeb開発において、ファイルアップロードや複雑なフォーム処理に欠かせない技術です!
適切に活用することで、ユーザーフレンドリーで堅牢なWebアプリケーションを構築できるようになります。 ぜひ今日から、FormDataを使ったファイルアップロード機能に挑戦してみてください。