🗂

defineModel()がめちゃ便利らしい【インターン備忘録 - 2025年1月編】

2025/02/08に公開

2025年も始まって早1ヶ月。12分の1が終わったと考えると、なんとも早いですね。

というわけで(?)、インターンも一旦3月まで延長になったこともあり、毎月投稿していた「半年間の長期インターンを始めたので、1ヶ月ごとに学んだこととかを吐き出してみるの会」シリーズも一旦完結。これまで投稿した記事のリンクをまとめましたので、興味がある方は下記からどうぞ!
https://zenn.dev/seiwell/articles/ee64863ad2ab88

少し嗜好を変えつつ、記事にしていこうと思います。

本日のお題

「最近知ったけど、defineModel()がなんか便利らしい」

Vue3.4から、標準になったdefineModel()。これが何かと便利で、親コンポーネント→子コンポーネント子コンポーネント→親コンポーネント間のデータの受け渡しをまとめることができる(双方向バインディング)便利なヤツです。

今までのやり方とdefineModel()の違い

今までの場合、親から子の場合はpropsを使い、逆に子→親の場合はemitを使うことで、親↔︎子間のデータの受け渡しを行うことができていました。一応、両方使うことで擬似的に双方向バインディングができていましたが、できることなら統一(まとめ)たい...!

そこで、便利なのがdefineModel()らしい。Vue3.4(2023/12/29リリース)から、標準化したらしく、お恥ずかしながら最近知りました。

defineModel()の使い方

使い方はめちゃ簡単。

Parent.vue
<script setup lang="ts">
import Child from './Child.vue'
import { ref } from "vue"

const count = ref(0)
</script>

<template>
    <div>
        <Child v-model="count" />
    </div>
</template>
Child.vue
<script setup lang="ts">
const model = defineModel()

const plusButton = () => {
  model.value++
}

const minusButton = () => {
  model.value--
}
</script>

<template>
    <div>
        <span>現在のカウント:{{ model }}</span>
        <button type="butoon" @click="plusButton">+</button>
        <button type="butoon" @click="minusButton">-</button>
    </div>
</template>

シンプル!

今までの場合は、データの更新を行う関数(plusButoon(), minusButton())は親(Parent.vue)に書いて、デザインは子(Child.vue)に書き、emitで処理を親に対して発火するという方式でした。そのため、ある程度慣れればどうってことはないものの、直感的に把握できるかどうかというとそうでもない...

しかし、defineModel()を使うことで、データを更新する関数とデータの大元を自動的に同期してくれるため、かなり直感的に把握できるようになりました。

ちなみに、以前の書き方の場合だと、

Parent.vue
<template>
    <div>
        <Child 
          :count="count" 
          @update:count="(newValue) => count = newValue"
        />
    </div>
</template>
Child.vue
<script setup lang="ts">
const props = defineProps<{
    count: number
}>()

const emit = defineEmits(['update:count'])

const plusButton = () => {
  emit('update:count', props.count + 1)
}

const minusButton = () => {
  emit('update:count', props.count - 1)
}
</script>

<template>
    <div>
        <span>現在のカウント:{{ count }}</span>
        <button type="butoon" @click="plusButton">+</button>
        <button type="butoon" @click="minusButton">-</button>
    </div>
</template>

複数の双方向バインディングを行いたい場合

下記のように書くことで、複数のデータの受け渡し・更新・同期を行うことができます。

Parent.vue
<Child
  v-model:first-name="first"
  v-model:last-name="last"
/>
Child.vue
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

公式Docsから引用
https://ja.vuejs.org/guide/components/v-model#multiple-v-model-bindings

はへー、、、

defineModel()には、オプションもあるようで、

const model = defineModel({ required: true }) // 必須化
const model = defineModel({ defalut: 0 }) // デフォ値の設定

のようにすることができます。ただ注意しないといけないのは、
親コンポーネントでデフォルト値を設定せず、子コンポーネントでデフォルト値を設定した場合、親子間で値の同期ズレが起きるので注意。

最後に

他にも色々な書き方があるので、詳しくはVueの公式Docsに詳しく載っていますので、ぜひ。

リバナレテックブログ

Discussion