🔄
Vue.js初心者が学んだ11のこと(第2回)コンポーネント間通信とフォーム実装
はじめに
前回の記事では、Vue.js初心者が管理画面実装を通して学んだ基礎知識を紹介しました。
今回は、利用者管理機能の実装を通して学んだコンポーネント間通信とフォーム実装のポイントを紹介します。
実装した機能:
- 利用者登録・編集フォーム
- モーダルUI
- バリデーション
- 親子コンポーネント通信
技術スタック:
- Vue 3 (Composition API with
<script setup>) - Pinia
- Vite
1. Props/Emits - 親子コンポーネント通信の基本
親子コンポーネント間でデータをやり取りする基本パターンです。
データの流れ:
- Props: 親 → 子へデータを渡す
- Emits: 子 → 親へイベントを通知
<!-- 親コンポーネント (UsersView.vue) -->
<script setup>
import UserForm from '@/components/UserForm.vue'
const showModal = ref(false)
function handleUserSubmit(userData) {
console.log('子から受け取ったデータ:', userData)
// API送信処理など
}
</script>
<template>
<UserForm
:is-edit-mode="false"
@submit="handleUserSubmit"
@cancel="showModal = false"
/>
</template>
<!-- 子コンポーネント (UserForm.vue) -->
<script setup>
const props = defineProps({
isEditMode: Boolean,
user: Object
})
const emit = defineEmits(['submit', 'cancel'])
function handleSubmit() {
emit('submit', formData) // 親にデータを送信
}
function handleCancel() {
emit('cancel') // 親にキャンセルを通知
}
</script>
ポイント:
- Props は読み取り専用(子で変更しない)
- Emits でイベント名とデータを親に送る
2. v-model - 双方向データバインディング
入力フォームとデータを自動で同期させる便利な機能です。
<script setup>
import { reactive } from 'vue'
const formData = reactive({
name: '',
nickname: '',
roomNumber: ''
})
</script>
<template>
<input v-model="formData.name" type="text" />
<input v-model="formData.nickname" type="text" />
<input v-model="formData.roomNumber" type="text" />
<!-- デバッグ用 -->
<pre>{{ formData }}</pre>
</template>
動作:
- input に入力 →
formData.nameが自動更新 -
formData.nameを変更 → input の表示が自動更新
3. @submit.prevent - フォーム送信のデフォルト動作を防止
HTMLフォームのデフォルト動作(ページリロード)を防ぎ、JavaScriptで処理します。
<template>
<!-- .prevent を付けないと、submitでページがリロードされる -->
<form @submit.prevent="handleSubmit">
<input v-model="formData.name" required />
<button type="submit">登録</button>
</form>
</template>
<script setup>
function handleSubmit() {
// ページリロードされずにこの関数が実行される
console.log('送信データ:', formData)
}
</script>
.prevent の意味:
-
event.preventDefault()の省略形 - Vueの便利な修飾子
4. ref vs reactive - 2つのリアクティブ変数の使い分け
Vue 3では、リアクティブ変数を作る方法が2つあります。
ref() - プリミティブ値用
import { ref } from 'vue'
const count = ref(0)
const message = ref('Hello')
// JavaScript内では.valueでアクセス
count.value++
console.log(message.value)
<template>
<!-- テンプレート内では.value不要 -->
<p>{{ count }}</p>
<p>{{ message }}</p>
</template>
reactive() - オブジェクト用
import { reactive } from 'vue'
const formData = reactive({
name: '',
email: '',
age: 0
})
// .valueは不要(直接アクセス)
formData.name = '山田太郎'
formData.age++
使い分けのポイント:
-
単一の値 →
ref() -
複数のフィールドを持つオブジェクト →
reactive()
5. computed - 自動計算されるリアクティブ値
依存する値が変わると自動で再計算される値を作れます。
<script setup>
import { reactive, computed } from 'vue'
const formData = reactive({
name: '',
email: '',
password: '',
confirmPassword: ''
})
// formDataが変わると自動再計算
const isFormValid = computed(() => {
return formData.name.length > 0 &&
formData.email.includes('@') &&
formData.password.length >= 8 &&
formData.password === formData.confirmPassword
})
</script>
<template>
<button type="submit" :disabled="!isFormValid">
登録
</button>
</template>
メリット:
- 手動で更新する必要がない
- キャッシュされる(依存値が変わらない限り再計算しない)
6. :class - 動的クラスバインディング
条件に応じてCSSクラスを動的に切り替えられます。
パターン1: オブジェクト形式
<script setup>
const hasError = ref(false)
</script>
<template>
<!-- hasErrorがtrueの時だけ 'error' クラスが付く -->
<input
v-model="formData.email"
:class="{ error: hasError }"
/>
</template>
<style>
.error {
border-color: red;
background-color: #fee;
}
</style>
パターン2: 配列形式
<template>
<!-- 常に 'input' クラス、hasErrorがtrueの時は 'error' も付く -->
<input :class="['input', { error: hasError }]" />
</template>
7. :disabled - フィールドの読み取り専用化
編集不可にしたいフィールドを制御します。
<script setup>
const props = defineProps({
isEditMode: Boolean, // 編集モードかどうか
user: Object
})
</script>
<template>
<!-- 新規登録時は編集可、編集時は読み取り専用 -->
<label>ユーザーID</label>
<input
v-if="isEditMode"
:value="props.user.id"
type="text"
disabled
/>
<label>氏名</label>
<input
v-model="formData.name"
:disabled="isEditMode"
required
/>
<label>デバイスID</label>
<input
v-model="formData.deviceId"
:disabled="isEditMode"
required
/>
</template>
<style>
input:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
</style>
使い分け:
-
v-if="isEditMode": 編集時のみ表示 -
:disabled="isEditMode": 編集時は読み取り専用
8. @click.stop - イベントバブリング防止
イベントが親要素に伝播するのを防ぎます。
<template>
<!-- モーダル背景 -->
<div class="modal-overlay" @click="closeModal">
<!-- モーダル本体 -->
<!-- .stopを付けないと、モーダル内をクリックしても閉じてしまう -->
<div class="modal-content" @click.stop>
<h2>利用者登録</h2>
<UserForm @submit="handleSubmit" />
</div>
</div>
</template>
動作:
- モーダル背景クリック →
closeModal実行(モーダルが閉じる) - モーダル本体クリック → イベント伝播が停止(モーダルは閉じない)
9. v-if - 条件付きレンダリング
条件に応じて要素の表示/非表示を切り替えます。
<script setup>
const showModal = ref(false)
const editingUser = ref(null)
</script>
<template>
<!-- showModalがtrueの時だけDOMに追加される -->
<div v-if="showModal" class="modal-overlay">
<div class="modal-content">
<h2>{{ editingUser ? '編集' : '新規登録' }}</h2>
<UserForm
:is-edit-mode="!!editingUser"
:user="editingUser"
/>
</div>
</div>
</template>
v-if vs v-show:
-
v-if: DOMから完全に削除/追加(重い処理) -
v-show:display: noneで隠すだけ(軽い処理)
頻繁に切り替わるなら v-show、そうでないなら v-if を使う。
10. Template Literals - 文字列の変数埋め込み
バッククォート (`) を使って、文字列内に変数を埋め込めます。
// 古い書き方
const url = API_BASE_URL + '/api/users/' + userId
// 新しい書き方(ES6+)
const url = `${API_BASE_URL}/api/users/${userId}`
// 複雑な例
const message = `${user.nickname}さん、朝のお薬の時間です。`
メリット:
- 読みやすい
-
+による文字列連結より直感的
11. Object Shorthand - オブジェクトプロパティの省略記法
プロパティ名と変数名が同じ場合、省略できます。
// 通常の書き方
const config = {
signal: signal,
timeout: timeout
}
// 省略形
const config = {
signal,
timeout
}
// 実際の使用例
const response = await apiClient.get(`${API_BASE_URL}/api/users`, { signal })
// ↑ これは { signal: signal } と同じ
まとめ
Vue.js初心者が利用者管理画面実装を通して学んだ11のポイントを紹介しました。
特に重要だったポイント:
- Props/Emits - 親子コンポーネント通信の基本
- v-model - フォーム実装の必須機能
- ref vs reactive - 適切な使い分け
- computed - 自動計算による効率化
次回は、状態管理とリアルタイム更新について紹介します。
Discussion