Kurs Vue 3: formularze i dwustronne wiązanie v-model
zweryfikowano 2 września 2026 · 5 min
v-model tworzy dwustronne wiązanie między polem formularza a daną. Modyfikator .number zamienia wpisaną wartość na liczbę, .trim obcina spacje, a @submit.prevent blokuje przeładowanie strony przy wysyłce.
v-bind wiąże dane z szablonem, ale tylko w jedną stronę. Formularz potrzebuje też drugiej: to, co użytkownik wpisuje, musi wrócić do stanu. Od tego jest v-model.
CSS formularza
Dodaj te reguły do src/assets/boutique.css:
.review-form { display: flex; flex-direction: column; width: 425px; padding: 20px; border: 2px solid #d8d8d8; background: #fff; }
.review-container { width: 425px; padding: 20px; border: 2px solid #d8d8d8; background: #fff; }
.review-error { color: #b91c1c; }Komponent formularza
Utwórz src/components/ReviewForm.vue. Każde pole jest powiązane z referencją przez v-model:
<script setup>
import { ref } from 'vue'
const name = ref('')
const review = ref('')
const rating = ref(null)
</script>
<template>
<form class="review-form">
<h3>Laisser un avis</h3>
<label for="name">Nom</label>
<input id="name" v-model="name">
<label for="review">Avis</label>
<textarea id="review" v-model="review"></textarea>
<label for="rating">Note</label>
<select id="rating" v-model.number="rating">
<option disabled :value="null">Choisir une note</option>
<option>5</option>
<option>4</option>
<option>3</option>
<option>2</option>
<option>1</option>
</select>
<button class="button" type="submit">Envoyer</button>
</form>
</template>Wpisz coś w polu „Nom” i obserwuj name w narzędziach deweloperskich Vue: wartość podąża za każdym znakiem.
Co robi v-model
v-model to nie magia, tylko skrót. Na <input> te dwie linie są równoważne:
<input v-model="name">
<input :value="name" @input="name = $event.target.value">Wiązanie schodzi w dół, nasłuch wraca w górę. Vue dobiera atrybut i zdarzenie do elementu: value i input dla pola tekstowego, checked i change dla pola wyboru, value i change dla listy rozwijanej.
Modyfikatory
Trzy przyrostki oszczędzają powtarzalnego kodu:
.numberzamienia wpisaną wartość na liczbę. Bez niego<select>i<input type="number">zwracają ciąg znaków:ratingmiałoby wartość"5", a test=== 5by nie przeszedł..trimusuwa spacje z początku i końca wpisanej wartości..lazysynchronizuje przy zdarzeniuchange, a nie przy każdym naciśnięciu klawisza.
<input id="name" v-model.trim="name">
<select id="rating" v-model.number="rating">Wysyłka formularza
Formularz HTML przy wysyłce przeładowuje stronę. Zapobiega temu modyfikator .prevent, poznany w rozdziale 5:
<form class="review-form" @submit.prevent="onSubmit">Funkcja sprawdza dane, emituje opinię do rodzica, a potem czyści pola:
<script setup>
import { ref } from 'vue'
const emit = defineEmits(['review-submitted'])
const name = ref('')
const review = ref('')
const rating = ref(null)
const error = ref('')
function onSubmit() {
if (name.value === '' || review.value === '' || rating.value === null) {
error.value = 'Avis incomplet. Merci de remplir tous les champs.'
return
}
emit('review-submitted', {
name: name.value,
review: review.value,
rating: rating.value
})
name.value = ''
review.value = ''
rating.value = null
error.value = ''
}
</script>Komunikat błędu wyświetl w szablonie, tuż przed przyciskiem:
<p v-if="error" class="review-error" role="alert">{{ error }}</p>Okno alert() blokuje stronę, nie jest odczytywane przez czytnik ekranu we właściwym momencie i nie da się go przetestować. Akapit z role="alert" technologie wspomagające ogłaszają, gdy tylko się pojawi, a on sam zostaje widoczny przez cały czas poprawiania.
Wyświetlanie opinii
Utwórz src/components/ReviewList.vue, który dostaje listę w propie:
<script setup>
defineProps({
reviews: { type: Array, required: true }
})
</script>
<template>
<div class="review-container">
<h3>Avis</h3>
<ul>
<li v-for="(review, index) in reviews" :key="index">
{{ review.name }} a mis {{ review.rating }} étoiles
<br>
« {{ review.review }} »
</li>
</ul>
</div>
</template>Złożenie całości
ProductDisplay.vue staje się rodzicem obu nowych komponentów: przechowuje opinie i nasłuchuje formularza, korzystając z emitowania zdarzeń z poprzedniego rozdziału.
<script setup>
import ReviewForm from './ReviewForm.vue'
import ReviewList from './ReviewList.vue'
const reviews = ref([])
function addReview(review) {
reviews.value.push(review)
}
</script>
<template>
<div class="product-display">
<div class="product-container">
<!-- la fiche produit, inchangée -->
</div>
<ReviewList v-if="reviews.length" :reviews="reviews" />
<ReviewForm @review-submitted="addReview" />
</div>
</template>v-if="reviews.length" zapobiega wyświetlaniu pustej ramki, dopóki nikt nie dodał opinii.
Wyślij pusty formularz: pojawi się komunikat błędu. Wypełnij go: opinia trafi na listę, a pola się wyczyszczą.
Opinie znikają po przeładowaniu strony, bo żyją tylko w pamięci. Żeby je zachować, potrzebna jest baza danych: zobacz Firebase z Vue.
v-model na własnym komponencie
v-model działa też na twoich własnych komponentach. Od Vue 3.4 makro defineModel załatwia to od ręki:
<script setup>
const valeur = defineModel()
</script>
<template>
<input :value="valeur" @input="valeur = $event.target.value">
</template>Rodzic używa go wtedy jak natywnego pola:
<ChampTexte v-model="name" />Aplikacja jest gotowa
Wyświetla produkt i jego warianty, reaguje na najechanie kursorem, pilnuje dostępności, napełnia koszyk i przyjmuje zwalidowane opinie. Następny rozdział dokłada ostatni szlif: animacje wejścia i wyjścia.
v-model i jego modyfikatory zapisuje się tak samo. Pola deklaruje się w data(), walidację w methods, a emitowanie przez this.$emit.
v-model z wartością logiczną, przekaż je w emitowanej opinii i wyświetl na liście.Częste błędy
<select> i <input type="number"> zwracają ciąg znaków. Bez .number test === 5 nie przechodzi, a obliczenie skleja wartości zamiast je dodać.@submit.prevent="onSubmit".role="alert".