v-model on Your Own Components
Shelf's Add Book form needs a star rating. You build a StarRating component with five clickable stars, and you want to use it exactly like an input: <StarRating v-model="rating" />. For that, the component must follow the v-model contract. Since Vue 3.4, defineModel does the wiring in one line.
The rule
On a component, v-model is a
modelValueprop plus anupdate:modelValueevent;defineModelhandles both.
The child:
<!-- StarRating.vue -->
<script setup>
const rating = defineModel({ type: Number, default: 0 })
</script>
<template>
<div class="stars">
<button v-for="n in 5" :key="n" type="button"
:class="{ filled: n <= rating }"
@click="rating = n">★</button>
</div>
</template>The parent:
<script setup>
import { ref } from 'vue'
import StarRating from './StarRating.vue'
const rating = ref(3)
</script>
<template>
<StarRating v-model="rating" />
<p>You gave it {{ rating }} stars.</p>
</template>What happens underneath: on a component, v-model="rating" expands to
<StarRating :modelValue="rating" @update:modelValue="v => rating = v" />defineModel() declares that prop and that event for you and returns a ref. Reading it gives the parent's current value. Assigning to it, as rating = n in the template or rating.value = n in script, emits update:modelValue with the new value, and the parent's ref updates. The child never mutates a prop; it asks, and the parent's v-model listener makes the change. This is still "props down, events up", just packaged.
Several models. Give the model a name to bind more than one value: defineModel('title') in the child pairs with v-model:title="book.title" in the parent. A component can have v-model:title and v-model:rating side by side.
Before Vue 3.4, you wrote the contract by hand: defineProps(['modelValue']) plus defineEmits(['update:modelValue']), and emit('update:modelValue', n) on click. You will still see this in older code, and it behaves the same.
The shortcut
Picture the expansion every time: v-model="x" on a component means "pass x as modelValue, and when the child says update:modelValue, write the value into x". With a name, swap modelValue for that name.
Parent passes down
ratingis 3, so StarRating receivesmodelValue3. Three stars are filled.
Drills
Answer fast — trust the rule.
On a component, v-model="rating" expands to which prop and event?
Inside StarRating, const rating = defineModel(). What does rating.value = 5 do?
The parent writes <BookForm v-model:title="book.title" />. What does the child use?
Which pre-3.4 code is equivalent to const rating = defineModel()?
Remember
- Component v-model =
modelValueprop +update:modelValueevent;defineModel()gives you a ref that does both. - Shortcut: picture the expansion; a named model swaps
modelValuefor the name. - Trap: mutating a plain prop instead of using a model; the parent must make the change.
