Vue.js para iniciantes, lição 11: guias, o ônibus global do evento

Hoje, na 11ª lição que conclui este tutorial de Fundamentos do Vue, falaremos sobre como organizar o conteúdo de uma página de aplicativo usando guias. Aqui, discutiremos o barramento de evento global - um mecanismo simples para transferir dados dentro de um aplicativo.







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.jshá 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 tabscontendo as strings que usamos como cabeçalhos de tabulação. O modelo de componente usa uma construção v-forque tabscria 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 componenteselectedTab. 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, selectedTabuma string será gravada Reviews. Se você clicar na guia Make a Review, a selectedTablinha 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 selectedTabigual tab. Como o selectedTabnome da guia na qual o usuário acabou de clicar está escrito, o estilo .activeTabserá aplicado especificamente à guia ativa.



Ou seja, quando o usuário clicar na primeira aba, tabserá 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 productmodelo 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 Reviewsacima 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 reviewscujos dados são usados ​​para exibir avaliações é armazenada como parte dos dados do componente product. Precisamos passar essa matriz para o componente product-tabsusando o mecanismo de adereços de componente . Vamos adicionar o product-tabsseguinte ao objeto com as opções usadas durante a criação :



props: {
  reviews: {
    type: Array,
    required: false
  }
}


Vamos passar uma matriz reviewsde componente productpara componente product-tabsusando a productseguinte 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-reviewdo componente do modelo do componente productpara 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-showpara 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étodoaddReview. O que aconteceu com ele?



Para responder a essa pergunta, vamos lembrar queaddReviewé 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-reviewagora não é um “filho”, um componente product, mas seu “neto”.



Nosso código agora é projetado para interagir product-reviewcom 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-reviewpara outro product, carregando a informação de que o formulário foi enviado e entregando os dados do formulário de product-reviewpara 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 eventBuslugar this:



eventBus.$emit('review-submitted', productReview)


Depois disso, você não precisa mais ouvir o evento do review-submittedcomponente product-review. Portanto, vamos alterar o código deste componente no modelo de componente product-tabspara o seguinte:



<product-review></product-review>


O productmé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 eventBusgerar um evento review-submitted, você precisa pegar os dados passados ​​nesse evento (ou seja, - productReview) e colocá-los na matriz de reviewscomponentes product. Na verdade, isso é muito semelhante ao que foi feito até agora em um método de addReviewque 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 thisque 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 productfunçã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 productfor 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 Shippinge ao projeto Details, que, respectivamente, exibem o custo de entrega das compras e informações sobre a mercadoria.





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






All Articles