
→ Vue.js para iniciantes lição 1: instância Vue
→ Vue.js para iniciantes, lição 2: atributos de vinculação
→ Vue.js para iniciantes lição 3: renderização condicional
→ Vue.js para iniciantes lição 4: exibição de listas
→ Vue .js para iniciantes lição 5: processamento de eventos
→ Vue.js para iniciantes lição 6: ligando classes e estilos
→ Vue.js para iniciantes lição 7: propriedades calculadas
→ Vue.js para iniciantes lição 8: componentes
→ Vue. js para iniciantes lição 9: eventos personalizados
→ Vue.js para iniciantes lição 10: formulários
O propósito da lição
Queremos ter guias na página do aplicativo, uma das quais permite que os visitantes escrevam análises de produtos e a outra permite que visualizem análises existentes.
Código inicial
É assim que o conteúdo do arquivo aparece nesta fase do trabalho
index.html
:
<div id="app">
<div class="cart">
<p>Cart({{ cart.length }})</p>
</div>
<product :premium="premium" @add-to-cart="updateCart"></product>
</div>
Em
main.js
há o seguinte código:
Vue.component('product', {
props: {
premium: {
type: Boolean,
required: true
}
},
template: `
<div class="product">
<div class="product-image">
<img :src="image" />
</div>
<div class="product-info">
<h1>{{ title }}</h1>
<p v-if="inStock">In stock</p>
<p v-else>Out of Stock</p>
<p>Shipping: {{ shipping }}</p>
<ul>
<li v-for="(detail, index) in details" :key="index">{{ detail }}</li>
</ul>
<div
class="color-box"
v-for="(variant, index) in variants"
:key="variant.variantId"
:style="{ backgroundColor: variant.variantColor }"
@mouseover="updateProduct(index)"
></div>
<button
@click="addToCart"
:disabled="!inStock"
:class="{ disabledButton: !inStock }"
>
Add to cart
</button>
</div>
<div>
<h2><font color="#3AC1EF">Reviews</font></h2>
<p v-if="!reviews.length">There are no reviews yet.</p>
<ul>
<li v-for="review in reviews">
<p>{{ review.name }}</p>
<p>Rating: {{ review.rating }}</p>
<p>{{ review.review }}</p>
</li>
</ul>
</div>
<product-review @review-submitted="addReview"></product-review>
</div>
`,
data() {
return {
product: 'Socks',
brand: 'Vue Mastery',
selectedVariant: 0,
details: ['80% cotton', '20% polyester', 'Gender-neutral'],
variants: [
{
variantId: 2234,
variantColor: 'green',
variantImage: './assets/vmSocks-green.jpg',
variantQuantity: 10
},
{
variantId: 2235,
variantColor: 'blue',
variantImage: './assets/vmSocks-blue.jpg',
variantQuantity: 0
}
],
reviews: []
}
},
methods: {
addToCart() {
this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);
},
updateProduct(index) {
this.selectedVariant = index;
},
addReview(productReview) {
this.reviews.push(productReview)
}
},
computed: {
title() {
return this.brand + ' ' + this.product;
},
image() {
return this.variants[this.selectedVariant].variantImage;
},
inStock() {
return this.variants[this.selectedVariant].variantQuantity;
},
shipping() {
if (this.premium) {
return "Free";
} else {
return 2.99
}
}
}
})
Vue.component('product-review', {
template: `
<form class="review-form" @submit.prevent="onSubmit">
<p v-if="errors.length">
<b>Please correct the following error(s):</b>
<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
</p>
<p>
<label for="name">Name:</label>
<input id="name" v-model="name">
</p>
<p>
<label for="review">Review:</label>
<textarea id="review" v-model="review"></textarea>
</p>
<p>
<label for="rating">Rating:</label>
<select id="rating" v-model.number="rating">
<option>5</option>
<option>4</option>
<option>3</option>
<option>2</option>
<option>1</option>
</select>
</p>
<p>
<input type="submit" value="Submit">
</p>
</form>
`,
data() {
return {
name: null,
review: null,
rating: null,
errors: []
}
},
methods: {
onSubmit() {
if(this.name && this.review && this.rating) {
let productReview = {
name: this.name,
review: this.review,
rating: this.rating
}
this.$emit('review-submitted', productReview)
this.name = null
this.review = null
this.rating = null
} else {
if(!this.name) this.errors.push("Name required.")
if(!this.review) this.errors.push("Review required.")
if(!this.rating) this.errors.push("Rating required.")
}
}
}
})
var app = new Vue({
el: '#app',
data: {
premium: true,
cart: []
},
methods: {
updateCart(id) {
this.cart.push(id);
}
}
})
Esta é a aparência do aplicativo agora.

Página do aplicativo
Tarefa
Atualmente, as resenhas e o formulário usado para enviar as resenhas são exibidos na página um ao lado do outro. Esta é uma estrutura bastante funcional. Porém, espera-se que mais e mais comentários apareçam na página ao longo do tempo. Isso significa que será mais conveniente para o usuário interagir com uma página que, de sua escolha, exibe um formulário ou uma lista de comentários.
A solução do problema
Para resolver nosso problema, podemos adicionar um sistema de guias à página. Um deles, com um título
Reviews
, exibirá resenhas. O segundo, com um título Make a Review
, exibirá um formulário para envio de avaliações.
Criação de um componente que implementa o sistema de guias
Vamos começar criando um componente
product-tabs
. Ele será exibido na parte inferior da representação visual do componente product
. Com o tempo, ele substituirá o código que é usado atualmente para exibir a lista de revisões e formulários na página.
Vue.component('product-tabs', {
template: `
<div>
<span class="tab" v-for="(tab, index) in tabs" :key="index">{{ tab }}</span>
</div>
`,
data() {
return {
tabs: ['Reviews', 'Make a Review']
}
}
})
No momento, este é apenas um componente em branco que iremos finalizar em breve. Por enquanto, vamos discutir brevemente o que é apresentado neste código.
Os dados do componente possuem um array
tabs
contendo as strings que usamos como cabeçalhos de tabulação. O modelo de componente usa uma construção v-for
que tabs
cria um elemento <span>
contendo a string correspondente para cada elemento do array . O que forma este componente neste estágio de trabalho será semelhante ao mostrado abaixo.

O componente product-tabs está no estágio inicial de trabalho
. Para atingir nossos objetivos, precisamos saber qual das guias está ativa. Portanto, vamos adicionar uma propriedade aos dados do componente
selectedTab
. Definiremos dinamicamente o valor dessa propriedade usando um manipulador de eventos que responde a cliques nos títulos da guia:
@click="selectedTab = tab"
A propriedade escreverá strings correspondentes aos cabeçalhos da guia.
Ou seja, se o usuário clicar na guia
Reviews
, selectedTab
uma string será gravada Reviews
. Se você clicar na guia Make a Review
, a selectedTab
linha será incluída Make a Review
.
É assim que o código do componente completo ficará agora.
Vue.component('product-tabs', {
template: `
<div>
<ul>
<span class="tab"
v-for="(tab, index) in tabs"
@click="selectedTab = tab"
>{{ tab }}</span>
</ul>
</div>
`,
data() {
return {
tabs: ['Reviews', 'Make a Review'],
selectedTab: 'Reviews' // @click
}
}
})
Vinculando uma classe a uma guia ativa
Um usuário que trabalha com uma interface que usa guias deve estar ciente de qual guia está ativa. Você pode implementar um mecanismo semelhante usando associação de classe para elementos
<span>
usados para exibir nomes de guia:
:class="{ activeTab: selectedTab === tab }"
Aqui está o arquivo CSS que define o estilo da classe usada aqui
activeTab
. Este é o aspecto deste estilo:
.activeTab {
color: #16C0B0;
text-decoration: underline;
}
E aqui está o estilo da aula
tab
:
.tab {
margin-left: 20px;
cursor: pointer;
}
Se explicarmos a construção acima em uma linguagem simples, descobrimos que o estilo especificado para a classe é aplicado à guia
activeTab
, no caso em que for selectedTab
igual tab
. Como o selectedTab
nome da guia na qual o usuário acabou de clicar está escrito, o estilo .activeTab
será aplicado especificamente à guia ativa.
Ou seja, quando o usuário clicar na primeira aba,
tab
será localizado Reviews
, a mesma será gravada selectedTab
. Como resultado, o estilo será aplicado à primeira guia .activeTab
.
Agora, os títulos das guias na página terão a aparência abaixo.

O título destacado da guia ativa
Parece que tudo está funcionando conforme o esperado neste estágio, então podemos prosseguir.
Trabalhando no modelo de componente
Agora que podemos dizer ao usuário qual guia é a ativa, podemos continuar trabalhando no componente. Ou seja, estamos falando sobre a finalização de seu modelo, descrevendo o que exatamente será exibido na página quando cada uma das guias for ativada.
Vamos pensar no que deve ser mostrado ao usuário se ele clicar na guia
Reviews
. Isso é, claro, análises de produtos. Portanto, moveremos o código para exibir as revisões do modelo de componente para o product
modelo de componente product-tabs
, colocando este código abaixo da construção usada para exibir os cabeçalhos da guia. Esta é a aparência do modelo de componente agora product-tabs
:
template: `
<div>
<ul>
<span class="tab"
:class="{ activeTab: selectedTab === tab }"
v-for="(tab, index) in tabs"
@click="selectedTab = tab"
>{{ tab }}</span>
</ul>
<div>
<p v-if="!reviews.length">There are no reviews yet.</p>
<ul>
<li v-for="review in reviews">
<p>{{ review.name }}</p>
<p>Rating: {{ review.rating }}</p>
<p>{{ review.review }}</p>
</li>
</ul>
</div>
</div>
`
Observe que nos livramos da tag
<h2><font color="#3AC1EF">
, pois não precisamos mais exibir o título Reviews
acima da lista de comentários. Em vez deste título, o título da guia correspondente será exibido.
Mas mover o código do modelo sozinho não é suficiente para fornecer feedback. A matriz
reviews
cujos dados são usados para exibir avaliações é armazenada como parte dos dados do componente product
. Precisamos passar essa matriz para o componente product-tabs
usando o mecanismo de adereços de componente . Vamos adicionar o product-tabs
seguinte ao objeto com as opções usadas durante a criação :
props: {
reviews: {
type: Array,
required: false
}
}
Vamos passar uma matriz
reviews
de componente product
para componente product-tabs
usando a product
seguinte construção no modelo :
<product-tabs :reviews="reviews"></product-tabs>
Agora vamos pensar sobre o que precisa ser exibido na página se o usuário clicar no título da guia
Make a Review
. Este é, obviamente, um formulário para enviar feedback. A fim de preparar o projeto para trabalhos futuros, vamos transferir o código de conexão product-review
do componente do modelo do componente product
para o modelo product-tabs
. Vamos colocar o seguinte código abaixo do elemento <div>
usado para exibir a lista de comentários:
<div>
<product-review @review-submitted="addReview"></product-review>
</div>
Se você olhar a página do aplicativo agora, verá que a lista de análises e o formulário são exibidos abaixo dos cabeçalhos das guias.

Uma etapa intermediária do trabalho na página
Neste caso, os cliques nos títulos, embora conduzam à sua seleção, não afetam de forma alguma os outros elementos da página. Além disso, se você tentar usar o formulário, verá que ele parou de funcionar normalmente. Todas essas são consequências bastante esperadas das alterações que fizemos no aplicativo. Vamos continuar trabalhando e trazer nosso projeto para um estado de funcionamento.
Exibição condicional de elementos da página
Agora que preparamos os elementos básicos do template de componente
product-tabs
, é hora de criar um sistema que permitirá exibir diferentes elementos de página com base no título de qual guia o usuário clicou.
Os dados do componente já possuem uma propriedade
selectedTab
. Podemos usá-lo em uma diretiva v-show
para renderizar condicionalmente o que pertence a cada uma das guias.
Assim, à tag que
<div>
contém o código de geração da lista de resenhas, podemos adicionar a seguinte construção:
v-show="selectedTab === 'Reviews'"
Graças a ela, a lista de comentários será exibida quando a guia estiver ativa
Reviews
.
Da mesma forma, adicionaremos o seguinte à tag
<div>
que contém o código de conexão do componente product-review
:
v-show="selectedTab === 'Make a Review'"
Isso fará com que o formulário seja exibido apenas quando a guia estiver ativa
Make a Review
.
Esta é a aparência do modelo de componente agora
product-tabs
:
template: `
<div>
<ul>
<span class="tab"
:class="{ activeTab: selectedTab === tab }"
v-for="(tab, index) in tabs"
@click="selectedTab = tab"
>{{ tab }}</span>
</ul>
<div v-show="selectedTab === 'Reviews'">
<p v-if="!reviews.length">There are no reviews yet.</p>
<ul>
<li v-for="review in reviews">
<p>{{ review.name }}</p>
<p>Rating: {{ review.rating }}</p>
<p>{{ review.review }}</p>
</li>
</ul>
</div>
<div v-show="selectedTab === 'Make a Review'">
<product-review @review-submitted="addReview"></product-review>
</div>
</div>
`
Se você olhar a página e clicar nas guias, poderá ter certeza de que o mecanismo que criamos está funcionando corretamente.

Clicar nas guias oculta alguns itens e exibe outros.
Enviar feedback por meio de um formulário ainda não funciona. Vamos investigar o problema e corrigi-lo.
Resolvendo o problema com o envio de feedback
Se você olhar o console de ferramentas do desenvolvedor do navegador agora, verá um aviso.

Aviso do console
Aparentemente, o sistema não pode detectar o método
addReview
. O que aconteceu com ele?
Para responder a essa pergunta, vamos lembrar que
addReview
é um método declarado em um componenteproduct
. Deve ser chamado se o componenteproduct-review
(e este é um componente filho do componenteproduct
) gerar um eventoreview-submitted
:
<product-review @review-submitted="addReview"></product-review>
É assim que tudo funcionava antes de transferir o trecho de código acima para o componente
product-tabs
. E agora um componente product
é um componente filho product-tabs
, e product-review
agora não é um “filho”, um componente product
, mas seu “neto”.
Nosso código agora é projetado para interagir
product-review
com o componente pai. Mas agora não é mais um componente product
. Como resultado, para que o formulário funcione corretamente, precisamos refatorar o código do projeto.
Refatorando o código do projeto
Para garantir a comunicação dos componentes dos netos com seus "avós", ou para estabelecer a comunicação entre os componentes do mesmo nível, é frequentemente utilizado um mecanismo denominado barramento de evento global.
O Global Event Bus é um canal de comunicação que pode ser usado para transferir informações entre componentes. E é, na verdade, apenas uma instância do Vue que é criada sem passar um objeto com opções. Vamos criar um ônibus de evento:
var eventBus = new Vue()
Este código irá para o nível superior do arquivo
main.js
.
Você pode achar mais fácil entender esse conceito se pensar no ônibus de eventos como um ônibus. Seus passageiros são dados que alguns componentes enviam a outros. Em nosso caso, estamos falando sobre a transferência de informações sobre eventos gerados por outros componentes para um componente. Ou seja, nosso "ônibus" irá viajar de um componente
product-review
para outro product
, carregando a informação de que o formulário foi enviado e entregando os dados do formulário de product-review
para product
.
Agora, no componente
product-review
, no método onSubmit
, há uma linha assim:
this.$emit('review-submitted', productReview)
Vamos substituí-lo pelo próximo, usando em seu
eventBus
lugar this
:
eventBus.$emit('review-submitted', productReview)
Depois disso, você não precisa mais ouvir o evento do
review-submitted
componente product-review
. Portanto, vamos alterar o código deste componente no modelo de componente product-tabs
para o seguinte:
<product-review></product-review>
O
product
método agora pode ser removido do componente addReview
. Em vez disso, usaremos a seguinte construção:
eventBus.$on('review-submitted', productReview => {
this.reviews.push(productReview)
})
Falaremos a seguir sobre como usá-lo em um componente, mas, por enquanto, descreveremos em poucas palavras o que acontece nele. Essa construção indica que, ao
eventBus
gerar um evento review-submitted
, você precisa pegar os dados passados nesse evento (ou seja, - productReview
) e colocá-los na matriz de reviews
componentes product
. Na verdade, isso é muito semelhante ao que foi feito até agora em um método de addReview
que não precisamos mais. Observe que o trecho de código acima usa uma função de seta. Este momento merece uma cobertura mais detalhada.
Razões para usar uma função de seta
Aqui, estamos usando a sintaxe da função de seta que apareceu no ES6. A questão é que o contexto da função de seta está vinculado ao contexto pai. Ou seja, quando nós, dentro desta função, usamos uma palavra-chave
this
, ela equivale à palavra-chave this
que corresponde à entidade que contém a função seta.
Este código pode ser reescrito sem o uso de funções de seta, mas você precisa organizar a vinculação
this
:
eventBus.$on('review-submitted', function (productReview) {
this.reviews.push(productReview)
}.bind(this))
Concluindo o projeto
Quase alcançamos nosso objetivo. Tudo o que resta a ser feito é encontrar um local para o trecho de código que fornece a reação ao evento
review-submitted
. Uma product
função pode se tornar um lugar em um componente mounted
:
mounted() {
eventBus.$on('review-submitted', productReview => {
this.reviews.push(productReview)
})
}
Qual é esta função? Este é um gancho de ciclo de vida que é chamado uma vez depois que o componente é montado no DOM. Agora, depois que o componente
product
for montado, ele aguardará que os eventos ocorram review-submitted
. Depois que tal evento for gerado, o que é passado neste evento será adicionado aos dados do componente, ou seja, - productReview
.
Se você agora tentar deixar um comentário sobre o produto usando o formulário, verifica-se que esse comentário é exibido onde deveria estar.

O formulário funciona como deveria
O ônibus de eventos não é a melhor solução para comunicar componentes
Embora o barramento de eventos seja frequentemente usado e você possa encontrá-lo em vários projetos, lembre-se de que essa está longe de ser a melhor solução para o problema de conexão de componentes de aplicativos.
Conforme o aplicativo cresce, um sistema de gerenciamento de estado baseado em Vuex pode ser muito útil . É um padrão e biblioteca de gerenciamento de estado de aplicativo.
Oficina
Adicione as abas
Shipping
e ao projeto Details
, que, respectivamente, exibem o custo de entrega das compras e informações sobre a mercadoria.
- Aqui está um modelo que você pode usar para resolver esse problema.
- Aqui está a solução para o problema.
Resultado
Aqui está o que você aprendeu neste tutorial:
- Você pode usar ferramentas de renderização condicional para organizar o mecanismo das guias.
- , Vue, .
- — . . — Vuex.
Esperamos que depois de fazer este curso Vue, você tenha aprendido o que queria e esteja pronto para aprender muito mais coisas novas e interessantes sobre esta estrutura.
Se você acabou de concluir este curso, compartilhe suas impressões.
→ Vue.js para iniciantes lição 1: instância Vue
→ Vue.js para iniciantes, lição 2: atributos de vinculação
→ Vue.js para iniciantes lição 3: renderização condicional
→ Vue.js para iniciantes lição 4: exibição de listas
→ Vue .js para Iniciantes Lição 5: Tratamento de Eventos
→ Vue.js para Iniciantes, Lição 6: Aulas e Estilos de Vinculação
→ Vue.js para Iniciantes, Lição 7: Propriedades Computadas
→Vue.js para iniciantes, lição 8: Componentes
→ Vue.js para iniciantes, lição 9: eventos personalizados
→ Vue.js para iniciantes, lição 10: Formulários
