フォームの入力値とデータを同期させるのが、v-modelです。入力するとデータが更新され、データを書き換えると入力欄の表示も変わります。この記事では、Vue 3(<script setup>)を前提に、フォーム部品ごとの使い方と、日本語入力で戸惑いやすい点を動くコードで整理します。
テキスト入力のバインディング
入力欄にv-modelを付けるだけで、入力した文字が変数に反映されます。<textarea>では{{ }}が使えないため、必ずv-modelを使います。
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
<template>
<input v-model="message">
<p>{{ message }}</p>
<!-- textarea では {{ }} が使えない。v-model を使う -->
<textarea v-model="message"></textarea>
</template>
日本語入力(IME)では変換中に更新されない
日本語のようにIMEで変換する言語では、v-modelは変換中には更新されません。確定したときに初めてデータに反映されます。入力中の文字をそのままリアルタイムに表示したい場合は、v-modelの代わりに:valueと@inputを自分で書きます。
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>
<template>
<!-- v-model の代わりに :value と @input を自分で書く -->
<input :value="text" @input="text = $event.target.value">
<p>{{ text }}</p>
</template>
チェックボックスのバインディング
チェックボックスは、使い方で受け取る値が変わります。1つだけなら真偽値、同じ変数に複数を結びつけると配列になります。true-valueとfalse-valueを指定すれば、真偽値の代わりに任意の値も使えます。
<script setup>
import { ref } from 'vue'
const agreed = ref(false) // 1つのチェックボックス → 真偽値
const names = ref([]) // 複数のチェックボックス → 配列
const toggle = ref('no') // true-value / false-value
</script>
<template>
<label><input type="checkbox" v-model="agreed"> 同意する</label>
<p>{{ agreed }}</p>
<label><input type="checkbox" value="Jack" v-model="names"> Jack</label>
<label><input type="checkbox" value="John" v-model="names"> John</label>
<p>{{ names }}</p>
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no">
<p>{{ toggle }}</p>
</template>
ラジオボタンのバインディング
同じ変数に結びつけたラジオボタンのうち、選択されたもののvalueが変数に入ります。
<script setup>
import { ref } from 'vue'
const picked = ref('')
</script>
<template>
<label><input type="radio" value="One" v-model="picked"> One</label>
<label><input type="radio" value="Two" v-model="picked"> Two</label>
<p>{{ picked }}</p>
</template>
セレクトボックスのバインディング
単一選択は文字列、multipleを付けると配列になります。
<script setup>
import { ref } from 'vue'
const selected = ref('') // 単一選択 → 文字列
const multi = ref([]) // multiple → 配列
</script>
<template>
<select v-model="selected">
<option disabled value="">選択してください</option>
<option>Option 1</option>
<option>Option 2</option>
</select>
<p>{{ selected }}</p>
<select v-model="multi" multiple>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
<p>{{ multi }}</p>
</template>
単一選択の先頭に、disabledで値が空のオプションを置いています。v-modelの初期値がどの選択肢にも一致しないと、セレクトボックスは未選択の状態で表示されます。この状態のiOSでは、先頭の項目を選べなくなります(changeイベントが発火しないため)。公式も、このオプションを置くことを推奨しています。
修飾子で入力値を整える
v-modelには3つの修飾子があります。
<!-- change イベントで同期(フォーカスを外したときなど) --> <input v-model.lazy="msg"> <!-- 数値に変換(parseFloat できなければ元の文字列のまま) --> <input v-model.number="age"> <!-- 前後の空白を除去 --> <input v-model.trim="msg">
初期値はJavaScript側で宣言する
v-modelは、フォーム要素に書いたvalue、checked、selectedの初期属性を無視します。常にJavaScript側の状態が正で、初期値はそちらで宣言する必要があります。
<!-- NG:HTML側の checked は無視される。常にJavaScript側の状態が正 -->
<input type="checkbox" v-model="agreed" checked>
<!-- OK:初期値はJavaScript側で宣言する -->
<script setup>
import { ref } from 'vue'
const agreed = ref(true)
</script>
カスタムコンポーネントでv-modelを使う
自作の入力コンポーネントにもv-modelが使えます。Vue 3では、子コンポーネントが受け取るpropはmodelValue、通知するイベントはupdate:modelValueです。Vue 2のvalueとinputから名前が変わっています。Vue 2のコードをそのまま使うと、値が親に伝わりません。
Vue 3.4以降は、defineModel()を使うと、propとイベントの定義を省略できます。
<!-- CustomInput.vue(Vue 3.4 以降) --> <script setup> const model = defineModel() </script> <template> <input v-model="model"> </template>
<!-- CustomInput.vue(3.4より前の書き方) -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
</template>
<!-- 親コンポーネント -->
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const message = ref('')
</script>
<template>
<CustomInput v-model="message" />
<p>{{ message }}</p>
</template>
v-modelの内部の仕組みやカスタム実装はv-modelの仕組みを完全理解で詳しく解説しています。
関連記事
- 属性へのバインディングは属性バインディングの使い方
- イベントの扱いはv-onでDOMイベントを購読する方法
- 入力のチェックは入力フォームのバリデーション機能を実装する方法
- 項目を増減するフォームは動的フォームの作り方とバリデーション設計
よくある質問(FAQ)
v-modelが更新されない仕様のためです。確定すると反映されます。変換中も反映したいときは、:valueと@inputで自分で書いてください。.numberはparseFloat()で変換します。変換できない値(空欄や文字だけの入力)は、元の文字列のままです。v-modelは初期のchecked属性を無視するためです。JavaScript側で、ref(true)のように初期値を宣言してください。modelValueを受け取り、update:modelValueを発行する必要があります。3.4以降ならdefineModel()が簡単です。まとめ
v-modelは、フォーム部品に合わせて、文字列、真偽値、配列を自動で使い分けてくれます。日本語サイトでは、IMEの変換中に更新されないことを押さえておくと、リアルタイム表示でつまずきません。初期値はJavaScript側で宣言し、コンポーネントではmodelValueとupdate:modelValueを使ってください。

