Rozdział 8 z 11

Kurs Vue 3: komponenty jednoplikowe i props z defineProps

zweryfikowano 2 września 2026 · 5 min

Szybka odpowiedź

Komponent to plik .vue z własnym szablonem, stanem i logiką. Zaimportuj go w <script setup>, żeby użyć go od razu w szablonie. Rodzic przekazuje mu dane przez propsy, zadeklarowane przez defineProps i podpięte z dwukropkiem.

Na koniec tego rozdziału, będziesz umieć podzielić interfejs na komponenty i przekazywać im dane.

Od siedmiu rozdziałów wszystko mieści się w jednym pliku. To się nie skaluje. Komponent to samodzielny kawałek interfejsu, z własnym szablonem, stanem i logiką, który da się wielokrotnie użyć i zagnieżdżać.

Wydzielenie karty produktu

Utwórz src/components/ProductDisplay.vue i przenieś tam wszystko, co dotyczy produktu: obrazek, tytuł, dostępność, szczegóły, kółka kolorów, przycisk.

src/components/ProductDisplay.vue
<script setup>
import { computed, ref } from 'vue'

const product = ref('T-Shirt')
const brand = ref('Gekkode')
const selectedVariant = ref(0)
const details = ref(['60 % coton', '30 % laine', '10 % polyester'])
const variants = ref([
  { id: 2234, color: '#2563eb', image: '/images/t-shirt-bleu.svg', quantity: 20 },
  { id: 2235, color: '#dc2626', image: '/images/t-shirt-rouge.svg', quantity: 0 }
])

const title = computed(() => `${brand.value} ${product.value}`)
const image = computed(() => variants.value[selectedVariant.value].image)
const inStock = computed(() => variants.value[selectedVariant.value].quantity > 0)

function updateVariant(index) {
  selectedVariant.value = index
}
</script>

<template>
  <div class="product-display">
    <div class="product-image">
      <img :src="image" :alt="title">
    </div>
    <div class="product-info">
      <h1>{{ title }}</h1>
      <p v-if="inStock">En stock</p>
      <p v-else>En rupture</p>
      <ul>
        <li v-for="detail in details" :key="detail">{{ detail }}</li>
      </ul>
      <div class="variants-wrapper">
        <div
          v-for="(variant, index) in variants"
          :key="variant.id"
          class="color-circle"
          :style="{ backgroundColor: variant.color }"
          @mouseover="updateVariant(index)"
        ></div>
      </div>
      <button class="button" :class="{ disabledButton: !inStock }" :disabled="!inStock">
        Ajouter au panier
      </button>
    </div>
  </div>
</template>

Koszyk zostaje na miejscu: nie należy do produktu. App.vue sprowadza się do:

src/App.vue
<script setup>
import { ref } from 'vue'
import ProductDisplay from './components/ProductDisplay.vue'

const cart = ref(0)
</script>

<template>
  <div class="cart">Panier ({{ cart }})</div>
  <ProductDisplay />
</template>

Komponent zaimportowany w <script setup> jest od razu używalny w szablonie: żadna dodatkowa deklaracja nie jest potrzebna.

Przycisk już nie napełnia koszyka

Tak ma być, i o tym jest następny rozdział. cart mieszka teraz w rodzicu, poza zasięgiem komponentu potomnego. Usunęliśmy zresztą @click z przycisku: zostawienie go dałoby błąd, bo addToCart już w tym komponencie nie istnieje.

Nazywanie komponentu

Współistnieją dwie konwencje. W PascalCase, <ProductDisplay />, na pierwszy rzut oka odróżnisz komponent od znacznika HTML, a edytor potrafi przeskoczyć do jego definicji. W kebab-case, <product-display></product-display>, zachowujesz zgodność z szablonem pisanym wprost w stronie HTML.

W pliku .vue wybieraj PascalCase.

Komponent ma własny zasięg

Dodaj naraz trzy karty:

src/App.vue
<template>
  <ProductDisplay />
  <ProductDisplay />
  <ProductDisplay />
</template>

Pojawiają się trzy niezależne karty: najechanie na kółko koloru w pierwszej nie rusza pozostałych. Każda instancja wykonuje własny <script setup> i ma własny selectedVariant.

Każda karta zachowuje też własny wybrany wariant.

To także powód, dla którego stan komponentu deklaruje się wewnątrz funkcji. Gdyby variants było zwykłym obiektem współdzielonym na poziomie modułu, trzy karty wchodziłyby sobie w drogę.

Usuń dwie nadmiarowe karty, zanim przejdziesz dalej.

Propsy

Odizolowany zasięg rodzi pytanie: jak rodzic przekazuje informację potomkowi? Przez prop, czyli własny atrybut, który komponent deklaruje jako akceptowany.

Dodajmy dostawę, darmową dla klientów premium. Informacja należy do rodzica:

src/App.vue
<script setup>
const premium = ref(true)
</script>

<template>
  <div class="cart">Panier ({{ cart }})</div>
  <ProductDisplay :premium="premium" />
</template>

A potomek ją deklaruje:

src/components/ProductDisplay.vue
<script setup>
const props = defineProps({
  premium: { type: Boolean, required: true }
})

const shipping = computed(() => (props.premium ? 'Gratuite' : '2,99 €'))
</script>

<template>
  <p>Livraison : {{ shipping }}</p>
</template>

defineProps jest dostępne bez importu w <script setup>: to makro kompilatora, a nie zwykła funkcja.

W szablonie piszesz po prostu {{ premium }}. W skrypcie trzeba przejść przez zwrócony obiekt: props.premium.

Deklarowanie propsów na poważnie

Dłuższa forma pozwala walidować:

javascript
const props = defineProps({
  premium: { type: Boolean, required: true },
  titre: { type: String, default: 'Sans titre' },
  tailles: { type: Array, default: () => [] },
  note: {
    type: Number,
    validator: (valeur) => valeur >= 0 && valeur <= 5
  }
})

Wartość domyślna tablicy albo obiektu musi być zwracana przez funkcję, inaczej wszystkie instancje współdzieliłyby ten sam obiekt.

Dwukropek jest obowiązkowy

:premium="premium" przekazuje wartość zmiennej. premium="premium", bez dwukropka, przekazuje ciąg znaków "premium". Ponieważ niepusty ciąg jest prawdziwy, błąd przechodzi niezauważony aż do dnia, w którym wartość powinna być fałszywa. Vue zresztą ostrzega w konsoli, że oczekiwał typu Boolean, a dostał String.

Propsy schodzą w dół i nie wracają

Prop krąży w jedną stronę: od rodzica do potomka. Potomek nie powinien go zmieniać, jeśli spróbujesz, Vue wyświetli ostrzeżenie.

Jeśli potomek potrzebuje własnej, modyfikowalnej wersji, skopiuj ją do ref. Jeśli ma wywołać zmianę u rodzica, musi wysłać mu sygnał: o tym jest następny rozdział.

W Options API

Propsy deklaruje się w opcji props, tą samą składnią walidacji, a odczytuje przez this.premium. Komponenty potomne trzeba dodatkowo zarejestrować w opcji components.

ĆwiczenieWydziel listę szczegółów do komponentu ProductDetails.vue, który dostaje tablicę w propie. Potem wyświetl dwie karty produktu i sprawdź, że każda ma faktycznie własny wybrany wariant.

Częste błędy

Pominięty dwukropek przy propie premium="premium" przekazuje ciąg „premium”, zawsze prawdziwy. Napisz :premium="premium". Vue zresztą zgłasza oczekiwany typ Boolean, otrzymany String.
Modyfikowanie propa w potomku Propsy schodzą wyłącznie w dół, Vue ostrzega w konsoli. Skopiuj wartość do lokalnego ref albo wyślij zdarzenie w górę.
Wartość domyślna tablicy albo obiektu Musi być zwracana przez funkcję, inaczej wszystkie instancje współdzielą ten sam obiekt.
Newsletter

Nowe testy, poradniki i projekty — e-mailem.

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