Skip to content

フォームパターン

15.1.1. v-modelバインディング

フォーム入力値はrefを使用したv-model双方向バインディングで管理します。フォームフィールドが2つ以上の場合、オブジェクトrefで状態をまとめて管理します。

vue
<script setup lang="ts">
import { ref } from 'vue'

interface SignUpForm {
  name: string
  email: string
  password: string
  agreeTerms: boolean
}

const form = ref<SignUpForm>({
  name: '',
  email: '',
  password: '',
  agreeTerms: false,
})
</script>

<template>
  <form>
    <input v-model="form.name" type="text" placeholder="名前" />
    <input v-model="form.email" type="email" placeholder="メールアドレス" />
    <input v-model="form.password" type="password" placeholder="パスワード" />
    <label>
      <input v-model="form.agreeTerms" type="checkbox" />
      利用規約に同意
    </label>
  </form>
</template>
  • フォーム状態の型はinterfaceで明示します。
  • 初期値は空文字列('')、falsenullなど型に合ったデフォルト値を割り当てます。

15.1.2. フォーム状態管理

フォームの非同期処理状態は以下の4種類に区分して管理します。

状態用途
isLoadingboolean初期データの読み込み(編集フォームなど)
isSubmittingboolean送信中かどうか
isSuccessboolean送信成功完了かどうか
errorsRecord<string, string>フィールドごとのエラーメッセージ
typescript
const isLoading = ref(false)
const isSubmitting = ref(false)
const isSuccess = ref(false)
const errors = ref<Record<string, string>>({})
  • isLoadingisSubmittingは同時にtrueにならないように管理します。
  • errorsはフィールド名をキーとしてエラーメッセージをマッピングします。

15.1.3. 制御コンポーネントパターン

Vue 3.4以上ではdefineModelを使用してカスタムInputコンポーネントを作成します。

vue
<script setup lang="ts">
// src/components/BaseInput.vue
const model = defineModel<string>({ required: true })

defineProps<{
  label: string
  error?: string
}>()
</script>

<template>
  <div class="base-input">
    <label>{{ label }}</label>
    <input v-model="model" />
    <span v-if="error" class="error">{{ error }}</span>
  </div>
</template>

親コンポーネントではv-modelで直接バインディングします。

vue
<template>
  <BaseInput v-model="form.name" label="名前" :error="errors.name" />
  <BaseInput v-model="form.email" label="メールアドレス" :error="errors.email" />
</template>
  • defineModelmodelValue Propsとupdate:modelValue Emitを自動生成します。
  • Vue 3.4未満の環境ではdefineProps + defineEmitsの組み合わせを使用します。

15.1.4. フォーム送信処理

フォーム送信は@submit.preventでデフォルト動作をブロックし、非同期関数で処理します。重複送信を必ず防止しなければなりません。

vue
<script setup lang="ts">
import { ref } from 'vue'
import { createUser } from '@/api/user'

const form = ref({ name: '', email: '' })
const isSubmitting = ref(false)
const errors = ref<Record<string, string>>({})

async function handleSubmit() {
  if (isSubmitting.value) return

  isSubmitting.value = true
  errors.value = {}

  try {
    await createUser(form.value)
    form.value = { name: '', email: '' }
  } catch (e) {
    // エラー処理は15.3節を参照
  } finally {
    isSubmitting.value = false
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="form.name" type="text" />
    <input v-model="form.email" type="email" />
    <button type="submit" :disabled="isSubmitting">
      {{ isSubmitting ? '処理中...' : '登録' }}
    </button>
  </form>
</template>
  • @submit.preventはすべてのフォームに必須で適用します。
  • 重複送信防止はisSubmittingガードとボタンのdisabled属性を併用します。
  • 送信成功後、フォーム状態を初期値にリセットします。

TIENIPIA QUALIFIED STANDARD