Capítulo 8 de 11

Tutorial VueJS 3 – Componentes e props com defineProps

verificado a 2 Setembro 2026 · 5 min

Resposta rápida

Um componente é um ficheiro .vue com o seu template, o seu estado e a sua lógica. Importa-o dentro de <script setup> para o usares diretamente no template. O pai passa-lhe dados através de props, declarados com defineProps e ligados com os dois-pontos.

No fim deste capítulo, saberás dividir uma interface em componentes e passar-lhes dados.

Há sete capítulos que tudo cabe num único ficheiro. Isso não escala. Um componente é um pedaço de interface autónomo, com o seu template, o seu estado e a sua lógica, que se reutiliza e se encaixa dentro de outros.

Extrair a ficha do produto

Cria src/components/ProductDisplay.vue e passa para lá tudo o que diz respeito ao produto: a imagem, o título, a disponibilidade, os detalhes, as pastilhas e o botão.

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>

O carrinho não vai com ele: não pertence ao produto. O App.vue reduz-se a:

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>

Um componente importado dentro de <script setup> fica logo utilizável no template: não é preciso mais nenhuma declaração.

O botão já não enche o carrinho

É normal, e é o tema do capítulo seguinte. O cart vive agora no pai, fora do alcance do componente filho. Aliás, retirámos o @click do botão: deixá-lo teria dado erro, já que addToCart deixou de existir neste componente.

Dar nome a um componente

Coexistem duas convenções. Em PascalCase, <ProductDisplay />, distingues num relance um componente de uma tag HTML, e o editor consegue saltar para a definição dele. Em kebab-case, <product-display></product-display>, mantém-se a compatibilidade com um template escrito diretamente numa página HTML.

Num ficheiro .vue, prefere o PascalCase.

Um componente tem o seu próprio âmbito

Acrescenta três fichas de uma vez:

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

Aparecem três fichas, independentes: passar o rato por uma pastilha da primeira não mexe nas outras. Cada instância executa o seu próprio <script setup> e tem o seu próprio selectedVariant.

Cada ficha guarda também a sua própria variante selecionada.

É também por isso que o estado de um componente se declara dentro de uma função. Se variants fosse um simples objeto partilhado ao nível do módulo, as três fichas pisavam-se umas às outras.

Retira as duas fichas a mais antes de continuar.

Os props

O âmbito isolado levanta uma questão: como é que o pai transmite uma informação ao filho? Através de um prop, um atributo personalizado que o componente declara aceitar.

Vamos acrescentar os portes, gratuitos para os clientes premium. A informação pertence ao pai:

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

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

E o filho declara-a:

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>

O defineProps está disponível sem import dentro de <script setup>: é uma macro do compilador, não uma função vulgar.

No template escreve-se {{ premium }} diretamente. No script, é preciso passar pelo objeto devolvido: props.premium.

Declarar os props a sério

A forma longa permite validar:

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
  }
})

O valor por omissão de um array ou de um objeto tem de ser devolvido por uma função, senão todas as instâncias partilhariam o mesmo objeto.

Dois-pontos obrigatórios

:premium="premium" transmite o valor da variável. premium="premium", sem dois-pontos, transmite a string "premium". Como uma string não vazia é verdadeira, o bug passa despercebido até ao dia em que o valor devia ser falso. Aliás, o Vue avisa na consola de um tipo esperado Boolean e recebido String.

Os props descem, não sobem

Um prop circula num só sentido: do pai para o filho. O filho não o deve modificar, o Vue mostra um aviso se tentares.

Se o filho precisar de uma versão local modificável, copia-a para um ref. Se tiver de provocar uma mudança no pai, tem de lhe enviar um sinal: é esse o tema do capítulo seguinte.

Na Options API

Os props declaram-se na opção props, com a mesma sintaxe de validação, e leem-se por this.premium. Além disso, os componentes filhos têm de ser registados na opção components.

ExercícioTira a lista dos detalhes para um componente ProductDetails.vue que recebe o array como prop. Depois mostra duas fichas de produto e confirma que cada uma tem mesmo a sua própria variante selecionada.

Erros frequentes

Esquecer os dois-pontos num prop premium="premium" transmite a string «premium», sempre verdadeira. Escreve :premium="premium". Aliás, o Vue assinala um tipo Boolean esperado e String recebido.
Modificar um prop no filho Os props só descem, o Vue avisa na consola. Copia o valor para um ref local, ou faz subir um evento.
Valor por omissão de um array ou de um objeto Tem de ser devolvido por uma função, senão todas as instâncias partilham o mesmo objeto.
Newsletter

Os novos testes, tutoriais e projetos, por e-mail.

Testes reproduzíveis, código versionado, resultados datados. Nunca spam.