【Vue3】defineModel を活用して入力フォームのコンポーネント化(部品化)をイイ感じにしたい!
2024-03-15
注: Vue3.4 以降の話
defineModelとは
defineModel
とはVue3.3で登場し、Vue3.4で安定版がリリースされた機能です。
従来、子コンポーネント側の値の変更を親に渡す場合には、props
とemit
を組み合わせる必要がありました。
しかしVue3.4からは、独自のコンポーネントに対してもv-model
を使用し、よりシンプルに書くことができるようになりました。
※ちなみに Vue3.3 以前のバージョンでも、VueUseライブラリのuseVModel
を使えば、Vue3.3以前でも同様のことは実現可能
useVModel | VueUse: https://vueuse.org/core/useVModel/
書き方
// 子側
<script setup>
const model = defineModel();
</script>
// 親側
<template>
<input v-model="model" />
</template>
https://ja.vuejs.org/guide/components/v-model#v-model-arguments
従来の記法
<script setup>
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
https://ja.vuejs.org/guide/components/v-model#v-model-arguments
defineModelでコンポーネント化したお問い合わせフォームの例
これを活用して、コンポーネント化(部品化)した入力フォームのイイ感じにできるのでは??と思ったので、
実際に試してみます。
結論としては、親からv-model
に渡すのはreactive
の要素でいけたのでこれが一番良いと思う。
(もちろんref
でもよいかと)
環境
- Vue 3.4以降
- Vuetify3系
コード
デモはこちら→ Vuetify Playground
ContactForm.vue
<template>
<v-form @submit.prevent="submit">
<CustomerInfoInput v-model="customerInfo" />
<ContactMessageInput v-model="contactMessage" />
<v-btn text="送信" type="submit"></v-btn>
</v-form>
</template>
<script setup lang="ts">
import CustomerInfoInput from "./CustomerInfoInput.vue";
import ContactMessageInput from "./ContactMessageInput.vue";
const customerInfo = reactive<CustomerInfoType>({
name: "",
email: "",
});
const contactMessage = reactive<ContactMessageType>({
type: null,
message: "",
});
const submit = () => {
// 送信処理
};
</script>
CustomerInfoInput.vue
<template>
<v-text-field v-model="customerInfo.name" label="お名前" />
<v-text-field
v-model="customerInfo.email"
type="email"
label="メールアドレス"
/>
</template>
<script setup lang="ts">
const customerInfo = defineModel<CustomerInfoType>({ required: true });
</script>
ContactMessageInput.vue
<template>
<v-radio-group v-model="contactMessage.type" inline>
<v-radio
v-for="item in contactTypeList"
:key="item.value"
:label="item.label"
:value="item.value"
></v-radio>
</v-radio-group>
<v-textarea v-model="contactMessage.message" />
</template>
<script setup lang="ts">
const contactMessage = defineModel<ContactMessageType>({ required: true });
const contactTypeList = [
{
label: "ご質問",
value: "question",
},
{
label: "ご要望",
value: "request",
},
];
</script>
(余談)複数のv-modelも渡せる
ちなみにdefineModel
は複数のv-model
を渡すこともできる。
// Parent.vue
<template>
<Child v-model:hoge="hoge" v-model:fuga="fuga" />
</template>
// Child.vue
<script setup lang="ts">
const hoge = defineModel<unknown>("hoge", { required: true });
const fuga = defineModel<unknown>("fuga", { default: "" });
</script>
スポンサーリンク