Vueの2種類の双方向データバインディングをおさらいし、defineModel()を完全に理解する。
双方向データバインディングやら、Vue3.4から追加されたdefineModel()で混乱してしまったので、ここに整理します。
データバインディングとは
Wikipediaの説明が一番わかりやすいです。
データバインディング(データバインド、データ結合)は、データと対象を結びつけ、データあるいは対象の変更を暗示的にもう一方の変更へ反映すること、それを実現する仕組みのことである。
引用元:ウィキペディア(Wikipedia)
双方向データバインディングとは
Vueでの双方向データバインディングとは2種類存在します。
1.親子コンポーネント間での双方向データバインディング
親コンポーネントと子コンポーネント間でのデータの変更を自動同期することです。
- 親コンポーネントでデータを変更すれば、その変更が子コンポーネントに自動的に反映されます。
- 子コンポーネントでデータを変更すれば、その変更が親コンポーネントに自動的に反映されます。
2.コンポーネント内での双方向データバインディング
コンポーネント内のデータとViewの間でのデータの変更を同期することです。
- データが変更されれば、その変更がViewに自動的に反映されます。
- View側でデータを変更すれば、その変更がデータに自動的に反映されます。
それでは次に、この2種類の双方向データバインディングについて、どうやって実装するのかご説明します。
双方向データバインディングの実装
1.親子コンポーネント間での双方向データバインディング
Vue3.4以前は、親コンポーネントと子コンポーネントの双方向データバインディングを実現するために、
- v-model
- defineProps
- defineEmits
を使って以下のような実装をしていました。
(defineModel()を使用した双方向データバインディングについては後で説明します。)
<script setup>
import { ref } from 'vue'
import child from './components/child.vue'
const text = ref('こんにちは')
</script>
<template>
<child v-model="text" />
</template>
この時、コンポーネントに渡しているv-modelは、
<child :modelValue="text" @update:modelValue="val => text = val" />
のシンタックスシュガーであることを思い出してください。
<script setup>
defineProps({ modelValue: String });
defineEmits(['update:modelValue']);
</script>
<template>
<p>{{ modelValue }}</p>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
子コンポーネントが直接親コンポーネントのデータ(props)を変更することは推奨されていません。
そのため、
- defineProps:propsを定義し、親コンポーネントからデータを受け取り、
- defineEmitで、propsを更新する手段を定義
しています。
2.単一コンポーネント(SFC)内での双方向データバインディング
次に、コンポーネント内での双方向データバインディング以下のように実装できます。
<script setup>
import { ref } from 'vue';
const message = ref(''); // リアクティブなデータを定義
</script>
<template>
<input v-model="message" /> <!-- データとフォーム要素を双方向にバインド -->
</template>
View側で、inputに何かを入力すると、リアクティブなデータが変更されます。一方で、リアクティブなデータが変更されれば、その変更がinput要素に反映されます。
この時、inputに渡しているv-modelは、
<input :value="message" @input="message = $event.target.value" />
のシンタックスシュガーであることを思い出してください。
defineModel()とは
Vue3.4以降、親子コンポーネント間での双方向データバインディングは、defineModel()を用いることで次のように書けるようになりました。
<script setup>
import { ref } from 'vue'
import child from './components/child.vue'
const text = ref('こんにちは')
</script>
<template>
<child v-model="text" />
</template>
<script setup>
const text = defineModel()
</script>
<template>
<p>{{ text }}</p>
<input v-model="text" />
</template>
defineModel()は、リアクティブな値であるrefを返します。
このrefは、通常のrefと同じように .value
でアクセスしたり、代入することができます。
しかし、このrefは通常のrefとは次の点で異なります。
defineModel()が返すrefのvalueは、親コンポーネント上でv-modelとして渡しているデータと自動的に同期されます。
つまり、子コンポーネントでvalueを変更すると、親のデータも自動的に更新されるようになるため、1.親子コンポーネント間の双方向バインディングを実現します。
さらに、子コンポーネント内で、v-modelにrefを渡すこともできるため、前述したように2.単一コンポーネント(SFC)内での双方向データバインディングも実現できます。
Discussion