Rozdział 10 z 11

Kurs Vue 3: formularze i dwustronne wiązanie v-model

zweryfikowano 2 września 2026 · 5 min

Szybka odpowiedź

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.

Na koniec tego rozdziału, będziesz umieć zbudować kompletny formularz, zwalidować go i przekazać jego wynik rodzicowi.

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:

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:

src/components/ReviewForm.vue
<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:

html
<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:

  • .number zamienia wpisaną wartość na liczbę. Bez niego <select> i <input type="number"> zwracają ciąg znaków: rating miałoby wartość "5", a test === 5 by nie przeszedł.
  • .trim usuwa spacje z początku i końca wpisanej wartości.
  • .lazy synchronizuje przy zdarzeniu change, a nie przy każdym naciśnięciu klawisza.
html
<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:

src/components/ReviewForm.vue
<form class="review-form" @submit.prevent="onSubmit">

Funkcja sprawdza dane, emituje opinię do rodzica, a potem czyści pola:

src/components/ReviewForm.vue
<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:

src/components/ReviewForm.vue
<p v-if="error" class="review-error" role="alert">{{ error }}</p>
Dlaczego nie alert()?

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:

src/components/ReviewList.vue
<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.

src/components/ProductDisplay.vue
<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:

src/components/ChampTexte.vue
<script setup>
const valeur = defineModel()
</script>

<template>
  <input :value="valeur" @input="valeur = $event.target.value">
</template>

Rodzic używa go wtedy jak natywnego pola:

html
<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.

W Options API

v-model i jego modyfikatory zapisuje się tak samo. Pola deklaruje się w data(), walidację w methods, a emitowanie przez this.$emit.

ĆwiczenieDodaj pole wyboru „Polecam ten produkt” powiązane przez v-model z wartością logiczną, przekaż je w emitowanej opinii i wyświetl na liście.

Częste błędy

Zapomniany .number przy ocenie <select> i <input type="number"> zwracają ciąg znaków. Bez .number test === 5 nie przechodzi, a obliczenie skleja wartości zamiast je dodać.
Zapomniany .prevent na formularzu Strona przeładowuje się przy wysyłce i stan przepada. Napisz @submit.prevent="onSubmit".
Walidacja przez alert() Okno blokuje stronę i nie jest poprawnie ogłaszane przez czytniki ekranu. Wyświetl komunikat z role="alert".
Newsletter

Nowe testy, poradniki i projekty, e-mailem.

Powtarzalne testy, wersjonowany kod, datowane wyniki. Nigdy spamu.