【Vue3】defineModel を活用して入力フォームのコンポーネント化(部品化)をイイ感じにしたい!

2024-03-15

注: Vue3.4 以降の話

defineModelとは

defineModelとはVue3.3で登場し、Vue3.4で安定版がリリースされた機能です。

従来、子コンポーネント側の値の変更を親に渡す場合には、propsemitを組み合わせる必要がありました。

しかし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>
スポンサーリンク