v-modelは、フォームとデータを双方向に結びつける、基本のディレクティブです。便利なぶん、中で何が起きているかを知らないと、日本語入力で更新されない、自作コンポーネントで値が親に伝わらない、といった場面で困ります。この記事では、Vue 3を前提に、v-modelの仕組みと、コンポーネントへの実装方法を整理します。フォーム部品ごとの使い方は双方向データバインディングでフォームの入力値を反映させる方法で解説しています。
ネイティブ要素でのv-model
v-modelは、手書きの:valueと@inputを、短く書くための構文です。公式でも、この2つの手書きの書き方と並べて説明されています。
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>
<template>
<!-- v-model を使った書き方 -->
<input v-model="text">
<!-- ほぼ同じ意味の、手書きの書き方 -->
<input :value="text" @input="event => text = event.target.value">
</template>
日本語入力(IME)では、完全には同じではない
ただし、両者は完全に同じ動きではありません。公式は、IMEを使う言語(日本語、中国語、韓国語など)では、v-modelは変換中に更新されないと説明しています。変換中も反応したい場合は、v-modelを使わず、:valueと@inputで自前で書く、というのが公式の案内です。つまり「省略形」は、普段の理解としては正しくても、変換中の挙動は別物と考えてください。
要素ごとのプロパティとイベント
v-modelは、使う要素によって、結びつけるDOMのプロパティとイベントの組み合わせを切り替えます。
- テキスト系の
<input>と<textarea>:valueプロパティとinputイベント - チェックボックスとラジオボタン:
checkedプロパティとchangeイベント <select>:valueプロパティとchangeイベント
修飾子.lazy、.number、.trimの使い方は、3000の記事をご覧ください。
コンポーネントでのv-model
自作のコンポーネントにv-modelを使うと、Vueは次のように展開します。子コンポーネントは、modelValueというpropを受け取り、値を変えたいときにupdate:modelValueイベントを発行する、という約束になります。
<!-- この書き方は --> <CustomInput v-model="searchText" /> <!-- 次のように展開される --> <CustomInput :model-value="searchText" @update:model-value="newValue => searchText = newValue" />
defineModel()で実装する(Vue 3.4以降)
Vue 3.4以降は、defineModel()を使うのが推奨です。propsとイベントの定義を自分で書かずに済みます。返ってくるrefは、親のv-modelの値と同期しており、子で変更すると親の値も更新されます。ネイティブの<input>には、そのままv-modelで渡せます。
<!-- CustomInput.vue(Vue 3.4 以降) --> <script setup> const model = defineModel() </script> <template> <!-- 返ってきた ref を、ネイティブ入力の v-model にそのまま渡せる --> <input v-model="model"> </template>
<!-- 親コンポーネント -->
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const myText = ref('')
</script>
<template>
<CustomInput v-model="myText" />
<p>{{ myText }}</p>
</template>
3.4より前の書き方:propsとemits
3.4より前のバージョンや、既存コードでは、propsとイベントを自分で書きます。update:modelValueはemitsに宣言してください。
<!-- CustomInput.vue(3.4 より前の書き方) -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
</template>
// Options API の場合
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
必須にする・既定値を持たせる
// 必須にする
const model = defineModel({ required: true })
// 既定値を持たせる
const model = defineModel({ default: 0 })
注意点として、defaultを指定して、親が値を渡さない場合は、親子の値がずれることがあります。親が必ず値を持つ設計なら、requiredを使うのが安全です。
v-modelの引数で複数の値をバインドする
v-model:first-nameのように、v-modelに引数を付けると、1つのコンポーネントに複数のv-modelを使えます。これを「エイリアス」と呼ぶ記事もありますが、公式の名称は「v-modelの引数」です。子側はdefineModel('firstName')のように、引数の名前を渡します。
<!-- 親:v-model に引数を付けて、複数をバインド --> <UserName v-model:first-name="first" v-model:last-name="last" />
<!-- UserName.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>
カスタム修飾子を作る
自作のコンポーネントには、自分で決めた修飾子も付けられます。defineModel()の第2戻り値modifiersに、親が付けた修飾子が入ります。値が更新されるときに加工するsetと組み合わせます。
<!-- 親:カスタム修飾子 .capitalize を付ける --> <MyInput v-model.capitalize="myText" />
<!-- MyInput.vue(3.4 以降) -->
<script setup>
// 第2戻り値の modifiers に、親が付けた修飾子が入る
const [model, modifiers] = defineModel({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<template>
<input type="text" v-model="model">
</template>
3.4より前は、modelModifiersというpropで修飾子を受け取り、イベントを発行する前に値を加工します。
Vue 2からの変更点
Vue 2のコードを流用すると、値が親に伝わらないことがあります。主な変更は次のとおりです。
- コンポーネントの
v-modelが使うprop名とイベント名が、valueとinputから、modelValueとupdate:modelValueに変わった v-bindの.sync修飾子と、コンポーネントのmodelオプションは廃止され、v-modelの引数に置き換えられた- 1つのコンポーネントに、複数のv-modelを使えるようになった
関連記事
- 属性へのバインディングは属性バインディングの使い方
- イベントの扱いはv-onでDOMイベントを購読する方法
- 項目の増減があるフォームは動的フォームの作り方とバリデーション設計
- 入力のチェックは入力フォームのバリデーション機能を実装する方法
- ダイアログの部品化はダイアログやモーダルをコンポーネント化して再利用する方法
よくある質問(FAQ)
:valueと@inputで自前で書いてください。valueとinputのままになっている可能性があります。Vue 3では、modelValueを受け取り、update:modelValueを発行する必要があります。3.4以降ならdefineModel()が簡単です。v-model:first-nameのように引数を付けて、子でdefineModel('firstName')のように、同じ名前を指定します。defaultがあると、親子の値がずれる場合があります。親が必ず値を持つ設計なら、required: trueを使うほうが安全です。まとめ
v-modelは、ネイティブ要素ではvalueやcheckedとイベントの組み合わせ、コンポーネントではmodelValueとupdate:modelValueのペアで成り立っています。日本語では、変換中に更新されないという違いに気をつけ、自作のコンポーネントではdefineModel()を使うと、短く、間違いなく書けます。Vue 2のコードは、prop名とイベント名の変更に注意して移してください。

