こんばんは。
今日はVue 3(Composition API)+ Vuelidate において、配列の各要素に対してバリデーションを行う場合の実装例をご紹介します。
今回のポイントは以下の通りです。
- 配列を持つ親コンポーネントと、
- 配列の要素(オブジェクト)に対するフォームを扱う子コンポーネント
を分けることで、要素ごとにバリデーションを実行しやすくする構成です。
親コンポーネント
親コンポーネントの概要
- 親コンポーネントで「配列のデータ」と「Vuelidate のバリデーションルール(rules)」を定義し、
useVuelidate
を呼び出します。 - 各要素を描画するときに、
:vuelidate="$v.items[index]"
という形で要素ごとの Vuelidate 状態を子コンポーネントへ渡します。
ParentForm.vue
<template>
<div>
<h1>親コンポーネント</h1>
<!-- 配列データ(items)をループして、子コンポーネントに渡す -->
<div v-for="(item, index) in items" :key="index" style="margin-bottom: 16px;">
<ItemForm
:item="item"
:vuelidate="$v.items[index]"
/>
</div>
<!-- バリデーション全体のチェックを行う例 -->
<button @click="onSubmit">送信</button>
<div v-if="submitErrorMessage" style="color: red;">
{{ submitErrorMessage }}
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'
import ItemForm from './ItemForm.vue'
export default {
name: 'ParentForm',
components: {
ItemForm,
},
setup() {
// バリデーション対象となるデータ
const items = ref([
{ name: '', age: '' },
{ name: '', age: '' },
])
// Vuelidate で使用するルール定義
// 配列の各要素に対して $each でルールを定義できる
const rules = computed(() => ({
items: {
$each: {
name: { required },
age: { required },
},
},
}))
// useVuelidate の呼び出し(第二引数は実際のデータ)
const v$ = useVuelidate(rules, { items })
const submitErrorMessage = ref('')
// 送信時のバリデーション例
const onSubmit = async () => {
// 全体のバリデーションチェック
const isValid = await v$.value.$validate()
if (!isValid) {
submitErrorMessage.value = '入力に不備があります。'
} else {
submitErrorMessage.value = ''
// 送信処理 ...
alert('送信成功!')
}
}
return {
items,
v$,
onSubmit,
submitErrorMessage,
}
},
}
</script>
上記の例では、items
配列に対して、$each
キーを使い、
name
とage
それぞれにrequired
バリデーションを付与しています。
-
v$.value.items[index]
には、該当インデックスの要素のVuelidate
状態が格納されます。
子コンポーネント
子コンポーネントの概要
- 親から受け取った「配列の要素(
item
)」と「要素ごとの Vuelidate インスタンス(vuelidate
)」 - 入力フォームとバリデーションエラー表示
- 自分自身の内部では
useVuelidate
を呼ばず、親コンポーネントから既に生成されたインスタンスを受け取る形にする
ItemForm.vue
<template>
<div>
<h2>子コンポーネント (ItemForm)</h2>
<div>
<label>名前:</label>
<input v-model="item.name" />
<!-- name フィールドのバリデーションエラー表示例 -->
<span v-if="vuelidate?.name?.$error" style="color: red;">
※名前は必須です
</span>
</div>
<div>
<label>年齢:</label>
<input type="number" v-model="item.age" />
<!-- age フィールドのバリデーションエラー表示例 -->
<span v-if="vuelidate?.age?.$error" style="color: red;">
※年齢は必須です
</span>
</div>
</div>
</template>
<script>
export default {
name: 'ItemForm',
props: {
// 親から受け取る、配列の要素
item: {
type: Object,
required: true,
},
// 親から受け取る、要素ごとの Vuelidate インスタンス
vuelidate: {
type: Object,
required: true,
},
},
}
</script>
-
v-model="item.name"
のように、直接親のデータを変更しています。 - バリデーションエラーの表示に
vuelidate?.name?.$error
を使用しています。-
?.
はオプショナルチェーン演算子で、vuelidate
やvuelidate.name
が未定義の場合でもエラーにならないようにするためのものです。
-
配列要素の追加・削除を行う場合
もし配列要素の追加や削除を行う場合は、以下のように親コンポーネントでitems
を変更してあげれば動的にバリデーションが適用されます。
<template>
<div>
<button @click="addItem">要素を追加</button>
<div v-for="(item, index) in items" :key="index">
<!-- 要素を削除するボタン例 -->
<button @click="removeItem(index)">削除</button>
<ItemForm :item="item" :vuelidate="$v.items[index]" />
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'
import ItemForm from './ItemForm.vue'
export default {
components: { ItemForm },
setup() {
const items = ref([
{ name: '', age: '' },
])
const rules = computed(() => ({
items: {
$each: {
name: { required },
age: { required },
},
},
}))
const v$ = useVuelidate(rules, { items })
const addItem = () => {
items.value.push({ name: '', age: '' })
}
const removeItem = (index) => {
items.value.splice(index, 1)
}
return {
items,
v$,
addItem,
removeItem,
}
},
}
</script>
このようにすると、要素を追加するたびに配列に新しいオブジェクトが追加され、そのオブジェクトにもバリデーションが動的に適用されるようになります。
まとめ
- 親コンポーネントで配列データ (
items
) とVuelidate
のルール定義・インスタンス生成を行う。 - 子コンポーネントでは親から渡された単一要素(item)と対応する
Vuelidate
インスタンス(vuelidate
)を使ってフォーム入力とバリデーションエラー表示を行う。 - 追加・削除など動的に要素を操作しても、
Vuelidate
が$each
を通じて自動的に管理してくれる。
以上の手順で、配列に対して要素ごとのバリデーションを行う構成が実装可能です。
ぜひ参考にしてみてください。
今日は以上です。
ありがとうございました。
よろしくお願いいたします。