Skip to content

Fundamentos dos Componentes ​

Os componentes permitem-nos separar os elementos da interface em pedaços independentes e reutilizáveis, e pensar sobre cada pedaço em isolamento. É comum para uma aplicação ser organizada numa árvore de componentes encaixados:

Árvore de Componente

Isto é muito semelhante a como encaixamos os elementos de HTML nativos, mas a Vue implementa o seu próprio modelo de componente que permite-nos encapsular o conteúdo personalizado e a lógica em cada componente. A Vue também lida muito bem com os Componentes da Web nativos. Se estivermos curiosos sobre a relação entre os Componentes da Vue e os Componentes da Web nativos, podemos ler mais sobre isto neste artigo.

Definindo um Componente ​

Quando usamos uma etapa de construção, normalmente definimos cada componente num ficheiro dedicado usando a extensão .vue - conhecido como Componente de Ficheiro Único (SFC, que é a forma abreviada no idioma original):

vue
<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>
vue
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>

Quando não usamos uma etapa de construção, um componente da Vue pode ser definido como um simples objeto de JavaScript contendo opções específicas da Vue:

js
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
}
js
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
  // Também pode mirar um modelo de marcação no DOM:
  // template: '#my-template-element'
}

Neste exemplo o modelo de marcação é incorporado como uma sequência de caracteres de JavaScript, a qual a Vue compilará rapidamente. Nós também podemos usar um seletor de identificador único apontando para um elemento (normalmente elementos <template> nativos) - a Vue usará o seu conteúdo como fonte do modelo de marcação.

O exemplo acima define um único componente e exporta-o como exportação padrão dum ficheiro .js, mas podemos usar exportações nomeadas para exportar vários componentes a partir do mesmo ficheiro.

Usando um Componente ​

NOTA

Nós usaremos a sintaxe de SFC para o resto deste guia - os conceitos em torno dos componentes são os mesmos independentemente de se estivermos usando uma etapa de construção ou não. A seção dos Exemplos mostra o uso do componente em ambos cenários.

Para usarmos um componente filho, precisamos de importá-lo no componente pai. Assumindo que colocamos o nosso componente contador dentro dum ficheiro chamado ButtonCounter.vue, o componente será exposto como exportação padrão do ficheiro:

vue
<script>
import ButtonCounter from './ButtonCounter.vue'

export default {
  components: {
    ButtonCounter
  }
}
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

Para expormos o componente importado ao nosso modelo de marcação, precisamos registá-lo com a opção components. O componente estará então disponível como um marcador usando a chave sob a qual está registado.

vue
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

Com o <script setup>, os componentes importados são disponibilizados automaticamente ao modelo de marcação.

Também é possível registar um componente globalmente, tornando-o disponível para todos os componentes numa dada aplicação sem ter de importá-lo. As vantagens e inconvenientes do registo global contra o registo local é discutido na seção de Registo de Componente dedicada.

Os componentes podem ser reutilizados quantas vezes quisermos:

template
<h1>Here are many child components!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

Repara que quando clicamos sobre os botões, cada um mantém o seu próprio count separado. Isto porque cada vez que usarmos um componente, uma nova instância do mesmo é criada.

Nos componentes de ficheiro único, é recomendado usar os nomes de marcadores em PascalCase para os componentes filhos para diferenciá-los dos elementos de HTML nativos. Apesar dos nomes dos marcadores de HTML nativos serem insensíveis a caracteres maiúsculos e minúsculos, o componente de ficheiro único da Vue é um formato compilado então somos capazes de usar nomes de marcadores sensíveis a caracteres maiúsculos e minúsculos nele. Nós também somos capazes de usar /> para fechar um marcador.

Se estivermos escrevendo os nossos modelos de marcação diretamente no DOM (por exemplo, como conteúdo dum elemento <template> nativo), o modelo de marcação estará sujeito ao comportamento de analise sintática de HTML nativo do navegador. Em tais casos, precisaremos usar kebab-case e fechar explicitamente os marcadores dos componentes:

template
<!-- se este modelo de marcação for escrito no DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

Consulte as Advertências da Analise Sintática do Modelo de Marcação no DOM por mais detalhes.

Passando as Propriedades ​

Se estivermos construindo um blogue, precisaremos possivelmente dum componente representando uma publicação de blogue. Nós queremos que todas as publicações de blogue partilhem a mesma disposição visual, mas com conteúdo diferente. Tal componente não será útil a menos que possamos passar dados a este, tais como o título e conteúdo da publicação específica que queremos exibir. É onde as propriedades entram.

As propriedades são atributos personalizados que podemos registar sobre um componente. Para passarmos um título ao nosso componente de publicação de blogue, devemos declará-lo na lista de propriedades que este componente aceita, usando a opção propsmacro defineProps:

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title']
}
</script>

<template>
  <h4>{{ title }}</h4>
</template>

Quando um valor for passado a um atributo da propriedade, este torna-se uma propriedade sobre a instância deste componente. O valor desta propriedade é acessível dentro do modelo de marcação e sobre o conteúdo this do componente, tal como qualquer outra propriedade do componente.

vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

A defineProps é uma macro do momento da compilação que apenas está disponível dentro do <script setup> e não precisa de ser explicitamente importada. As propriedades declaradas são automaticamente expostas ao modelo de marcação. A defineProps também retorna um objeto que contém todas as propriedades passadas ao componente, para que possamos acessá-las na JavaScript se necessário:

js
const props = defineProps(['title'])
console.log(props.title)

Consulte também: Tipificando as Propriedades do Componente

Se não estivermos usando o <script setup>, as propriedades devem ser declaradas usando a opção props, e o objeto de props será passado à setup() como primeiro argumento:

js
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

Um componente pode ter quantas propriedades acharmos conveniente, por padrão, qualquer valor pode ser passado a qualquer propriedade.

Depois duma propriedade ser registada, podemos passar os dados à esta como um atributo personalizado, desta maneira:

template
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

Numa aplicação normal, no entanto, possivelmente teremos um vetor de publicações no nosso componente pai:

js
export default {
  // ...
  data() {
    return {
      posts: [
        { id: 1, title: 'My journey with Vue' },
        { id: 2, title: 'Blogging with Vue' },
        { id: 3, title: 'Why Vue is so fun' }
      ]
    }
  }
}
js
const posts = ref([
  { id: 1, title: 'My journey with Vue' },
  { id: 2, title: 'Blogging with Vue' },
  { id: 3, title: 'Why Vue is so fun' }
])

Então desejaremos interpretar um componente para cada uma, usando v-for:

template
<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />

Repara como a sintaxe de v-bind (:title="post.title") é usada para passar os valores dinâmicos da propriedade. Isto é especialmente útil quando não sabemos antecipadamente qual é o exato conteúdo que iremos interpretar.

É tudo o que precisamos saber sobre as propriedades por agora, mas depois de terminarmos a leitura desta página e estivermos confortáveis com o seu conteúdo, recomendamos voltar mais tarde para ler o guia completo sobre as Propriedades.

Ouvindo Eventos ​

Conforme desenvolvermos o nosso componente <BlogPost>, algumas funcionalidades podem precisar de comunicarem-se de volta ao componente pai. Por exemplo, podemos decidir incluir uma funcionalidade de acessibilidade para ampliar o texto das publicações de blogue, enquanto deixamos o resto da página no seu tamanho padrão.

No componente pai, podemos suportar esta funcionalidade adicionando uma propriedade de dadosreferência postFontSize:

js
data() {
  return {
    posts: [
      /* ... */
    ],
    postFontSize: 1
  }
}
js
const posts = ref([
  /* ... */
])

const postFontSize = ref(1)

A qual pode ser usada no modelo de marcação para controlar o tamanho da fonte de todas as publicações de blogue:

template
<div :style="{ fontSize: postFontSize + 'em' }">
  <BlogPost
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
   />
</div>

Agora adicionaremos um botão ao modelo de marcação do componente <BlogPost>:

vue
<!-- BlogPost.vue, omitindo <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Enlarge text</button>
  </div>
</template>

O botão ainda não faz nada - queremos clicar sobre o botão para comunicar ao pai que este deve ampliar o tamanho da fonte do texto de todas as publicações. Para solucionarmos este problema, os componentes fornecem um sistema de eventos personalizados. O pai pode escolher ouvir qualquer evento sobre a instância do componente filho com a v-on ou @, tal como faríamos com um evento de DOM nativo:

template
<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

Então o componente filho pode emitir um evento sobre si mesmo chamando o método $emit embutido, passando o nome do evento:

vue
<!-- BlogPost.vue, omitindo <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

Graças ao ouvinte @enlarge-text="postFontSize += 0.1", o pai receberá o evento e atualizará o valor da postFontSize.

Nós podemos opcionalmente declarar os eventos emitidos usando a opção emitsmacro defineEmits:

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title'],
  emits: ['enlarge-text']
}
</script>
vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

Isto documenta todos os eventos que um componente emite e valida-os opcionalmente. Também permite a Vue evitar aplicá-los implicitamente como ouvintes nativos ao elemento de raiz do componente filho.

Semelhante à defineProps, a defineEmits apenas é utilizável no <script setup> e não precisa de ser importada. Esta retorna uma função emit que é equivalente ao método $emit. Esta pode ser usada para emitir eventos na seção <script setup> dum componente, onde $emit não está diretamente acessível:

vue
<script setup>
const emit = defineEmits(['enlarge-text'])

emit('enlarge-text')
</script>

Consulte também: Tipificando as Emissões do Componente

Se não estivermos usando o <script setup>, podemos declarar os eventos emitidos usando a opção emits. Nós podemos acessar a função emit como uma propriedade do contexto de configuração (passada à setup() como segundo argumento):

js
export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}

É tudo que precisamos saber sobre os eventos personalizados dos componentes por agora, mas depois que terminarmos a leitura desta página e estivermos confortáveis como o seu conteúdo, recomendamos voltar para ler o guia completa sobre os Eventos Personalizados.

Distribuição de Conteúdo com as Ranhuras ​

Tal como com os elementos de HTML, é muitas vezes útil ser capaz de passar conteúdo para um componente, desta maneira:

template
<AlertBox>
  Something bad happened.
</AlertBox>

O qual pode interpretar alguma coisa do tipo:

ISTO É UM ERRO PARA FINS DE DEMONSTRAÇÃO

Something bad happened.

Isto pode ser alcançado usando o elemento <slot> personalizado da Vue:

vue
<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

Conforme veremos acima, usamos <slot> como uma reserva de espaço de onde queremos que conteúdo seja interpretado - e é isto. Terminamos!

É tudo o que precisamos saber sobre as ranhuras por agora, mas depois de terminarmos a leitura desta página e estivermos confortáveis com o seu conteúdo, recomendamos voltar mais tarde para ler o guia completo sobre as Ranhuras.

Componentes Dinâmicos ​

Algumas vezes é útil alternar dinamicamente entre os componentes, como numa interface composta por abas:

O exemplo acima foi possível através do elemento <component> da Vue com o atributo especial is:

template
<!-- O componente muda quando `currentTab` mudar -->
<component :is="currentTab"></component>
template
<!-- O componente muda quando `currentTab` mudar -->
<component :is="tabs[currentTab]"></component>

No exemplo acima, o valor passado para :is pode conter ou:

  • a sequência de caracteres do nome dum componente registado, OU
  • o verdadeiro objeto do componente importado

Nós também podemos usar o atributo is para criar os elementos de HTML normais.

Quando alternamos entre vários componentes com o <component :is="...">, um componente será desmontado quando este for alternado para fora. Nós podemos forçar os componentes inativos para manterem-se "vivos" com o componente <KeepAlive> embutido.

Advertências da Analise do Modelo de Marcação no DOM ​

Se estivermos escrevendo os nossos modelos de marcação de Vue diretamente no DOM, a Vue precisará de recuperar a sequência de caracteres do modelo de marcação a partir do DOM. Isto leva a algumas advertências devido ao comportamento da analise sintática do HTML nativo do navegador.

NOTA

Deve ser notado que as limitações discutidas abaixo apenas aplicam-se se estivermos escrevendo os nossos modelos de marcação diretamente no DOM. Elas NÃO aplicam-se se estivermos usando os modelos de marcação de sequência de caracteres a partir das seguintes fontes:

  • Componentes de Ficheiro Único
  • Sequências de Caracteres de Modelo de Marcação Incorporado (por exemplo, template: '...')
  • <script type="text/x-template">

Insensibilidade aos Caracteres Maiúsculos e Minúsculos ​

Os marcadores da HTML e nomes de atributo são insensíveis aos caracteres maiúsculos e minúsculos, assim os navegadores interpretarão quaisquer caracteres maiúsculos como minúsculos. Isto significa que quando usamos os modelos de marcação no DOM, os nomes de componente de PascalCase e nomes de propriedade em camelCase ou os nomes de evento de v-on, todos precisam usar os seus equivalentes em kebab-case (delimitados por hífen):

js
// `camelCase` em JavaScript
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
template
<!-- `kebab-case` em HTML -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

Marcadores de Auto-Fechamento ​

Nós temos estado a usar os marcadores de auto-fechamento para os componentes nos anteriores exemplos de código:

template
<MyComponent />

Isto é porque o analisador sintático do modelo de marcação da Vue respeita o /> como uma indicação para o final de qualquer marcador, independentemente do seu tipo.

Nos modelos de marcação no DOM, no entanto, devemos sempre incluir explicitamente os marcadores de fechamento:

template
<my-component></my-component>

Isto porque a especificação da HTML apenas permite alguns elementos específicos omitirem os marcadores de fechamento, os mais comuns sendo <input> e <img>. Para todos os outros elementos, se omitirmos o marcador de fechamento, o analisador sintático da HTML nativa pensará que nunca terminamos a marcação de abertura. Por exemplo, o seguinte trecho:

template
<my-component /> <!-- tencionamos fechar o marcador nesta linha... -->
<span>hello</span>

será analisado sintaticamente como:

template
<my-component>
  <span>hello</span>
</my-component> <!-- mas o navegador fechará nesta linha. -->

Restrições da Colocação do Elemento ​

Alguns elementos da HTML, tais como <ul>, <ol>, <table> e <select> têm restrições sobre quais elementos podem aparecer dentro destes, e alguns elementos tais como <li>, <tr>, e <option> apenas podem aparecer dentro de outros certos elementos.

Isto levará a problemas quando usarmos componentes com os elementos que têm tais restrições. Por exemplo:

template
<table>
  <blog-post-row></blog-post-row>
</table>

O componente personalizado <blog-post-row> será içado para fora como conteúdo inválido, causando erros na eventual saída interpretada. Nós podemos usar o atributo especial is como uma solução:

template
<table>
  <tr is="vue:blog-post-row"></tr>
</table>

NOTA

Quando usado sobre os elementos da HTML nativa, o valor do is deve ser prefixado com vue: no sentido de ser interpretado como um componente de Vue. Isto é obrigatório para evitar a confusão com os elementos personalizados embutidos nativos.

É tudo o que precisamos saber sobre as advertências da analise do modelo de marcação no DOM por agora - e é de fato, o fim dos Fundamentos da Vue. Parabéns! Existe ainda muito mais para se aprender, mas primeiro, recomendamos dar uma pausa para experimentar a Vue por conta própria - construa alguma coisa divertida, ou consulte alguns dos Exemplos se já não o fizeste.

Depois de estivermos confortáveis com o conhecimento que já digerimos, podemos seguir com o guia para aprendermos mais sobre os componentes em profundidade.

Fundamentos dos Componentes has loaded