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:
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 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
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 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 macro defineProps
:
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
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 referência postFontSize
:
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 macro defineEmits
:
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="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.