Académique Documents
Professionnel Documents
Culture Documents
js 2 (Portuguese)
Alex Kyriakidis, Kostas Maniatis and Daniel Schmitz
Esse livro está à venda em http://leanpub.com/vuejs2-portuguese
Bem vindo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Sobre o livro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Para quem é este livro . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Entre em Contato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
Código Fonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
Errata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
Convenções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vi
Sobre o Vue.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii
Visão Geral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii
O que as pessoas estão dizendo sobre o Vue.js . . . . . . . . . . . . . . . vii
I Fundamentos do Vue.js . . . . . . . . . . . . . . . . 1
1. Instalar Vue.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1 Versão Standalone . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Download no Site vuejs.org . . . . . . . . . . . . . . . . . . . . 2
Incluir do CDN . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Download usando NPM . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Download usando Bower . . . . . . . . . . . . . . . . . . . . . . 3
2. Começando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1 Olá mundo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 Two-way Binding . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 Comparação com jQuery. . . . . . . . . . . . . . . . . . . . . . . 8
2.4 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3. Um Leque de Diretivas . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1 v-show . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2 v-if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Template v-if . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3 v-else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.4 v-if vs. v-show . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.5 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4. Renderização de Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.1 Instalação & uso do Bootstrap . . . . . . . . . . . . . . . . . . . 23
4.2 v-for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Range para o v-for . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.3 Renderização de Array . . . . . . . . . . . . . . . . . . . . . . . 28
Loop através de um Array . . . . . . . . . . . . . . . . . . . . . 28
Loop através de um Array de Objetos . . . . . . . . . . . . . . . 31
4.4 Objeto v-for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.5 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5. Interatividade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.1 Gerenciando Eventos . . . . . . . . . . . . . . . . . . . . . . . . 39
Gerenciando Eventos Inline . . . . . . . . . . . . . . . . . . . . 40
CONTEÚDO
6. Filtros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.1 Filtrando Resultados . . . . . . . . . . . . . . . . . . . . . . . . . 59
Usando Propriedades Computadas . . . . . . . . . . . . . . . . . 63
6.2 Ordenar resultados . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.3 Filtros Customizados . . . . . . . . . . . . . . . . . . . . . . . . 74
6.4 Bibliotecas Utilitárias . . . . . . . . . . . . . . . . . . . . . . . . 76
6.5 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
7. Componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
7.1 O que são Componentes? . . . . . . . . . . . . . . . . . . . . . . 82
7.2 Usando Componentes . . . . . . . . . . . . . . . . . . . . . . . . 82
7.3 Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
7.4 Propriedades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
7.5 Reutilização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
7.6 Completando . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
7.7 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Sintaxe em um Array . . . . . . . . . . . . . . . . . . . . . . . . 130
9.2 Binding em estilos . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Sintaxe em Arrays . . . . . . . . . . . . . . . . . . . . . . . . . 133
9.3 Bindings em ação . . . . . . . . . . . . . . . . . . . . . . . . . . 134
9.4 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
Este livro é útil para desenvolvedores que nunca tiveram contato com o Vue.js, bem
como para aqueles que já o usam e querem expandir seus conhecimentos. Ele também
é útil para desenvolvedores que querem migrar para o Vue.js 2.
Entre em Contato
Caso você queira entrar em contato conosco sobre o livro, envie-nos comentários ou
outros assuntos que gostaria de chamar a nossa atenção. Não hesite em nos contatar.
Nota do tradutor: Para questões em português, entre em contato com Daniel Schmitz
pelo Telegram1 .
Tarefa
A melhor maneira de aprender a programar é programando, então preparamos
um exercício no final da maioria dos capítulos para você resolver e realmente testar
o que aprendeu. Recomendamos muito que você tente resolver o máximo possível
a fim de obter um melhor entendimento do Vue.js. Não tenha medo de testar suas
ideias, um pouco de esforço te levará longe! Talvez alguns exemplos ou diferentes
formas de resolução te darão uma boa ideia. É claro que não somos intransigentes,
sugestões e possíveis soluções serão fornecidas!
Você pode começar sua jornada!
1
http://t.me/DanielSchmitz
Bem vindo iii
Código Fonte
Você pode encontrar a maioria dos exemplos de código usados no livro no GitHub.
Você pode acessar o código aqui2 .
Se preferir fazer o download, você encontrará um arquivo .zip aqui3 .
Isto te poupará de copiar e colar coisas do livro, o que provavelmente seria horrível.
Errata
Embora todos os cuidados tenham sido tomados para garantir a precisão do nosso
conteúdo, erros acontecem. Se você encontrar um erro no livro, ficaremos gratos se
puder nos avisar. Ao fazer isso, você ajudará outros leitores e a melhorar as versões
subsequentes deste livro. Caso encontre algum erro, por favor envie uma issue em
nosso repositório GitHub4 .
Convenções
As seguintes convenções de notação são usadas em todo o livro.
Um bloco de código é definido da seguinte forma:
JavaScript
1 function(x, y){
2 // isto é um comentário
3 }
Isto é um aviso
Este elemento indica um aviso ou atenção.
5
https://github.com/vuejs/awesome-vue#libraries--plugins
6
https://br.vuejs.org/v2/guide/
Sobre o Vue.js viii
“Vue.js tem sido capaz de fazer um framework que é tão simples de usar quanto fácil
de entender. Ele é um sopro de ar fresco num mundo onde os outros estão lutando
para ver quem pode fazer o framework mais complexo.”
— Eric Barnes
Complexidade
O Vue é muito mais simples que o Angular 1, tanto em termos de API quanto de
design. Aprender o suficiente para construir aplicações não triviais leva normalmente
menos de um dia, o que não acontece com o Angular 1.
Flexibilidade e Modularidade
O Angular 1 é mais rígido em relação ao modo como suas aplicações deveriam ser
estruturadas, enquanto que o Vue é uma solução mais flexível e modular.
É por isso que um Template Webpack7 é fornecido, podendo ser configurado por você
em poucos minutos, enquanto também lhe concede acesso a recursos avançados, tais
como: module reloading, linting, CSS extraction e muito mais.
Data binding
O Angular 1 usa two-way binding entre escopos, enquanto o Vue força o fluxo de
dados no modo one-way entre componentes. Isso torna o fluxo de dados mais fácil
de ser compreendido em aplicações não triviais.
7
https://github.com/vuejs-templates/webpack
Comparação com Outros Frameworks xi
Diretivas vs Componentes
O Vue tem uma separação mais clara entre diretivas e componentes. Diretivas
destinam-se a encapsular apenas manipulações na DOM, enquanto que componentes
são unidades auto-contidas que tem seu próprio layout e lógica de dados. No Angular,
existe muita confusão entre os dois.
Desempenho
O Vue tem um melhor desempenho e é muito, muito mais fácil de otimizar porque
não usa dirty checking. O Angular 1 torna-se lento quando há muitos observadores,
porque todas as vezes que algo no escopo muda, todos esses observadores precisam
ser reavaliados novamente. Além disso, o ciclo de notificação pode ter que ser
executado várias vezes caso algum observador acione outra atualização. Os usuários
do Angular muitas vezes precisam recorrer à técnicas extravagantes para contornar
o ciclo de notificação, e em algumas situações, não há como otimizar um escopo com
muitos observadores.
O Vue por sua vez não tem esse problema, porque usa um sistema de observação
transparente de rastreamento de dependência com filas assíncronas - todas as
alterações são acionadas independemente, a menos que tenham relações explícitas
de dependência.
Curiosamente, existem muitas semelhanças no modo como o Angular 2 e o Vue
abordam essas questões mal resolvidas do Angular 1.
Angular 2
Há uma seção específica para o Angular 2 porque ele é realmente um framework
completamente novo. Por exemplo, ele possui um sistema baseado unicamente em
componentes, muitos detalhes de implementação foram completamente reescritos, e
a API também mudou drasticamente.
Tamanho e Desempenho
entanto, se você está determinado a ver alguns números, o Vue 2.0 parece estar à
frente do Angular 2 de acordo com isso: 3rd party benchmark8 .
Em termos de tamanho, embora o Angular 2 com uma compilação offline tenha o seu
tamanho reduzido consideravelmente, uma versão full do Vue 2.0 (23kb), continua
sendo menor que a versão do Angular 2 (50kb).
Flexibilidade
O Vue é bem mais flexível que o Angular 2, oferecendo suporte oficial para uma
variedade de sistemas de build, sem restrições sobre como estruturar sua aplicação.
Muitos desenvolvedores gostam dessa liberdade, enquanto que alguns preferem ter
apenas uma maneira correta de construir qualquer aplicativo.
Curva de Aprendizado
Para começar com o Vue, tudo o que você precisa é familiaridade com HTML e
JavaScript ES5 (ou seja, JavaScript puro). Com essas habilidades básicas, você pode
começar a construir aplicações dentro de menos de um dia da leitura do guia.
A curva de aprendizado do Angular 2 é muito mais íngreme. Mesmo sem o TypeS-
cript, seu Quickstart guide9 começa com um aplicativo que usa ES2015 JavaScript,
NPM com 18 dependências, 4 arquivos, e mais de 3.000 palavras para explicar apenas
como dizer: Hello World.
8
http://stefankrause.net/js-frameworks-benchmark4/webdriver-ts/table.html
9
https://angular.io/docs/js/latest/quickstart.html
Comparação com Outros Frameworks xiii
React
O React e o Vue compartilham muitas similaridades. Ambos:
Perfis de Desempenho
Em cada cenário do mundo real que foi testado até agora, o Vue supera considera-
velmente o React.
Desempenho de Renderização
Quando a UI é renderizada, a manipulação do DOM é normalmente a operação mais
custosa e, infelizmente, nenhuma biblioteca pode tornar essa ação mais rápida. O
melhor que pode ser feito é:
Isto significa que, em aplicações típicas, onde existem muito mais elementos que
componentes sendo renderizados, o Vue irá superar o React por uma margem
significativa. Em casos extremos no entanto, tal como usar um componente para
renderizar cada elemento, o Vue será geralmente mais lento.
Comparação com Outros Frameworks xiv
Tanto o Vue quanto o React também oferecem componentes funcionais, que são
stateless e instanceless - e portanto requerem menos sobrecarga. Quando estes são
usados em situações de desempenho crítico, o Vue é mais uma vez mais rápido.
Desempenho de Atualização
No React, você precisa implementar o shouldComponentUpdate em toda parte e
usar estruturas de dados imutáveis para obter re-renders totalmente otimizados. No
Vue, as dependências de um componente são rastreadas automaticamente, de modo
que ele atualiza apenas quando uma dessas dependências sofre mudança. A única
otimização extra que as vezes pode ser útil no Vue está em adicionar um atributo
chave a itens em listas longas.
Isso significa que as atualizações no Vue sem otimização serão mais rápidas que as
do React nas mesmas condições e, devido ao melhor desempenho de renderização do
Vue, mesmo que o React esteja totalmente otimizado, ele será geralmente mais lento
que o Vue sem otimizações.
Em Desenvolvimento
Obviamente, o desempenho na produção é o mais importante e é isso que discutimos
até agora. Mesmo assim, o desempenho no desenvolvimento ainda é importante. A
boa notícia é que tanto o Vue quanto o React permanecem rápidos o suficiente no
desenvolvimento da maioria das aplicações normais.
No entanto, se você estiver criando protótipos de visualização de dados de alto
desempenho ou animações, é interessante saber que, em cenários onde o Vue pode
exibir mais de 10 frames por segundo no desenvolvimento, vemos que o React entrega
cerca de 1 frame por segundo.
Isso se deve às muitas e intensas verificações invariantes do React, que ajudam a
exibir muitos e excelentes avisos e mensagens de erros.
Comparação com Outros Frameworks xv
Ember
O Ember é um framework completo projetado para ter uma estrutura engessada.
Ele fornece uma série de convenções estabelecidas e uma vez que familiarizado o
suficiente com elas, você poderá ser muito produtivo. Entretanto, isso significa uma
alta curva de aprendizado e muito pouca flexibilidade. É uma questão de opção,
escolher entre um framework engessado ou uma biblioteca com um conjunto de
ferramentas pouco acopladas que interagem entre si. Esta última opção lhe dá mais
liberdade, mas também exige que você tome mais decisões arquitetônicas.
Dito isso, provavelmente a melhor comparação seria entre o núcleo do Vue e as
camadas de tamplates e de modelo de objetos do Ember:
Polymer
O Polymer é mais um projeto patrocinado pelo Google e com certeza também foi
uma fonte de inspiração para o Vue. Os componentes do Vue podem ser vagamente
comparados aos elementos personalizados do Polymer e ambos fornecem um estilo
de desenvolvimento muito semelhante. A maior diferença é que o Polymer é
construído com base nos mais recentes recursos de Web Components e requer polyfills
não triviais para funcionar (com desempenho degradado) em navegadores que não
suportam nativamente esses recursos. Ao contrário, o Vue funciona sem qualquer
dependência ou polyfills até o IE9.
No Polymer 1.0, a equipe também fez seu data-binding muito limitado a fim
de compensar o desempenho. Por exemplo, as únicas expressões suportadas em
templates do Polymer são de negação booleana e chamadas simples de método. Sua
implementação de propriedade calculada também não é muito flexível.
Os elementos personalizados do Polymer são criados em arquivos HTML, os quais
te limitam ao JavaScript/CSS puro (e recursos de linguagens suportados pelos
navegadores atuais). Em comparação, os single file components do Vue permitem
facilmente usar ES2015+ e qualquer pré-processador CSS que você desejar.
Após a implantação, o Polymer recomenda carregar tudo sob demanda com importa-
ções HTML, o que pressupõe navegadores que suportem esta especificação e HTTP/2
no servidor e no cliente. Isto pode ou não ser viável, dependendo de seu público-
alvo e do ambiente de implantação. Nos casos em que isso não é desejável, você
terá que usar uma ferramenta especial chamada Vulcanizer para empacotar seus
elementos Polymer. Nesta frente, o Vue pode combinar o seu recurso de componente
assíncrono com o recurso de code-splitting do Webpack para dividir facilmente partes
do pacote da aplicação para serem carregados de acordo com a necessidade (lazy-
loaded). Isso garante a compatibilidade com navegadores mais antigos, mantendo o
excelente desempenho no carregamento.
Comparação com Outros Frameworks xvii
Riot
O Riot 2.0 fornece um modelo similar de desenvolvimento de componentes (que é
chamado de “tag” no Riot), com uma API bem projetada e minimalista. O Riot e
o Vue provavelmente compartilham muito quando o assunto é design. Entretanto,
apesar de ser um pouco mais pesado que o Riot, o Vue oferece algumas vantagens
significativas:
10
http://vuejs.org/v2/guide/comparison.html
I Fundamentos do Vue.js
1. Instalar Vue.js
Quando se trata da instalação do Vue.js, você tem algumas opções a escolher.
Incluir do CDN
Vue.js.org3 recomenda unpkg4 , o qual reflete a versão mais recente assim que for
publicado no npm.
1
http://vuejs.org/js/vue.js
2
http://vuejs.org/js/vue.min.js
3
https://vuejs.org/v2/guide/installation.html#CDN
4
https://unpkg.com/vue/dist/vue.js
Instalar Vue.js 3
Leva algum tempo para sincronizar a versão mais recente, então você deve
verificar atualizações frequentemente.
Na maioria dos exemplos estamos incluindo o Vue.js pelo CDN, mas você pode
instalar por qualquer método que achar melhor.
5
https://cdn.jsdelivr.net/vue/2.0.1/vue.min.js
6
https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.min.js
7
http://webpack.github.io/
8
http://browserify.org/
9
http://br.vuejs.org/v2/guide/installation.html
2. Começando
Vamos iniciar com um tour rápido pelas funcionalidades de data binding do Vue.js.
Vamos fazer uma simples aplicação que nos permitirá inserir uma mensagem e exibí-
la na página em tempo real. Ela vai mostrar o poder do two-way data binding que o
Vue.js possui.
Para criar a nossa aplicação Vue, precisamos configurar algumas coisas que envolvem
apenas a criação de uma página HTML.
Neste processo você terá uma ideia de quanto de tempo e esforço é poupado ao usar
o Vue.js em relação à outra ferramenta, como a biblioteca jQuery.
1 <html>
2 <head>
3 <title>Hello Vue</title>
4 </head>
5 <body>
6 <h1>Greetings your Majesty!</h1>
7 </body>
8 </html>
1 <html>
2 <head>
3 <title>Hello Vue</title>
4 </head>
5 <body>
6 <div id="app">
7 <h1>Greetings your majesty!</h1>
8 </div>
9 </body>
10 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.mi\
11 n.js"></script>
12 <script>
13 new Vue({
14 el: '#app',
15 })
16 </script>
17 </html>
Para iniciarmos, nós incluímos o Vue.js do cdnjs1 e dentro da tag script teremos a
instância do Vue disponível para uso.
Usamos uma div com o parâmetro id tendo o valor #app, que é o elemento de
referência, então o Vue irá saber para onde “olhar”. Tente pensar nisso como um
container no qual o Vue irá trabalhar. Vue não irá reconhecer nada fora do seu
contêiner. Use a opção el para indicar qual elemento será observado pelo Vue.
Agora nós iremos atribuir a mensagem que queremos exibir a uma variável dentro
do objeto chamado de data. Em seguida, passaremos o objeto data como uma
propriedade do construtor do Vue.
1
https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.min.js
Começando 6
1 var data = {
2 message: 'Greetings your majesty!'
3 };
4 new Vue({
5 el: '#app',
6 data: data
7 })
1 <div id="app">
2 <h1>{{ message }}</h1>
3 </div>
É simples assim!
Outra maneira de definir uma mensagem em uma variável é fazer isso diretamente
dentro do construtor Vue, no objeto data.
1 new Vue({
2 el: '#app',
3 data: {
4 message: 'Greetings your Majesty!'
5 }
6 });
Ambos os caminhos têm o mesmo resultado, então você pode escolher livremente a
sintaxe que preferir.
Informação
Os colchetes duplos não são código HTML, são scripts, qualquer coisa
dentro dele é chamado de binding expression. O Javascript irá “executar”
estas expressões. A expressão {{ message }} irá trazer o valor da variável.
Este código {{1+2}} irá mostrar o número 3.
Começando 7
1 <div id="app">
2 <h1>{{ message }}</h1>
3 <input v-model="message">
4 </div>
1 new Vue({
2 el: '#app',
3 data: {
4 message: 'Greetings your Majesty!'
5 }
6 })
É isso aí! Agora a nossa mensagem e a caixa de texto estão ligadas. Usando v-model
dentro da tag input dizemos ao Vue qual variável deverá estar ligada a qual input,
neste caso message.
Começando 8
O termo Two-way data binding significa que se você alterar o valor do modelo no
formulário, tudo será atualizado.
1 <html>
2 <head>
3 <title>Hello Vue</title>
4 </head>
5 <body>
6 <div id="app">
7 <h1>Greetings your Majesty!</h1>
8 <input id="message">
9 </div>
10 </body>
11 <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
12 <script type="text/javascript">
13 $('#message').on('keyup', function(){
14 var message = $('#message').val();
15 $('h1').text(message);
16 })
17 </script>
18 </html>
Este é um exemplo simples de comparação e, como você pode ver, o Vue parece ser
bem mais bonito, mais rápido de escrever e mais fácil de entender.
Claro que, o jQuery é uma poderosa biblioteca para manipular o Documento Object
Model (DOM), mas tudo tem os seus prós e contras.
Código fonte
Você pode encontrar estes exemplos no GitHub2 .
2
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/codes/chapter2.html
Começando 10
2.4 Tarefa
Um bom e super exercício de introdução é criar um arquivo com uma mensagem de
Hello no cabeçalho com a variável {{name}}. Adicione uma caixa de texto e ligue-
a a variável name. Como pode-se imaginar, o título será alterado instantaneamente
enquanto o usuário digita algo. Boa sorte e divirta-se!
Exemplo
Nota
O exemplo exibido faz uso do Bootstrap Se você não é familiar ao que o
Bootstrap faz, pode ignorar por enquanto, ele será visto em um próximo
capítulo.
Solução sugerida
Você pode encontrar uma solução sugerida deste exercício aqui3 .
3
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/homework/chapter2.html
3. Um Leque de Diretivas
Neste capítulo vamos abordar alguns exemplos básicos das diretivas Vue.
Bom, se você não usou um Framework como o Vue.js ou Angular.js antes, você
provavelmente não sabe o que são diretivas. Essencialmente, uma diretiva é um token
especial que diz à biblioteca algo para se fazer em um elemento da DOM.
Em Vue.js, o conceito de diretiva é drasticamente simples em relação ao Angular.
Algumas diretivas são:
Além disso, existe o v-for, o qual requer uma sintaxe especial e é usado para uma
renderização de laço (ex. renderizar uma lista de itens baseada em um Array).
Vamos elaborar mais sobre o uso de cada um deles, mais tarde neste livro.
Vamos começar e dar uma olhada nas diretrizes que mencionamos.
3.1 v-show
Para demonstrar esta primeira diretiva, vamos criar algo simples. Vamos dar a você
algumas dicas que irão lhe fazer entender a trabalhar de forma muito mais fácil!
Suponha que você precise alternar a visualização de um elemento, baseando-se em
algum critério.
Talvez um botão não deva aparecer a não ser que você digite alguma mensagem.
Como nós podemos fazer isso com o Vue?
Um Leque de Diretivas 12
1 <html>
2 <head>
3 <title>Hello Vue</title>
4 </head>
5 <body>
6 <div id="app">
7 <textarea></textarea>
8 </div>
9 </body>
10 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
11 "></script>
12 <script>
13 new Vue({
14 el: '#app',
15 data: {
16 message: 'Our king is dead!'
17 }
18 })
19 </script>
20 </html>
1 <html>
2 <head>
3 <title>Hello Vue</title>
4 </head>
5 <body>
6 <div id="app">
7 <textarea v-model="message"></textarea>
8 <pre>
9 {{ $data }}
10 </pre>
11 </div>
12 </body>
13 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
14 "></script>
15 <script>
16 new Vue({
17 el: '#app',
18 data: {
19 message: 'Our king is dead!'
20 }
21 })
22 </script>
23 </html>
É hora de ligar o valor do textarea com a variável message usando v-model para a
mensagem ser exibida.
Qualquer coisa que digitarmos irá alterar a variável em tempo real, assim como vimos
no capítulo anterior quando usamos uma caixa de texto.
Adicionalmente, estamos usando a tag pre para formatar corretamente a saída do
objeto data. O que isso faz é levar o objeto data, da instância Vue, com um filtro por
json, e exibir os dados no navegador.
O Vue vai formatar automaticamente e de forma nítida a saída para nós, indepen-
dente de ser uma string, number, Array ou um objeto. Acreditamos que isso seja uma
melhor maneira de manipular dados, já que estes estarão sempre visíveis para você,
o que é melhor que ter que olhar sempre no console.
Um Leque de Diretivas 14
Informação
JSON (JavaScript Object Notation) é um formato leve de dados para
intercâmbio de informações. Você pode encontrar mais sobre JSON aqui1 .
A saída do {{ $data }} é ligada ao objeto data do Vue e será atualizada
a cada alteração.
1 <html>
2 <head>
3 <title>Hello Vue</title>
4 </head>
5 <body>
6 <div id="app">
7 <h1>You must send a message for help!</h1>
8 <textarea v-model="message"></textarea>
9 <button v-show="message">
10 Send word to allies for help!
11 </button>
12 <pre>
13 {{ $data }}
14 </pre>
15 </div>
16 </body>
17 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
18 "></script>
19 <script>
20 new Vue({
21 el: '#app',
22 data: {
23 message: 'Our king is dead! Send help!'
24 }
25 })
26 </script>
27 </html>
1
http://www.json.org/
Um Leque de Diretivas 15
Continuando, agora temos um simples aviso na tag h1 que irá alternar mais tarde
baseado em algum critério. Próximo a ele, existe o botão que será exibido dada a
condição. Ele aparece somente se houver algum conteúdo na variável message.
Se o textarea estiver vazio, e portanto, nossos dados, o atributo display do botão
será automaticamente definido como none, e o botão desaparecerá.
Informação
Um elemento com v-show irá sempre ser renderizado e ficará na DOM. A
diretiva v-show simplesmente altera o atributo CSS display do elemento.
O que queremos realizar neste exemplo é alternar elementos diferentes. Nesta etapa,
precisamos ocultar o aviso dentro da tag h1, se a mensagem estiver presente. Caso
contrário, ocultaremos a mensagem através do atributo style, informando o valor
display: none.
3.2 v-if
Neste ponto você pode perguntar ‘E a diretiva v-if que mencionamos mais cedo?’.
Então, nós vamos construir o exemplo anterior novamente, só que desta vez vamos
usar v-if!
Um Leque de Diretivas 16
1 <html>
2 <head>
3 <title>Hello Vue</title>
4 </head>
5 <body>
6 <div id="app">
7 <h1 v-if="!message">You must send a message for help!</h1>
8 <textarea v-model="message"></textarea>
9 <button v-if="message">
10 Send word to allies for help!
11 </button>
12 <pre>
13 {{ $data }}
14 </pre>
15 </div>
16 </body>
17 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
18 "></script>
19 <script>
20 new Vue({
21 el: '#app',
22 data: {
23 message: 'Our king is dead! Send help!'
24 }
25 })
26 </script>
27 </html>
Como mostrado, a substituição de v-show por v-if funciona tão bem quanto
pensávamos. Vá em frente e tente fazer suas próprias experiências para ver como
funciona. A única diferença é que o elemento com o v-if não estará na DOM.
Template v-if
Se precisarmos alterar a visibilidade de vários elementos ao mesmo tempo, podemos
usar o v-if em um elemento chamado <template>. Em ocasiões em que usar div
Um Leque de Diretivas 17
ou span não parece ser apropriado, o elemento <template> pode ser usado como um
elemento invisível.
O <template> não será renderizado no resultado final.
1 <div id="app">
2 <template v-if="!message">
3 <h1>You must send a message for help!</h1>
4 <p>Dispatch a messenger immediately!</p>
5 <p>To nearby kingdom of Hearts!</p>
6 </template>
7 <textarea v-model="message"></textarea>
8 <button v-show="message">
9 Send word to allies for help!
10 </button>
11 <pre>
12 {{ $data }}
13 </pre>
14 </div>
Template v-if
Um Leque de Diretivas 18
Atenção
A diretiva v-show não suporta o elemento <template>.
3.3 v-else
Ao usar v-if você pode usar a diretiva v-else para indicar um bloco “else”,
como você já deve ter imaginado. Lembre-se que a diretiva v-else deve seguir
imediatamente a diretiva v-if - caso contrário não será reconhecida.
1 <html>
2 <head>
3 <title>Hello Vue</title>
4 </head>
5 <body>
6 <div id="app">
7 <h1 v-if="!message">You must send a message for help!</h1>
8 <h2 v-else>You have sent a message!</h2>
9 <textarea v-model="message"></textarea>
10 <button v-show="message">
11 Send word to allies for help!
12 </button>
13 <pre>
14 {{ $data }}
15 </pre>
16 </div>
17 </body>
18 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
19 "></script>
20 <script>
21 new Vue({
Um Leque de Diretivas 19
22 el: '#app',
23 data: {
24 message: 'Our king is dead! Send help!'
25 }
26 })
27 </script>
28 </html>
v-if in action
Um Leque de Diretivas 20
v-else in action
Apenas por causa do exemplo, usamos a tag h2 com um aviso diferente do anterior,
que será exibido condicionalmente. Se a mensagem não estiver presente, veremos a
tag h1
Se existir uma mensagem, veremos a tag h2 usando esta simples sintaxe do Vue: v-if
e v-else. Simples assim!
Atenção
A diretiva v-show não funciona mais com v-else no Vue 2.0.
Você pode notar que o uso do v-show em muitas situações pode causar um peso maior
e consequentemente um tempo maior de carregamento durante a renderização da
página.
Em comparação, v-if é uma condicional válida de acordo com o guia do Vue.js.
Código Fonte
Você pode encontrar estes exemplos no GitHub2 .
2
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter3
Um Leque de Diretivas 22
3.5 Tarefa
Após a tarefa anterior, você deverá expandí-la um pouco. O usuário agora informará
o seu gênero junto com o seu nome.
Se o usuário for masculino, então o cabeçalho deverá mostrar “Hello Mister
{{name}}”. Se o usuário for feminino, então o cabeçalho deverá mostrar “Hello Miss
{{name}}”.
Enquanto o gênero nao estiver definido, o usuário deverá ver esta mensagem “So
you can’t decide. Fine!”.
Dica
Um operador lógico pode ser usado para determinar um título
Saída
Solução sugerida
Você pode encontrar uma solução para este exercício aqui3 .
3
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/homework/chapter3.html
4. Renderização de Listas
No quarto capítulo deste livro, vamos aprender sobre a renderização de listas. Usando
as diretivas do Vue estaremos abordando o seguinte:
Informação
Bootstrap é o framework HTML/CSS/JS mais popular para o desenvolvi-
mento responsivo, compatível com mobile, na web.
1
http://getbootstrap.com/
2
https://www.bootstrapcdn.com/
Renderização de Listas 24
1 <div class="container">
2 ...
3 </div>
1 <div class="container-fluid">
2 ...
3 </div>
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Hello Bootstrap</title>
6 </head>
7 <body>
8 <div class="container">
9 <h1>Hello Bootstrap, sit next to Vue.</h1>
10 </div>
11 </body>
12 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
13 "></script>
14 <script type="text/javascript">
15 new Vue({
16 el: '.container'
17 })
18 </script>
19 </html>
Renderização de Listas 25
Observe que, desta vez, ao invés de usar o id app de um elemento html, estamos
apontando para a classe container na opção el da instância do Vue.
Dica
No exemplo acima usamos o elemento com a classe .container. Tome
cuidado: quando apontamos para uma classe de um elemento e esta classe
estiver presente em mais de um elemento, o Vue.js irá operar somente no
primeiro elemento encontrado daquela classe.
A propriedade el: pode ser um seletor CSS ou um elemento HTML. Não
é recomendado apontar o Vue para o elemento <html> ou <body>.
Renderização de Listas 26
4.2 v-for
Para fazer um loop através de cada item em uma matriz, usaremos a diretiva v-for.
Esta diretiva requer uma sintaxe especial como parâmetro do tipo item in Array
onde Array é o Array da fonte de dados e item é um apelido para o elemento do
Array que está sendo selecionado naquele laço específico.
Atenção
Se você está vindo do mundo php, você poderá notar que o v-for é similar
a função foreach do php. Mas cuidado, se você usar foreach($Array as
$value) perceberá que no Vue o v-for é exatamente o oposto, value in
Array.
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/\
4 bootstrap.min.css" rel="stylesheet">
5 <title>Hello Vue</title>
6 </head>
7 <body>
8 <div class="container">
9 <h1>The multiplication table of 4.</h1>
10 <ul class="list-group">
11 <li v-for="i in 11" class="list-group-item">
12 {{ i-1 }} times 4 equals {{ (i-1) * 4 }}.
Renderização de Listas 27
13 </li>
14 </ul>
15 </div>
16 </body>
17 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
18 "></script>
19 <script type="text/javascript">
20 new Vue({
21 el: '.container'
22 })
23 </script>
24 </html>
Tabuada do 4
Nota
Já que queremos exibir os valores da multiplicação do 4, repetimos o
modelo 11 vezes desde o primeiro valor, onde o primeiro valor de i é 0
stories: [
"I crashed my car today!",
"Yesterday, someone stole my bag!",
"Someone ate my chocolate...",
]
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Stories</title>
6 </head>
7 <body>
8 <div class="container">
9 <h1>Let's hear some stories!</h1>
10 <div>
11 <ul class="list-group">
12 <li v-for="story in stories" class="list-group-item">
13 Someone said "{{ story }}"
14 </li>
15 </ul>
16 </div>
17 <pre>
18 {{ $data }}
19 </pre>
20 </div>
21 </body>
22 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
23 "></script>
24 <script type="text/javascript">
25 new Vue({
26 el: '.container',
Renderização de Listas 30
27 data: {
28 stories: [
29 "I crashed my car today!",
30 "Yesterday, someone stole my bag!",
31 "Someone ate my chocolate...",
32 ]
33 }
34 })
35 </script>
36 </html>
Informação
Ambas as classes list-group e list-group-item são classes do Bootstrap.
Aqui você pode encontrar mais informações sobre estilos de lista3 .
3
http://getbootstrap.com/css/#type-lists
Renderização de Listas 31
Usando v-for é possível exibir as histórias em uma lista não ordenada. É realmente
muito simples.
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Stories</title>
6 </head>
7 <body>
8 <div class="container">
9 <h1>Let's hear some stories!</h1>
10 <div>
11 <ul class="list-group">
12 <li v-for="story in stories"
13 class="list-group-item"
14 >
15 {{ story.writer }} said "{{ story.plot }}"
16 </li>
17 </ul>
18 </div>
19 <pre>
20 {{ $data }}
21 </pre>
22 </div>
23 </body>
24 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
25 "></script>
26 <script type="text/javascript">
27 new Vue({
28 el: '.container',
29 data: {
30 stories: [
31 {
32 plot: "I crashed my car today!",
33 writer: "Alex"
34 },
35 {
Renderização de Listas 33
Além disso, quando você precisar exibir o índice do item atual, você pode usar a
propriedade especial index. Funciona assim:
<ul class="list-group">
<li v-for="(story, index) in stories"
class="list-group-item" >
{{index}} {{ story.writer }} said "{{ story.plot }}"
</li>
</ul>
Informação
Quando iteramos com um objeto, o index está entre 0 … n-1, onde n é o
número de propriedades do objeto.
Reestruturamos nossos dados para que sejam um único objeto com 3 atributos desta
vez: plot, writer e upvotes
<div class="container">
<h1>Let's hear some stories!</h1>
<ul class="list-group">
<li v-for="value in story" class="list-group-item">
{{ value }}
</li>
</ul>
</div>
new Vue({
el: '.container',
data: {
story: {
plot: "Someone ate my chocolate...",
writer: 'John',
upvotes: 47
}
}
})
Renderização de Listas 36
1 <div class="container">
2 <h1>Let's hear some stories!</h1>
3 <ul class="list-group">
4 <li v-for="(value, key, index) in story"
5 class="list-group-item"
6 >
7 {{index}} : {{key}} : {{value}}
8 </li>
9 </ul>
10 </div>
Como você pode ver no código acima, usamos key e index para incorporar os pares
key-value, bem como o index de cada item.
O resultado será:
Exemplo
Você pode encontrar estes exemplos no GitHub4 .
4
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter4
Renderização de Listas 38
4.5 Tarefa
Tendo em mente o que revisamos neste capítulo, para esta tarefa, crie um objeto com
seus atributos pessoais. Por atributos pessoais, quero dizer, o seu nome, peso, altura,
corOlhos e a sua comidaFavorita.
Usando v-for, itere através de cada propriedade e exiba-a no formato: index: key
= value.
Saída
Solução sugerida
Você pode encontrar uma sugestão de solução deste exercício aqui5 .
5
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/homework/chapter4.html
5. Interatividade
Neste capítulo vamos criar e expandir os exemplos anteriores e aprender coisas novas
sobre métodos, eventos e propriedades computadas. Criaremos alguns exemplos
usando diferentes abordagens.
É hora de ver como podemos implementar esta interatividade com o Vue para ter
uma pequena app, como uma calculadora, sendo bem executada de forma fácil.
O ponto aqui é que você sempre pode fazer algo quando um evento ocorrer. No Vue.js,
para escutar os eventos da DOM, você pode usar a diretiva v-on.
A diretiva v-on irá anexar o event listener no elemento. O tipo de evento é designado
pelo seu argumento, por exemplo v-on:keyup escuta o evento keyup.
Informação
O evento keyup ocorre quando o usuário solta uma tecla. Você pode
encontrar uma lista de eventos HTML aqui1 .
1
http://www.w3schools.com/tags/ref_eventattributes.asp
Interatividade 40
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Upvote</title>
6 </head>
7 <body>
8 <div class="container">
9 <button v-on:click="upvotes++">
10 Upvote! {{upvotes}}
11 </button>
12 </div>
13 </body>
14 <script type="text/javascript" src="https://cdnjs.cloudflare.com/aja\
15 x/libs/vue/2.0.1/vue.js"></script>
16 <script type="text/javascript">
17 new Vue({
18 el: '.container',
19 data: {
20 upvotes: 0
21 }
22 })
23 </script>
24 </html>
Interatividade 41
Contador de votos
Existe uma variável upvotes dentro do nosso data. Neste caso, estamos referenciando
o evento click e sua implementação está ao lado dele (inline), dentro das aspas
duplas. Cada vez que o botão é clicado, nós simplemesmente aumentamos o valor
da variável upvotes usando o incrementador upvotes++.
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Upvote</title>
6 </head>
7 <body>
8 <div class="container">
9 <button v-on:click="upvote">
10 Upvote! {{upvotes}}
11 </button>
12 </div>
13 </body>
14 <script type="text/javascript" src="https://cdnjs.cloudflare.com/aja\
15 x/libs/vue/2.0.1/vue.js"></script>
16 <script type="text/javascript">
17 new Vue({
18 el: '.container',
19 data: {
20 upvotes: 0
21 },
22 // define methods under the **`methods`** object
23 methods: {
24 upvote: function(){
25 // **`this`** inside methods points to the Vue instance
26 this.upvotes++;
27 }
28 }
29 })
30 </script>
31 </html>
Atenção
Event handlers são restritos para executar em apenas uma única decla-
ração.
Usando v-on:
<button v-on:click="upvote">
Upvote! {{upvotes}}
</button>
Usando @
<button @click="upvote">
Upvote! {{upvotes}}
</button>
1 <html>
2 <head>
3 <title>Calculator</title>
4 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/\
5 bootstrap.min.css" rel="stylesheet">
6 </head>
7 <body>
8 <div class="container">
9 <h1>Type 2 numbers and choose operation.</h1>
10 <form class="form-inline">
11 <!-- Notice here the special modifier 'number'
12 is passed in order to parse inputs as numbers.-->
13 <input v-model.number="a" class="form-control">
14 <select v-model="operator" class="form-control">
15 <option>+</option>
16 <option>-</option>
17 <option>*</option>
18 <option>/</option>
19 </select>
20 <!-- Notice here the special modifier 'number'
21 is passed in order to parse inputs as numbers.-->
22 <input v-model.number="b" class="form-control">
23 <button type="submit" @click="calculate"
24 class="btn btn-primary">
25 Calculate
26 </button>
27 </form>
28 <h2>Result: {{a}} {{operator}} {{b}} = {{c}}</h2>
29 <pre>
30 {{ $data }}
31 </pre>
32 </div>
33 </body>
34 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
35 "></script>
Interatividade 45
36 <script type="text/javascript">
37 new Vue({
38 el: '.container',
39 data: {
40 a: 1,
41 b: 2,
42 c: null,
43 operator: "+",
44 },
45 methods:{
46 calculate: function(){
47 switch (this.operator) {
48 case "+":
49 this.c = this.a + this.b
50 break;
51 case "-":
52 this.c = this.a - this.b
53 break;
54 case "*":
55 this.c = this.a * this.b
56 break;
57 case "/":
58 this.c = this.a / this.b
59 break;
60 }
61 }
62 },
63 });
64 </script>
65 </html>
Se você executar este código, peceberá que quando clicar no botão “calculate”, ao
invés do cálculo ser realizado, a página irá atualizar.
Isso faz sentido porque quando clicamos no botão “calculate”, o submit do formulário
será realizado.
Interatividade 46
Para prevenir o envio do formulário, temos que cancelar a ação padrão do evento on-
submit. O jeito mais comum de se fazer isso é chamar o método event.preventDefault()
dentro do método calculate.
Então, o método fica assim:
calculate: function(event){
event.preventDefault();
switch (this.operator) {
case "+":
this.c = this.a + this.b
break;
case "-":
this.c = this.a - this.b
break;
case "*":
this.c = this.a * this.b
break;
case "/":
this.c = this.a / this.b
break;
}
}
Interatividade 47
Embora possamos fazer isso facilmente dentro dos métodos, seria melhor se os
métodos pudessem abstrair e ignorar essa funcionalidade e focar apenas na sua
lógica, evitando assim ter que cancelar um evento que é da DOM.
O Vue.js fornece quatro modificadores de eventos para v-on, a fim de prevenir o
comportamento padrão destes eventos:
1. .prevent
2. .stop
3. .capture
4. .self
para:
Interatividade 48
1 <!-- the submit event will no longer reload the page -->
2 <button type="submit" @click.prevent="calculate">Calculate</button>
Nota
.capture e .self são raramente usados, por isso não vamos abordá-los.
Se você estiver interessado em estudar mais sobre estes eventos, dê uma
olhada neste tutorial2 .
Caso não deseje usar códigos, existe um alias para algumas teclas mais usadas:
• enter
• tab
• delete
• esc
• space
• up
• down
• left
• right
Então, para executar o método quando a tecla enter é pressionada, podemos fazer o
seguinte:
2
http://www.quirksmode.org/js/events_order.html
Interatividade 49
Dica
Quando tiver um formulário com muitas caixas de texto e botões, e for
necessário cancelar o evento de envio, você pode modificar o evento
submit diretamente na tag form.
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Hello Vue</title>
6 </head>
7 <body>
8 <div class="container">
9 a={{ a }}, b={{ b }}
10 <pre>
11 {{ $data }}
12 </pre>
13 </div>
14 </body>
15 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
16 "></script>
17 <script type="text/javascript">
18 new Vue({
19 el: '.container',
20 data: {
21 a: 1,
22 },
23 computed: {
24 // a computed getter
25 b: function () {
26 // **`this`** points to the Vue instance
27 return this.a + 1
28 }
29 }
30 });
31 </script>
32 </html>
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Hello Vue</title>
6 </head>
7 <body>
8 <div class="container">
9 a={{ a }}, b={{ b }}
10 <input v-model="a">
11 <pre>
12 {{ $data }}
13 </pre>
14 </div>
15 </body>
16 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
17 "></script>
18 <script type="text/javascript">
19 new Vue({
20 el: '.container',
21 data: {
22 a: 1,
23 },
24 computed: {
25 // a computed getter
26 b: function () {
27 // **`this`** points to the vm instance
28 return this.a + 1
29 }
30 }
31 });
32 </script>
33 </html>
Este exemplo é semelhante ao segundo, mas com uma diferença. Inserimos uma caixa
de texto ligada à variável a. O valor digitado irá alterar o valor de a e imediatamente
Interatividade 52
new Vue({
el: '.container',
data: {
a: 1,
},
computed: {
b: function () {
return parseFloat(this.a) + 1
}
}
});
Uma outra solução é usar <input type="number">, o que força a caixa de texto a
conter somente números.
Mas existe uma outra maneira. Com o Vue.js, dentro da caixa de texto que precisa
ser número, você pode usar o modificador especial .number.
Interatividade 53
<body>
<div class="container">
a={{ a }}, b={{ b }}
<input v-model.number="a">
<pre>
{{ $data }}
</pre>
</div>
</body>
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/\
4 bootstrap.min.css" rel="stylesheet">
5 <title>Hello Vue</title>
6 </head>
7 <body>
8 <div class="container">
9 <h1>Enter 2 numbers to calculate their sum.</h1>
10 <form class="form-inline">
11 <input v-model.number="a" class="form-control">
12 +
13 <input v-model.number="b" class="form-control">
14 </form>
15 <h2>Result: {{a}} + {{b}} = {{c}}</h2>
16 <pre> {{ $data }} </pre>
17 </div>
18 </body>
Interatividade 54
19 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
20 "></script>
21 <script type="text/javascript">
22 new Vue({
23 el: '.container',
24 data: {
25 a: 1,
26 b: 2
27 },
28 computed: {
29 c: function () {
30 return this.a + this.b
31 }
32 }
33 });
34 </script>
35 </html>
O código inicial está pronto. Se o usuário digitar 2 números irá obter a sua soma. A
calculadora faz mais operações, então vamos continuar programando.
Como o código HTML será o mesmo do exemplo anterior (exceto pelo botão extra),
vamos exibir somente a parte Javascript.
1 new Vue({
2 el: '.container',
3 data: {
4 a: 1,
5 b: 2,
6 operator: "+",
7 },
8 computed: {
9 c: function () {
10 switch (this.operator) {
11 case "+":
12 return this.a + this.b
Interatividade 55
13 break;
14 case "-":
15 return this.a - this.b
16 break;
17 case "*":
18 return this.a * this.b
19 break;
20 case "/":
21 return this.a / this.b
22 break;
23 }
24 }
25 },
26 });
A calculadora está pronta para uso. A única coisa que tínhamos que fazer era mover
o que estava dentro do método calculate para a propriedade computada chamada
de c.
Sempre que se altera o valor de a ou b o resultado é atualizado em tempo real. Nós
não precisamos de botões, eventos ou outra coisa. Não é incrível?
Nota
Observe que precisamos ter uma abordagem com if para evitar erros
de divisão. Mas, já existe um resultado para este comportamento. Se o
usuário digitar 1/0 o resultado será automaticamente infinito, e o texto
apresentado no resultado será “not a number”.
Interatividade 56
Código Fonte
Você pode encontrar estes exemplos no GitHub3 .
3
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter5
Interatividade 57
5.5 Tarefa
Agora que você tem uma compreensão básica do Vue.js (eventos, métodos, proprie-
dades computadas, etc.), vamos criar algo um pouco mais desafiador.
Comece criando um Array de candidatos a prefeitos (mayors). Cada candidato tem
um nome (name) e um número de votos (votes). Use um botão para aumentar
a quantidade de votos de cada candidato. Use uma propriedade computada para
determinar quem é o atual prefeito, exibindo o seu nome. Por último, crie uma caixa
de texto. Quando ela estiver selecionada, se a tecla delete for pressionada, a eleição
será reiniciada, ou seja, todos terão 0 votos.
Dica
Os métodos do Javascript sort() e map() podem ser muito úteis!
Interatividade 58
Resultado esperado
Solução sugerida
Você pode encontrar uma sugestão de solução deste exercício aqui4 .
4
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/homework/chapter5.html
6. Filtros
Nos dois capítulos anteriores, analisamos a renderização em listas (list rendering), os
métodos e propriedades computadas. É hora de fazer alguns exemplos usando tudo
o que foi aprendido até agora. Neste capítulo, vamos abordar o seguinte:
O plano é fazer alguns exemplos semelhantes aos que foram feitos antes, combinando
algumas técnicas que já vimos.
Informação
No Vue 2.0, os filtros não podem ser mais usados dentro do v-for. Filtros
podem ser usados somente dentro de ({{ }}). A equipe do Vue sugere
mover a lógica de filtros para o JavaScript, então ela pode ser reusada ao
longo do seu componente.
Filtros 60
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>User Stories</title>
6 </head>
7 <body>
8 <div class="container">
9 <h1>Let's hear some stories!</h1>
10 <div>
11 <h3>Alex's stories</h3>
12 <ul class="list-group">
13 <li v-for="story in storiesBy('Alex')"
14 class="list-group-item"
15 >
16 {{ story.writer }} said "{{ story.plot }}"
17 </li>
18 </ul>
19 <h3>John's stories</h3>
20 <ul class="list-group">
21 <li v-for="story in storiesBy('John')"
22 class="list-group-item"
23 >
24 {{ story.writer }} said "{{ story.plot }}"
25 </li>
26 </ul>
27 </div>
28 <pre>
29 {{ $data }}
30 </pre>
31 </div>
32 </body>
33 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
34 "></script>
35 <script type="text/javascript">
Filtros 61
36 new Vue({
37 el: '.container',
38 data: {
39 stories: [
40 {
41 plot: "I crashed my car today!",
42 writer: "Alex"
43 },
44 {
45 plot: "Yesterday, someone stole my bag!",
46 writer: "John"
47 },
48 {
49 plot: "Someone ate my chocolate...",
50 writer: "John"
51 },
52 {
53 plot: "I ate someone's chocolate!",
54 writer: "Alex"
55 },
56 ]
57 },
58 methods:
59 {
60 // a method which filters the stories depending on the writt\
61 er
62 storiesBy: function (writer) {
63 return this.stories.filter(function (story) {
64 return story.writer === writer
65 })
66 },
67 }
68 })
69 </script>
70 </html>
Filtros 62
Informação
Dentro do método storiesBy, usamos a função nativa filter1 . A função
filter() cria um novo Array com todos os elementos que passaram no
teste de acordo com o retorno provido (neste caso, story.writer ===
writer).
1
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Filtros 63
Nota
Como podemos notar, a tag li ficou muito grande, então a dividimos em
mais linhas. O resultado atual é o mesmo do uso de filtros no for, no Vue
1.x.
new Vue({
el: '.container',
data: {
stories: [
{
plot: "I crashed my car today!",
writer: "Alex",
upvotes: 28
},
{
plot: "Yesterday, someone stole my bag!",
writer: "John",
upvotes: 8
},
{
Filtros 64
<body>
<div class="container">
<h1>Let's hear some famous stories! ({{famous.length}})</h1>
<ul class="list-group">
<li v-for="story in famous"
class="list-group-item"
>
{{ story.writer }} said "{{ story.plot }}"
and upvoted {{ story.upvotes }} times.
</li>
</ul>
</div>
</body>
Filtros 65
.
É isso! Nós filtramos o nosso Array usando propriedades computadas. Você notou o
quanto foi fácil obter o valor do número de histórias famosas próximo ao cabeçalho,
usando {{famous.length}}?
Agora vamos implementar uma busca bem básica, porém impressionante. Quando o
usuário digita algo, nós podemos adivinhar qual é a história, em tempo real. Vamos
usar uma caixa de texto input, ligada a uma variável chamada query, podemos então
dinamicamente, filtrar nossas histórias.
1 <div class="container">
2 <h1>Lets hear some stories!</h1>
3 <div>
4 ...
5 <div class="form-group">
6 <label for="query">
7 What are you looking for?
8 </label>
9 <input v-model="query" class="form-control">
10 </div>
11 <h3>Search results:</h3>
12 <ul class="list-group">
13 <li v-for="story in search"
Filtros 66
14 class="list-group-item"
15 >
16 {{ story.writer }} said "{{ story.plot }}"
17 </li>
18 </ul>
19 </div>
20 </div>
Então vamos criar uma propriedade computada chamada search. Juntamente com
a função filter, usaremos a função Javascript includes2 , que determina se uma
sequência de caracteres pode ser encontrada dentro de outra sequência de caracteres.
1 new Vue({
2 el: '.container',
3 data: {
4 stories: [...],
5 query: ' '
6 },
7 methods:{
8 storiesBy: function (writer) {
9 return this.stories.filter(function (story) {
10 return story.writer === writer
11 })
12 }
13 },
14 computed: {
15 search: function () {
16 var query = this.query
17 return this.stories.filter(function (story) {
18 return story.plot.includes(query)
19 })
20 }
21 }
22 })
2
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
Filtros 67
Buscar histórias.
Filtros 68
Não é incrível??
Filtros 69
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Famous Stories</title>
6 </head>
7 <body>
8 <div class="container">
9 <h1>Let's hear some stories!</h1>
10 <ul class="list-group">
11 <li v-for="story in orderedStories"
12 class="list-group-item"
13 >
14 {{ story.writer }} said "{{ story.plot }}"
15 and upvoted {{ story.upvotes }} times.
16 </li>
17 </ul>
18 <pre>
19 {{ $data }}
20 </pre>
21 </div>
22 </body>
23 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
24 "></script>
3
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Filtros 70
25 <script type="text/javascript">
26 new Vue({
27 el: '.container',
28 data: {
29 stories: [...]
30 },
31 computed: {
32 orderedStories: function () {
33 return this.stories.sort(function(a, b){
34 return a.upvotes - b.upvotes;
35 })
36 }
37 }
38 })
39 </script>
40 </html>
Hmmm, o Array está ordenado, mas isso não é o que esperávamos. Precisamos da
história mais famosa em primeiro lugar.
Filtros 71
Para mudar a ordem do Array, temos que dar uma olhada na função sort. Na função
sort(compareFunction), se compareFunction é fornecido, os elementos do Array são
ordenados de acordo com o retorno de compareFunction. Se a e b são dois elementos
a serem comparados, então:
compareFunction
function(a, b){
return a.upvotes - b.upvotes;
}
1 <div class="container">
2 <h1>Let's hear some stories!</h1>
3 <ul class="list-group">
4 <li v-for="story in orderedStories"
5 class="list-group-item"
6 >
7 {{ story.writer }} said "{{ story.plot }}"
8 and upvoted {{ story.upvotes }} times.
9 </li>
10 </ul>
11 <button @click="order = order * -1">Reverse Order</button>
12 <pre>
Filtros 72
13 {{ $data }}
14 </pre>
15 </div>
1 new Vue({
2 el: '.container',
3 data: {
4 stories: [...],
5 order : -1
6 },
7 computed: {
8 orderedStories: function () {
9 var order = this.order;
10 return this.stories.sort(function(a, b) {
11 return (a.upvotes - b.upvotes) * order;
12 })
13 }
14 }
15 })
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>The Gotham Gazette</title>
6 </head>
7 <body>
8 <div class="container">
9 <h1>Real identities of Super Heroes!</h1>
10 <ul class="list-group">
11 <li v-for="hero in heroes"
12 class="list-group-item"
13 >
14 {{ hero | snitch }}
15 </li>
16 </ul>
17 </div>
18 </body>
19 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
Filtros 75
20 ">
21 </script>
22 <script>
23 Vue.filter('snitch', function (hero) {
24 return hero.secretId + ' is '
25 + hero.firstname + ' '
26 + hero.lastname + ' in real life!'
27 })
28
29 new Vue({
30 el: '.container',
31 data: {
32 heroes: [
33 { firstname: 'Bruce', lastname: 'Wayne', secretId: 'Bat\
34 man'},
35 { firstname: 'Clark', lastname: 'Kent', secretId: 'Super\
36 man'},
37 { firstname: 'Jay', lastname: 'Garrick', secretId: 'Flas\
38 h'},
39 { firstname: 'Peter', lastname: 'Parker', secretId: 'Spi\
40 der-Man'}
41 ]
42 }
43 })
44 </script>
45 </html>
Filtros 76
4
https://lodash.com
5
http://underscorejs.org/
6
https://sugarjs.com/
Filtros 77
Sintaxe
A sintaxe do orderBy no Lodash é:
var kids = [
{ name: 'Stan', strength: 70, intelligence: 70},
{ name: 'Kyle', strength: 40, intelligence: 80},
{ name: 'Eric', strength: 45, intelligence: 80},
{ name: 'Kenny', strength: 100, intelligence: 70}
]
E executar:
var kids = [
{ name: 'Kyle', strength: 40, intelligence: 80},
{ name: 'Eric', strength: 45, intelligence: 80},
{ name: 'Stan', strength: 70, intelligence: 70},
{ name: 'Kenny', strength: 100, intelligence: 70}
]
Filtros 78
computed: {
orderedStories: function () {
var order = this.order
return _.orderBy(this.stories, 'upvotes')
}
}
O código acima funciona, mas se o argumento order nao for repassado, o Array
será ordenado de forma crescente. Além disso, deve ser possível alterar a ordem
da matriz através do clique do botão, como fizemos anteriormente. Para fazer
isso dinamicamente, podemos informar a propriedade order : 'desc' e criar um
método que irá alterar o seu valor.
methods: {
reverseOrder: function () {
this.order = (this.order === 'desc') ? 'asc' : 'desc'
}
},
computed: {
orderedStories: function () {
var order = this.order
return _.orderBy(this.stories, 'upvotes', [order])
}
}
<button v-on:click="reverseOrder">
Dica
Ao usar uma biblioteca externa para filtrar/ordenar dados, você pode
iterar o resultado do Array sem usar qualquer tipo de propriedade com-
putada.
Podemos atualizar este exemplo para que possamos ter uma ideia. Nosso
HTML que processa o Array ordenado seria:
<div class="container">
<h1>Let's hear some stories!</h1>
<ul class="list-group">
<li v-for="story in _.orderBy(stories, ['upvotes'], ['desc'])">
{{ story.writer }} said "{{ story.plot }}"
and upvoted {{ story.upvotes }} times.
</li>
</ul>
</div>
Exemplos
Você pode encontrar estes exemplos no GitHub7 .
7
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter6
Filtros 80
6.5 Tarefa
Para o exercício deste capítulo você deve fazer o seguinte. Comece criando um Array
de pessoas (people ). Cada pessoa (person) tem os atributos name e age. Usando o que
você acabou de aprender, tente renderizar o Array em uma lista e ordenar pela idade
(age ). Depois disso, crie uma segunda lista abaixo da primeira e crie uma propriedade
computada chamada old, a qual retornará pessoas que tem mais de 65 anos.
Fique a vontade em preencher o Array com os seus próprios dados. Lembre-se de
adicionar pessoas com mais de 65 anos.
Dica
Você vai precisar usar .filter.
Saída
Filtros 81
Solução sugerida
Você pode encontrar uma sugestão de solução deste exercício aqui8 .
8
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/homework/chapter6.html
7. Componentes
7.1 O que são Componentes?
Componentes são umas das funcionalidades mais poderosas do Vue.js. Eles te ajudam
a estender elementos básicos do HTML para que se possa deixá-los reutilizáveis.
Em outras palavras, os componentes são elementos personalizados que o compilador
Vue.js atribui um comportamento específico. Em alguns casos, eles também podem
parecer como um elemento HTML nativo estendido com o atributo especial is.
É uma maneira realmente inteligente e poderosa de estender o HTML para fazer no-
vas funcionalidades. Neste capítulo, vamos começar com um exemplo extremamente
simples. Em seguida, vamos ver como os componentes podem nos ajudar a melhorar
o código que criamos nos capítulos anteriores.
1 Vue.component('story', {
2 template: '<h1>My horse is amazing!</h1>'
3 });
Agora que nós registramos o componente, nós podemos fazer uso dele. Iremos
adicionar o elemento <story> dentro do HTML para exibir a história.
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/\
4 bootstrap.min.css" rel="stylesheet">
5 <title>Hello Vue</title>
6 </head>
7 <body>
8 <div class="container">
9 <story></story>
10 </div>
11 </body>
12 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
13 "></script>
14 <script type="text/javascript">
15 Vue.component('story', {
16 template: '<h1>My horse is amazing!</h1>'
17 });
18
19 new Vue({
20 el: '.container'
21 })
22 </script>
23 </html>
Nota
Observe que você pode dar qualquer nome ao seu componente personali-
zado, mas geralmente é recomendado que você use um único nome para
evitar conflitos com as atuais tags que podem ser introduzidas em algum
momento futuro.
Componentes 84
<body>
<div class="container">
<story></story>
<story></story>
<story></story>
</div>
</body>
Exibindo o componente
Componentes 85
7.3 Templates
Há mais uma maneira de declarar um modelo para o componente. O template criado
até agora, pode rapidamente ficar confuso, a medida que cresce.
Outra maneira é criar uma tag script com o tipo definido para text/template e
com o id de valor story-template. Para usar este template precisamos referenciar o
template na tag script.
<script type="text/javascript">
Vue.component('story', {
template: "#story-template"
});
</script>
Informação
O valor text/template não é compreendido pelo navegador, então ele o
ignorará.
Meu jeito favorito de definir um template (e uma das formas que faço nos exemplos
do livro) é criar uma tag template e dar a ela um id. Ela pode ser usada como
referência, como já fizemos antes. Usando esta técnica o componente possui um
código semelhante a este:
Componentes 86
<template id="story-template">
<h1>My horse is amazing!</h1>
</template>
<script type="text/javascript">
Vue.component('story', {
template: "#story-template"
});
</script>
7.4 Propriedades
Vejamos agora como podemos usar várias instâncias do componente story para
exibir uma lista de histórias.
Temos que atualizar o template para que não mostre a mesma história, mas um
pedaço de história qualquer, que chamaremos de plot.
<template id="story-template">
<h1>{{ plot }}</h1>
</template>
Temos que atualizar o componente para que possa usar esta propriedade. Para fazer
isso, iremos adicionar uma nova propriedade plot ao atributo props do componente.
Vue.component('story', {
props: ['plot'],
template: "#story-template"
});
Agora nós podemos passar o plot toda vez que usarmos o elemento <story>.
Componentes 87
<div class="container">
<story plot="My horse is amazing."></story>
<story plot="Narwhals invented Shish Kebab."></story>
<story plot="The dark side of the Force is stronger."></stor\
y>
</div>
Atenção
Atributos no HTML são case insensitive. Quando usamos camelCase nas
propriedades dos nomes dos atributos, você precisa usar o kebab-case
(delimitado por hífen).
Então, camelCase no JavaScript, kebab-case no HTML. Por exem-
plo, para props: ['isUser'], o atributo HTML deverá ser <story
is-user="true"></story>.
Como você provavelmente deve imaginar, um componente pode ter mais de uma
propriedade. Por exemplo, se precisamos exibir o escritor para cada história, podemos
criar o atributo writer.
Componentes 88
Se você tem muitas propriedades e os seus elementos estão ficando confusos, você
pode passar um objeto que possui estas propriedades.
Vamos refatorar o nosso exemplo mais uma vez.
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/\
4 bootstrap.min.css" rel="stylesheet">
5 <title>Awesome Stories</title>
6 </head>
7 <body>
8 <div class="container">
9 <story v-bind:story="{plot: 'My horse is amazing.', writer: \
10 'Mr. Weebl'}">
11 </story>
12 <story v-bind:story="{plot: 'Narwhals invented Shish Kebab.'\
13 , writer: 'Mr. Weebl'}"
14 >
15 </story>
16 <story v-bind:story="{plot: 'The dark side of the Force is s\
17 tronger.', writer: 'Darth Vader'}"
18 >
19 </story>
20 </div>
21 <template id="story-template">
22 <h1>{{ story.writer }} said "{{ story.plot }}"</h1>
23 </template>
24 </body>
25 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
26 "></script>
27 <script type="text/javascript">
28 Vue.component('story', {
29 props: ['story'],
Componentes 89
30 template: "#story-template"
31 });
32
33 new Vue({
34 el: '.container'
35 })
36 </script>
37 </html>
Informação
v-bind é usado para dinamicamente ligar um ou mais atributos ou, a
propriedade prop a uma expressão.
Como a propriedade story não é uma string, e sim um objeto javascript,
em vez de story="..." usamos v-bind:story="..." para ligar a propri-
edade story ao objeto repassado.
O atalho para v-bind é :, então a partir de agora vamos usá-lo assim:
:story="...".
7.5 Reutilização
Vamos dar uma olhada novamente no exemplo de Resultados Filtrados. Suponha
que desta vez, nós iremos pegar os dados da variável stories através de uma API
externa, por uma chamada http. Os desenvolvedores da API decidiram renomear a
propriedade plot para body. Então, nós temos que alterar o nosso código para realizar
as mudanças necessárias.
Informação
Mais adiante neste livro, vamos abordar como podemos fazer requisições
web usando o Vue.
Componentes 90
1 <div class="container">
2 <h1>Lets hear some stories!</h1>
3 <div>
4 <h3>Alex's stories</h3>
5 <ul class="list-group">
6 <li v-for="story in storiesBy('Alex')"
7 class="list-group-item"
8 >
9 {{ story.writer }} said "{{ story.plot }}"
10 {{ story.writer }} said "{{ story.body }}"
11 </li>
12 </ul>
13 <h3>John's stories</h3>
14 <ul class="list-group">
15 <li v-for="story in storiesBy('John')"
16 class="list-group-item"
17 >
18 {{ story.writer }} said "{{ story.plot }}"
19 {{ story.writer }} said "{{ story.body }}"
20 </li>
21 </ul>
22 <div class="form-group">
23 <label for="query">
24 What are you looking for?
25 </label>
26 <input v-model="query" class="form-control">
27 </div>
28 <h3>Search results:</h3>
29 <ul class="list-group">
30 <li vv-for="story in search"
31 class="list-group-item"
32 >
33 {{ story.writer }} said "{{ story.plot }}"
34 {{ story.writer }} said "{{ story.body }}"
35 </li>
Componentes 91
36 </ul>
37 </div>
38 </div>
Nota
Neste exemplo em particular, o syntax highlighting está desligado.
Como você deve ter notado, tivemos que fazer exatamente a mesma mudança 3 vezes
e eu não sei quanto a você, mas eu odeio ter que fazer repetições. Pode não parecer
um grande problema, mas imagine se fossem em 100 lugares diferentes. O que você
faria?
Felizmente, o Vue fornece uma solução para este tipo de situação, e esta solução se
chama Componente.
Dica
Sempre que você se encontrar com uma repetição de código de alguma
funcionalidade, a maneira mais eficiente de lidar com ela é criando um
Componente.
<div class="container">
<h1>Lets hear some stories!</h1>
<div>
<h3>Alex's stories</h3>
<ul class="list-group">
<story v-for="story in storiesBy('Alex')"
:story="story"></story>
</ul>
<h3>John's stories</h3>
<ul class="list-group">
<story v-for="story in storiesBy('John')"
:story="story"></story>
</ul>
<div class="form-group">
<label for="query">What are you looking for?</label>
<input v-model="query" class="form-control">
</div>
<h3>Search results:</h3>
<ul class="list-group">
<story v-for="story in search"
:story="story"></story>
</ul>
</div>
</div>
Aviso do Vue
<template id="story-template">
<li class="list-group-item">
{{ story.writer }} said "{{ story.body }}"
</li>
</template>
1 Vue.component('story', {
2 props: ['story'],
3 template: '#story-template'
4 });
Se você executar o código acima, verá que tudo funciona da mesma forma, mas desta
vez com o uso de um componente.
Muito legal, hein?
Componentes 94
7.6 Completando
Usando o que aprendemos até agora, somos capazes de construir algo um pouco
mais complexo. Baseado no exemplo da estrutura acima, vamos criar um sistema de
votação para as nossas stories, e adicionar uma funcionalidade de favoritos. Para
fazermos isso, vamos usar métodos, diretivas e, claro, componentes.
Vamos começar com a configuração inicial da história.
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Hello Vue</title>
6 </head>
7 <body>
8 <div id="app">
9 <div class="container">
10 <h1>Let's hear some stories!</h1>
11 <ul class="list-group">
12 <story v-for="story in stories" :story="story"></story>
13 </ul>
14 <pre>{{ $data }}</pre>
15 </div>
16 </div>
17 <template id="story-template">
18 <li class="list-group-item">
19 {{ story.writer }} said "{{ story.plot }}"
20 </li>
21 </template>
22 </body>
23 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
24 "></script>
25 <script type="text/javascript">
26 Vue.component('story', {
27 template: "#story-template",
Componentes 95
28 props: ['story'],
29 });
30
31 new Vue({
32 el: '#app',
33 data: {
34 stories: [
35 {
36 plot: 'My horse is amazing.',
37 writer: 'Mr. Weebl',
38 },
39 {
40 plot: 'Narwhals invented Shish Kebab.',
41 writer: 'Mr. Weebl',
42 },
43 {
44 plot: 'The dark side of the Force is stronger.',
45 writer: 'Darth Vader',
46 },
47 {
48 plot: 'One does not simply walk into Mordor',
49 writer: 'Boromir',
50 },
51 ]
52 }
53 })
54 </script>
55 </html>
<template id="story-template">
<li class="list-group-item">
{{ story.writer }} said "{{ story.plot }}".
Story upvotes {{ story.upvotes }}.
<button v-show="!story.voted" @click="upvote"
class="btn btn-default"
>
Upvote
</button>
</li>
</template>
Vue.component('story', {
template: "#story-template",
props: ['story'],
methods:{
upvote: function(){
this.story.upvotes += 1;
this.story.voted = true;
},
}
});
new Vue({
el: '#app',
data: {
stories: [
{
plot: 'My horse is amazing.',
writer: 'Mr. Weebl',
upvotes: 28,
voted: false,
},
{
plot: 'Narwhals invented Shish Kebab.',
Componentes 97
Nós implementamos, com o uso de métodos, o sistema de votação. Está bom até
agora, então vamos continuar com a parte dos favoritos. Nós precisamos que o
usuário escolha uma história para ser sua favorita. A primeira coisa que vem em
minha mente é adicionar um novo objeto, inicialmente vazio, chamado favorite e
sempre que o usuário escolher a história favorita, atualizar a variável favorite.
Esta é a forma pela qual verificamos se a história é a historia favorita.
<template id="story-template">
<li class="list-group-item">
{{ story.writer }} said "{{ story.plot }}".
Story upvotes {{ story.upvotes }}.
<button v-show="!story.voted" @click="upvote"
class="btn btn-default">
Upvote
</button>
<button v-show="!isFavorite" @click="setFavorite"
Componentes 99
class="btn btn-primary">
Favorite
</button>
<span v-show="isFavorite"
class="glyphicon glyphicon-star pull-right" aria-hidden="tru\
e">
</span>
</li>
</template>
Vue.component('story', {
template: "#story-template",
props: ['story'],
methods:{
upvote: function(){
this.story.upvotes += 1;
this.story.voted = true;
},
setFavorite: function(){
this.favorite = this.story;
},
},
computed:{
isFavorite: function(){
return this.story == this.favorite;
},
}
});
new Vue({
el: '#app',
data: {
stories: [
...
],
Componentes 100
favorite: {}
}
})
Se você tentar executar o código acima, perceberá que não irá funcionar da forma
que deseja. Sempre que tentar favoritar uma história, a variável favorite dentro do
objeto data permanece nula.
Parece que o nosso componente story é nulo para atualizar o objeto favorite,
então vamos passar cada história e adicionar a variável favorite às propriedades
do componente.
<ul class="list-group">
<story v-for="story in stories"
:story="story"
:favorite="favorite">
</story>
</ul>
Vue.component('story', {
...
props: ['story', 'favorite'],
...
});
Componentes 101
Exemplos
Você pode encontrar estes exemplos no GitHub1 .
1
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter7
Componentes 102
7.7 Tarefa
Crie um Array de planetas. Cada planeta deve ter as propriedades name e number of
visits. Você pode escolher viajar para qualquer planeta, mas está limitado a 3 visitas
até o combustível terminar.
Você deve ter um componente Planet com seus métodos e propriedades.
Quando renderizado, cada planeta deve exibir:
• o seu nome
• o número de visitas
• o botão Visit (Se o número máximo de visitas não for atingido)
• um ícone para dizer se o planeta foi visitado pelo menos uma vez
Saída esperada
Componentes 103
Solução sugerida
Você pode encontrar uma sugestão de solução deste exercício here2 .
2
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/homework/chapter7.html
8. Eventos Customizados
Algumas vezes é necessário disparar um evento customizado. Para fazer isso,
podemos usar alguns métodos que estão disponíveis na instância do Vue. A instância
do Vue implementa a seguinte interface1 .
Isto significa que você pode:
Pode também:
1
http://vuejs.org/api/#Instance-Methods-Events
Eventos Customizados 105
1 <html>
2 <head>
3 <title>Emit and Listen</title>
4 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/cs\
5 s/bootstrap.min.css" rel="stylesheet">
6 </head>
7 <body>
8 <div class="container text-center">
9 <p style="font-size: 140px;">
10 {{ votes }}
11 </p>
12 <button class="btn btn-primary" @click="vote">Vote</button>
13 </div>
14 </body>
15 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vu\
16 e.js"></script>
17 <script type="text/javascript">
18 new Vue({
19 el: '.container',
20 data: {
21 votes: 0
22 },
23 methods:
24 {
25 vote: function (writer) {
26 this.$emit('voted')
27 },
28 },
29 created () {
30 this.$on('voted', function(button) {
31 this.votes++
32 })
33 }
34 })
35 </script>
Eventos Customizados 106
36 </html>
Saída do Exemplo
Não é preciso saber toda a sequência e todos os eventos, mas é bom saber que eles
existem. Se você precisar aprender mais sobre esse ciclo de eventos, dê uma olhada
na API2 .
2
http://vuejs.org/api/#Options-Lifecycle-Hooks
Eventos Customizados 108
Food Component
Vue.component('food', {
template: '#food',
props: ['name'],
methods: {
vote: function () {
this.$emit('voted')
}
},
})
Parent Component
new Vue({
el: '.container',
data: {
votes: 0
},
methods:
{
countVote: function () {
this.votes++
},
}
})
Eventos Customizados 109
1 <html>
2 <head>
3 <title>Food Battle</title>
4 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bo\
5 otstrap.min.css" rel="stylesheet">
6 </head>
7 <body>
8 <div class="container text-center">
9 <p style="font-size: 140px;">
10 {{ votes }}
11 </p>
12
13 <div class="row">
14 <food @voted="countVote" name="Cheeseburger"></food>
15 <food @voted="countVote" name="Double Bacon Burger"></food>
16 <food @voted="countVote" name="Rodeo Burger"></food>
17 </div>
18 </div>
19
20 </body>
21 <template id="food">
22 <div class="text-center col-lg-4">
23 <p style="font-size: 40px;">
24 {{ votes }}
25 </p>
26 <button class="btn btn-default" @click="vote">{{ name }}</button>
27 </div>
28 </template>
29 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
30 "></script>
31 <script type="text/javascript">
32 var bus = new Vue()
33
34 Vue.component('food', {
35 template: '#food',
Eventos Customizados 111
36 props: ['name'],
37 data: function () {
38 return {
39 votes: 0
40 }
41 },
42 methods: {
43 vote: function () {
44 this.votes++
45 this.$emit('voted')
46 }
47 }
48 })
49 new Vue({
50 el: '.container',
51 data: {
52 votes: 0
53 },
54 methods:
55 {
56 countVote: function () {
57 this.votes++
58 }
59 }
60 })
61 </script>
62 </html>
Eventos Customizados 112
Nada de novo até agora. Para deixar a aplicação um pouco mais complexa, podemos
adicionar um log para a votação. O log irá atualizar toda vez que alguém votar em
uma comida. Temos que atualizar o componente filho, para passar o nome da comida
ao emitir o evento voted.
Informação
A função $emit tem como primeiro argumento o nome do evento, e os
demais são parâmetros adicionais à função de callback. Por exemplo:
vm.$emit('voted', 'Alex', 'Sunday', 'Bob Ross')
Temos duas opções para acessar o nome da comida. Uma óbvia, é a propriedade name
do componente. A segunda, é acessar o elemento que disparou o evento e encontrar
o conteúdo de texto contido nele. Vamos pela segunda opção.
Podemos logar a variável event ao console, dentro do método vote, para descobrir
como podemos acessar o elemento clicado.
Eventos Customizados 113
Food Component
Vue.component('food', {
...
methods: {
vote: function (event) {
console.log(event)
this.votes++
this.$emit('voted')
}
}
})
event.srcElement
Se estiver seguindo o código, verá que temos acesso ao elemento clicado através do
atributo event.srcElement. O nome pode ser encontrado em ambas as propriedades:
event.srcElement.outerText e event.srcElement.textContent.
Food Component
Vue.component('food', {
...
methods: {
vote: function () {
this.votes++
this.$emit('voted', event.srcElement.textContent)
}
}
})
new Vue({
el: '.container',
data: {
votes: 0,
log: []
},
methods:
{
countVote: function (food) {
this.votes++
this.log.push(food + ' received a vote.')
}
}
})
<h1>Log:</h1>
<ul class="list-group">
<li class="list-group-item" v-for="vote in log"> {{ vote }} </li>
</ul>
Log de Votos
HTML
1 <body>
2 <div class="container text-center">
3 <h1>Food Battle</h1>
4 <p style="font-size: 140px;">
5 {{ votes.count }}
6 </p>
7 <button class="btn btn-danger" @click="reset">Reset votes</butto\
8 n>
9 <hr>
10
11 <div class="row">
12 <food name="Cheeseburger"></food>
13 <food name="Double Bacon Burger"></food>
14 <food name="Whooper"></food>
15 </div>
16 <hr>
17
18 <h1>Log:</h1>
19 <ul class="list-group">
20 <li class="list-group-item" v-for="vote in votes.log"> {{ vote\
21 }} </li>
22 </ul>
23 </div>
24 </body>
Eventos Customizados 117
JavaScript
35 methods:
36 {
37 countVote: function (food) {
38 this.votes.count++
39 this.votes.log.push(food + ' received a vote.')
40 },
41 reset: function () {
42 this.votes = {
43 count: 0,
44 log: []
45 }
46 bus.$emit('reset')
47 }
48 },
49 created () {
50 bus.$on('voted', this.countVote)
51 }
52 })
Eventos Customizados 119
Atenção
Observe aqui que estamos usando:
bus.$on('voted', this.countVote)
bus.$on('voted', function(){
this.vote(food)
})
Teria dado um erro, já que this deveria estar limitado à instância bus em
vez da instância do componente atual.
Eventos Customizados 120
Para ver isso em ação, iremos adicionar um botão Stop para impedir que os votos
sejam contados/registrados. Vamos inserir o método stop na instância Vue.
new Vue({
...
methods:
{
...
stop: function () {
bus.$off(['voted'])
}
}
})
Story Component
Vue.component('story', {
...
methods:{
...
updateFavorite: function(){
// 'update' is just the name of the custom event
// it could be anything. ex: fav-update
this.$emit('update', this.story)
}
}
...
});
Na instância pai, vamos adicionar a variável favorite ao data. Além disso, vamos
criar um novo método, que irá atualizar a variável favorite quando chamada.
Eventos Customizados 122
Parent Instance
new Vue({
...
data: {
...
favorite: {}
},
methods: {
updateFavorite: function(story) {
this.favorite = story;
}
},
})
Informação
No Vue 2, os bindings são sempre one-way (único fluxo ou único cami-
nho). Para manter os dados sincronizados entre componentes Pai-Filho é
necessário usar Eventos.
Exemplos
Você pode encontrar estes exemplos no GitHub3 .
3
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter8
Eventos Customizados 124
8.7 Tarefa
• Este é o exercício mais difícil até agora, então tenha em mente que é preciso
compreender tudo o que foi visto até agora.
• Crie um Array que contém 4 carruagens puxadas por cavalos. Cada carruagem
tem um nome (name) e um número de cavalos, chamado de horses (1 até 4).
• Crie o componente carruagem, que se chamará chariot.
• O componente chariot deverá exibir o seu nome e o número de cavalos que
possui.
• Ele também deve ter um botão de ação. O texto do botão depende da carruagem
selecionada no momento.
Dica
Você precisa manter sincronizada a propriedade currentChariot dos
componentes pai e filhos.
Eventos Customizados 125
Hint
Para desabilitar um botão use o atributo disabled="true". Você precisa
pensar em como fará isso na forma condicional.
Exemplo
Solução sugerida
Você pode encontrar uma sugestão de solução deste exercício aqui4 .
4
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/homework/chapter8.html
9. Bindings em classes e estilos
9.1 Binding em classes
Sintaxe
Uma necessidade comum para o data binding é manipular a classe de um elemento
e seus estilos. Para estes casos, você pode usar v-bind:class
Para estes casos, você pode usar v-bind:class. Isso pode ser usado para aplicar
classes condicionalmente, alterando-os e/ou aplicando vários ao mesmo tempo
ligados a um objeto.
A diretiva v-bind:class pode usar um objeto com o seguinte formato como
argumento.
{
'classA': true,
'classB': false,
'classC': true
}
E aplica todas as classes com o valor true ao elemento. Por exemplo, as classes que
irão compor o elemento, podem ser classA e classC.
<div v-bind:class="elClasses"></div>
Bindings em classes e estilos 127
data: {
elClasses:
{
'classA': true,
'classB': false,
'classC': true
}
}
Para demonstrar como o v-bind é usado como atributos de classe, vamos fazer um
exemplo que envolve a troca de classes. Usando a diretiva v-bind:class, vamos
dinamicamente alterar a classe dos elementos div.
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/\
4 bootstrap.min.css" rel="stylesheet">
5 <title>Hello Vue</title>
6 </head>
7 <body>
8 <div class="container text-center">
9 <div class="box" v-bind:class="{ 'red' : color, 'blue' : !color \
10 }"></div>
11 <div class="box" v-bind:class="{ 'purple' : color, 'green' : !co\
12 lor }"></div>
13 <div class="box" v-bind:class="{ 'red' : color, 'blue' : !color \
14 }"></div>
15 <div class="box" v-bind:class="{ 'purple' : color, 'green' : !co\
16 lor }"></div>
17 <button v-on:click="flipColor" class="btn btn-block btn-success">
18 Flip color!
19 </button>
20 </div>
21 </body>
22 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
Bindings em classes e estilos 128
23 "></script>
24 <script type="text/javascript">
25 new Vue({
26 el: '.container',
27 data: {
28 color: true
29 },
30 methods: {
31 flipColor: function() {
32 this.color = !this.color;
33 }
34 }
35 });
36 </script>
37 <style type="text/css">
38 .red {
39 background: #ff0000;
40 }
41 .blue {
42 background: #0000ff;
43 }
44 .purple {
45 background: #7B1FA2;
46 }
47 .green {
48 background: #4CAF50;
49 }
50 .box {
51 float: left;
52 width: 200px;
53 height: 200px;
54 margin: 40px;
55 border: 1px solid rgba(0, 0, 0, .2);
56 }
57 </style>
Bindings em classes e estilos 129
58 </html>
Alternância de cores
Alternância de cores
ou de purple para green, dependendo do valor atual de color. Com a troca feita, o
estilo será aplicado para cada uma das classes para que a saída que desejamos seja
feita.
Informação
A diretiva v-bind:class pode coexistir com o atributo class simultane-
amente.
Então, em nosso exemplo, divs sempre tem a classe box e condicional-
mente tem uma de red, blue, purple ou green.
Sintaxe em um Array
Também podemos aplicar uma lista de classes a um elemento, usando um Array.
Aplicando uma condição a uma classe, pode também ser feito com o uso do if
inline, dentro do Array
Informação
Inline if é comumente referenciado como operador ternário, operador
condicional, or if ternário.
A condicional (ternário) é o único operador em Javascript que possui três
operadores.
A sintaxe de um operador ternário é condição ? expressao1 :
expressao2. Se a condição for verdadeira, o operador retorna o valor de
expressão1, caso contrário o valor de expressão2 é retornado.
1 new Vue({
2 el: '.container',
3 data: {
4 color: true
5 },
6 methods: {
7 flipColor: function() {
8 this.color = !this.color;
9 }
10 }
11 });
Dica
Para usar um nome de classe em vez de variável dentro de um Array, use
aspas simples. v-bind:class="[ variable, 'classname']"
Bindings em classes e estilos 132
Sintaxe
A sintaxe de objeto para v-bind:style é bastante direta; se assemelha ao css, exceto
que é um objeto JavaScript.
Vamos usar a abreviação que o Vue.js fornece para o uso de diretivas, v-bind(:).
1 data: {
2 niceStyle:
3 {
4 color: 'blue',
5 fontSize: '20px'
6 }
7 }
Muitas vezes é uma boa ideia usar um style object pois o template fica mais limpo.
Sintaxe em Arrays
Usando a sintaxe de Array inline para v-bind:style, podemos criar vários objetos
de estilo no mesmo elemento, o que significa que cada item da lista vai ter o atributo
color e font-size da classe niceStyle e a fonte da classe badStyle.
1 data: {
2 niceStyle:
3 {
4 color: 'blue',
5 fontSize: '20px'
6 }
7 badStyle:
8 {
9 fontStyle: 'italic'
10 }
11 }
Informação
Quando você usa a propriedade CSS que requer prefixos em
v-bind:style, por exemplo, transform, Vue.js irá automaticamente
detectar e adicionar os prefixos apropriados para que os estilos sejam
aplicados.
Você pode encontrar mais informações sobre prefixos aqui1 .
1
https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix
Bindings em classes e estilos 135
1 <html>
2 <head>
3 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/boot\
4 strap.min.css" rel="stylesheet">
5 <title>Hello Vue</title>
6 </head>
7 <body class="container-fluid">
8 <div id="app">
9 <ul>
10 <li :class="{'completed' : task.done}"
11 :style="styleObject"
12 v-for="task in tasks">
13 {{task.body}}
14 <button @click="completeTask(task)" class="btn">
15 Just do it!
16 </button>
17 </li>
18 </ul>
19 </div>
20 </body>
21 <script type="text/javascript" src="https://cdnjs.cloudflare.com/aja\
22 x/libs/vue/2.0.1/vue.js"></script>
23 <script type="text/javascript">
24 new Vue({
25 el: '#app',
26 data: {
27 tasks: [
28 {body: "Feed the horses", done: true},
29 {body: "Wash armor", done: true},
30 {body: "Sharp sword", done: false},
31 ],
32 styleObject: {
33 fontSize: '25px'
34 }
35 },
Bindings em classes e estilos 136
36 methods: {
37 completeTask: function(task) {
38 task.done = !task.done;
39 }
40 },
41 });
42 </script>
43 <style type="text/css">
44 .completed {
45 text-decoration: line-through;
46 }
47 </style>
48 </html>
cada tarefa é acompanhada por um botão, ouvindo o evento click, no qual dispara
um método, alternando o status da conclusão da tarefa. O atributo style é ligado ao
styleObject, resultando na mudança do font-size de todas as tarefas. Como você
pode ver, o método completeTasks possui o parâmetro task.
Bindings em classes e estilos 137
Exemplos
Você pode encontrar estes exemplos no GitHub2 .
2
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter9
Bindings em classes e estilos 138
9.4 Tarefa
Um exercício divertido e talvez complicado para este capítulo. Crie uma caixa de
texto onde o usuário pode escolher uma cor. Com a cor escolhida, aplique=a no
elemento de sua escolhe. É isso, vamos pintar!! :))
Dica
Você pode usar input type="color" para facilitar (suportado na maioria
dos navegadores).
Exemplo
Solução sugerida
Você pode encontrar uma sugestão de solução deste exercício aqui3 .
3
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/homework/chapter9.html
II Consumindo uma API
10. Introdução
Neste capítulo, estamos avançar um pouco mais e demonstrar como podemos usar
Vue.js para consumir uma API.
Seguindo os exemplos dos capítulos anteriores (histórias), vamos obter alguns dados
reais de uma fonte externa.
Para usar dados reais, precisamos de um banco de dados. Assumindo que você já
sabe como criar um banco de dados, isso não será abordado neste livro. Para este
livro, criamos um banco de dados pronto que poderá ser usado.
10.1 CRUD
Presumindo que temos um banco de dados, precisamos realizar as operações CRUD
(Create, Read, Update, Delete).
Sendo mais específico, precisamos:
Visto que o Vue.js é um framework front-end, ele não é usado para conectar no banco
de dados diretamente. Para acessar o banco de dados, precisamos de uma camada
entre ele e o Vue.js. Essa camada é a API (Application Program Interface).
Introdução 141
10.2 API
Como este livro trata sobre o Vue.js e não sobre o projeto de APIs, fornecemos
uma de demonstração criada com Laravel1 . Laravel é um dos frameworks PHP mais
poderosos, juntamente com Sinfony 2, Nette, Cb odeIgniter e Yii2. Você pode criar
uma outra API usando qualquer outra linguagem ou framework que você gosta. Nós
usamos Laravel porque ele é simples, tem uma grande comunidade e é incrível! :)
Portanto, recomendamos que use nossa API demo que construímos exclusivamente
para os exemplos desse livro.
mkdir ∼/themajestyofvuejs2
cd ∼/themajestyofvuejs2
git clone https://github.com/hootlex/the-majesty-of-vuejs-2 .
Como alternativa, você pode visitar o github2 e realizar o download do arquivo zip.
Em seguida, extraia os arquivos no diretório criado.
1
https://laravel.com/
2
https://github.com/hootlex/the-majesty-of-vuejs-2
Introdução 142
cd ∼/themajestyofvuejs2/apis/stories
sh setup.sh
1. Agora você tem um banco de dados preenchido com dados fictícios, bem como
um servidor funcionando em http://localhost:3000!
Se você quiser customizar o servidor (host, porta etc), você pode fazer isso
manualmente O código fonte do script é:
# install dependencies
$ composer install
# Start server
$ php artisan serve --port=3000 --host localhost;
Introdução 143
Nota
Se você estiver usando Vagrant, você deve executar o servidor no host
‘0.0.0.0’. Então, você terá acesso ao seu servidor pelo ip do Vagrant.
Se, por exemplo, o ip do Vagrant’s for 192.168.10.10 e você executar
$ php artisan serve --port=3000 --host 0.0.0.0;
Se você optou por criar sua própria API, você deve criar uma tabela no banco de
dados para chamada stories, com as seguintes colunas:
API Endpoints
Um endpoint é simplesmente uma URL. Quando acessamos http://example.com/foo/bar,
este é o endpoint e você precisa simplesmente acessar /foo/bar já que o domínio é o
mesmo para todos os endpoints.
Para gerenciar as histórias, precisamos de 5 endpoints. Cada endpoint corresponde a
uma específica ação.
Introdução 144
Como indicado na tabela acima, para obter uma lista com todas as histórias,
temos que fazer uma requisição HTTP GET ou HEAD para api/stories. Para
atualizar uma história temos que fazer uma requisição HTTP PUT ou PATCH para
api/stories/{storyID} informando os dados para a atualização, juntamente com o
{id}.
Assumindo que o servidor está sendo executado em http://localhost:3000, você
pode ver uma lista com todas as histórias no formato JSON visitando http://localhost:3000/ap
no seu navegador.
Introdução 145
Resposta JSON
Dica
Ler dados JSON no navegador pode ser doloroso. É sempre melhor ler os
dados formatados. Chrome tem alguns bons plugins para formatar o JSON.
Eu uso JSONFormatter3 porque suporta syntax highlighting e mostra o
JSON no formato tree, onde os nós da árvore podem ser agrupados e
expandidos. Ele também mostra um botão para alterar entre a formatação
padrão e o formato original.
Você pode escolher qualquer extensão, mas definitivamente você deve
usar uma.
3
https://chrome.google.com/webstore/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa
11. Trabalhando com Dados
Reais
É hora de realmente colocar o banco de dados para realizar as operações CRUD.
Vamos utilizar o último exemplo do capítulo de Componentes, mas dessa vez, nossos
dados serão obtidos através de uma fonte externa. Para trocar dados com o servidor,
precisamos fazer chamadas HTTP (Ajax) assíncronas.
Informação
Ajax é uma técnica que permite que páginas Web sejam atualizadas
assincronamente trocando pequenas quantidades de dados com o servidor.
new Vue({
data: {
stories: [
{
plot: 'My horse is amazing.',
writer: 'Mr. Weebl',
},
{
plot: 'Narwhals invented Shish Kebab.',
Trabalhando com Dados Reais 147
Informação
vue-resource é um plugin para o Vue.js que provê serviços para a
realização de requisições web e o gerenciamento de respostas do servidor.
$.get(
url,
success
);
1
https://github.com/vuejs/vue-resource
Trabalhando com Dados Reais 148
$.ajax({
url: url,
success: success
});
Há um porém aqui, nós precisamos fazer essa chamada depois que a instância Vue
estiver pronta. Você se lembra dos ciclos de vida do Vue?
Existe um evento chamado mounted, que é chamado imediatamente após a instância
do Vue ser “montada”.
Atenção
O evento mounted não é equivalente ao evento jQuery
$(document).ready(). Quando usamos mounted, ainda não há garantias
que a DOM está pronta. Se você precisar executar algo no qual a DOM
precise estar pronta, você pode usar:
mounted: function () {
this.$nextTick(function () {
// código assume que this.$el está pronto
})
}
1 <div id="app">
2 <div class="container">
3 <h1>Let's hear some stories!</h1>
4 <ul class="list-group">
5 <story v-for="story in stories" :story="story">
6 </story>
7 </ul>
8 <pre>{{ $data }}</pre>
9 </div>
10 </div>
11 <template id="template-story-raw">
12 <li class="list-group-item">
13 {{ story.writer }} said "{{ story.plot }}"
14 <span>{{story.upvotes}}</span>
15 </li>
16 </template>
1 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js\
2 "></script>
3 <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
4 <script type="text/javascript">
5 Vue.component('story', {
6 template: "#template-story-raw",
7 props: ['story'],
8 });
9
10 var vm = new Vue({
11 el: '#app',
12 data: {
13 stories: []
14 },
15 mounted: function(){
16 $.get('/api/stories', function(data){
17 vm.stories = data;
18 })
Trabalhando com Dados Reais 150
19 }
20 })
21 </script>
Obter histórias
11.2 Refatorando
Ter uma grande quantidade de código no editor de texto pode ser confuso se não for
exibido corretamente, bem como no navegador. Por esse motivo vamos refatorar o
código do exemplo, para renderizar a lista de stories usando o elemento <table>
em vez do <ul>.
1 <div id="app">
2 <table class="table table-striped">
3 <tr>
4 <th>#</th>
5 <th>Plot</th>
6 <th>Writer</th>
7 <th>Upvotes</th>
8 <th>Actions</th>
9 </tr>
10 <tr v-for="story in stories" is="story" :story="story"><\
11 /tr>
12 </table>
13 </div>
14 <template id="template-story-raw">
15 <tr>
16 <td>
17 {{story.id}}
18 </td>
19 <td>
20 <span>
21 {{story.plot}}
22 </span>
23 </td>
24 <td>
25 <span>
26 {{story.writer}}
27 </span>
28 </td>
Trabalhando com Dados Reais 152
29 <td>
30 {{story.upvotes}}
31 </td>
32 </tr>
33 </template>
34 <p class="lead">Here's a list of all your stories.
35 </p>
36 <pre>{{ $data }}</pre>
Mas há um problema!
Problema na renderização
Portanto, para resolver este problema, temos que usar o atributo especial is.
<table>
<tr is="my-component"></tr>
</table>
sempre que uma história for votada, garantindo que os votos da história sejam
atualizados no banco de dados também.
Para atualizar uma história existente, temos que fazer uma requisição HTTP PUT ou
PATCH para api/stories/{storyID}.
Dentro da função upvoteStory, que deve ser criada, vamos fazer uma chamada
HTTP depois que incrementarmos a variável upvotes.
1 <td>
2 <div class="btn-group">
3 <button @click="upvoteStory(story)" class="btn btn-primary">
4 Upvote
5 </button>
6 </div>
7 </td>
1 Vue.component('story',{
2 template: '#template-story-raw',
3 props: ['story'],
4 methods: {
5 upvoteStory: function(story){
6 story.upvotes++;
7 $.ajax({
8 url: '/api/stories/'+story.id,
9 type: 'PATCH',
10 data: story,
11 });
12 }
13 },
14 })
Votação
1 <td>
2 <div class="btn-group">
3 <button @click="upvoteStory(story)" class="btn btn-primary">
4 Upvote
5 </button>
6 <button @click="deleteStory(story)" class="btn btn-danger">
7 Delete
8 </button>
9 </div>
10 </td>
Trabalhando com Dados Reais 156
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 deleteStory: function(story){
6 // find story
7 var index = vm.stories.indexOf(story);
8
9 // delete it
10 vm.stories.splice(index, 1)
11 }
12 }
13 ...
14 })
Mas é claro, desta forma, vamos apenas remover a história de forma temporária.
Para excluir a história do banco de dados, precisamos executar uma solicitação HTTP
DELETE.
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 deleteStory: function(story){
6 // find story
7 var index = vm.stories.indexOf(story);
8
9 // delete it
10 vm.stories.splice(index, 1)
11
12 // make DELETE request
13 $.ajax({
Trabalhando com Dados Reais 157
14 url: '/api/stories/'+story.id,
15 type: 'DELETE'
16 });
17 },
18 }
19 ...
20 })
Estamos usando a mesma URl, como fizemos antes. Já o tipo de requisição é DELETE.
O método está pronto e podemos excluir a história de nossa base de dados, bem como
na DOM.
Removendo Histórias
mounted: function() {
// GET request
this.$http({url: '/someUrl', method: 'GET'})
.then(function (response) {
// success callback
}, function (response) {
// error callback
});
}
1
https://github.com/vuejs/vue-resource
2
https://cdnjs.com/libraries/vue-resource
Integrando o vue-resource 159
Informação
A instância Vue fornece a função this.$http(options) que aponta para o
objeto que realiza a chamada HTTP e retornar um Promise. Além disso, a
instância Vue será automaticamente vinculada ao escopo this nas funções
de callback.
12.2 Migração
1 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/1.0\
2 .3/vue-resource.js"></script>
Para obter as histórias, vamos fazer uma requisição GET no formulário correspon-
dente:
Integrando o vue-resource 160
mounted: function() {
// GET request
this.$http({url: '/api/stories', method: 'GET'})
.then(function (response) {
Vue.set(vm, 'stories', response.data)
// Or we as we did before
// vm.stories = response.data
})
}
Requisição PATCH
upvoteStory: function(story){
story.upvotes++;
this.$http.patch('/api/stories/'+story.id , story)
}
Requisição DELETE
deleteStory: function(story){
this.$parent.stories.indexOf(story)
this.$parent.stories.splice(index, 1)
this.$http.delete('/api/stories/'+story.id )
}
Conseguimos então remover os métodos Ajax com jQuery e substituir pelo vue-
resource de forma bem rápida!
Informação
Como o componente story não tem acesso ao Array stories, acessa-
mos o Array usando this.$parent.stories. Também poderíamos usar
vm.stories ou disparar um evento repassando os dados e atualizar o
Array na instância Vue pai.
Integrando o vue-resource 161
Editando Histórias
Vamos começar com a primeira tarefa e possibilitar que o usuário possa manipular
as histórias. Duas caixas de texto podem ser usadas, mas somente devemos mostrá-
las se o usuário estiver editando a história. Para definir que uma história esteja no
“modo de edição”, podemo usar uma propriedade chamada editing, no qual assume
o valor true se o usuário clicar em um botão.
1 <td>
2 <!--se estiver editando a história, exibe a caixa de texto-->
3 <input v-if="story.editing" v-model="story.plot" class="form-con\
4 trol">
5 </input>
6 <!--em outros casos, mostra somente a história-->
7 <span v-else>
8 {{story.plot}}
9 </span>
10 </td>
11 <td>
12 <!-- se estiver editando a história, exibe a caixa de texto -->
13 <input v-if="story.editing" v-model="story.writer" class="form-c\
14 ontrol">
15 </input>
16 <!--em outros casos, mostra somente o autor-->
17 <span v-else>
18 {{story.writer}}
19 </span>
20 </td>
Integrando o vue-resource 162
21 <td>
22 {{story.upvotes}}
23 </td>
24 <td>
25 <div v-if="!story.editing" class="btn-group">
26 <button @click="upvoteStory(story)" class="btn btn-primary">
27 Upvote
28 </button>
29 <button @click="editStory(story)" class="btn btn-default">
30 Edit
31 </button>
32 <button @click="deleteStory(story)" class="btn btn-danger">
33 Delete
34 </button>
35 </div>
36 </td>
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 editStory: function(story){
6 story.editing=true;
7 },
8 }
9 ...
10 })
Criamos então a tabela que permite editar uma história, que contém duas caixas
de texto e um botão. Utilizamos a função editStory para alterar a propriedade
story.editing para true, então o v-if irá deixar visível as caixas de texto e esconder
os botões de voto e remoção da história.
Agora, essa abordagem ainda não funciona! Parece que a DOM não está atualizando
após definirmos a propriedade story.editing para true. Mas porque isso acontece?
Integrando o vue-resource 163
Acontece que, de acordo com esse artigo no blog do Vue.js3 , quando você está
adicionando uma nova propriedade que não estava registrada no objeto data, a
DOM não irá atualizar. A melhor prática é sempre declarar as propriedades que serão
reativas no contexto que está trabalhando. Isso significa que você deve adicionar
a propriedade story.editing com o valor false em todas as histórias logo após
receber o array de histórias do servidor.
Como alternativa, você pode usar os métodos especiais Vue.set ou Vue.delete para
adicionar ou remover uma propriedade que será automaticamente observada pela
DOM.
Para adicionar a propriedade, podemos usar o método .map() do javascript, dentro
do método de retorno da consulta ao servidor.
mounted: function() {
var vm = this;
// GET request
this.$http({url: '/api/stories', method: 'GET'})
.then(function (response) {
var storiesReady = response.data.map(function(story){
story.editing = false;
return story
})
Informação
O método .map() chama uma função de callback para cada elemento do
Array e retorna um Array com os resultados. Você pode encontrar mais
informações sobre este método aqui4 .
3
http://vuejs.org/2016/02/06/common-gotchas/
4
https://msdn.microsoft.com/en-us/library/ff679976(v=vs.94).aspx
Integrando o vue-resource 164
Esta função adiciona o atributo editing a cada item do objeto story e então retorna
o Array atualizado.
A nova variável, storiesReady, é um Array que contém o Array atualizado com a
nova propriedade.
Quando a história estiver sendo editada, nós daremos duas opções: atualizar a história
com os novos valores ou cancelar a edição.
Formulário de edição
Então, vamos seguir em frente e adicionar dois novos botões, que devem ser exibidos
somente quando a história estiver sendo editada.
Além disso, um novo método chamado updateStory será criado. Ele vai atualizar a
história, após o botão Atualizar ser pressionado.
Vue.component('story',{
...
methods: {
...
updateStory: function(story){
this.$http.patch('/api/stories/'+story.id , story)
//Set editing to false to show actions again and hid\
e the inputs
story.editing = false;
},
}
...
})
Atualizando histórias
Após a requisição PATCH terminar com sucesso, temos que alterar o valor de
story.editing de volta para false, para esconder as caixas de texto de edição e
os botões de ação.
Inicializaremos todos os atributos da história para null, exceto o editing. Uma vez
Integrando o vue-resource 166
Informação
O método push() adiciona novos itens ao final de um Array e retorna
o novo comprimento. Você pode encontrar mais informações sobre o
método push() e sua sintaxe aqui5 .
Como o newStory.editing está definido como true, as entradas para plot e writer
junto com os botões de ação Edit, estão sendo renderizadas instantaneamente.
Além disso, o novo objeto story deve ser enviado ao servidor para ser armazenado
no banco de dados. Vamos executar uma solicitação POST dentro de um método
chamado storeStory.
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 storeStory: function(story){
6 this.$http.post('/api/stories/', story).then(function() {
7 story.editing = false;
8 });
9 },
10 }
11 ...
12 })
1 <td>
2 <div class="btn-group" v-if="!story.editing">
3 <button @click="upvoteStory(story)" class="btn btn-primary">
4 Upvote
5 </button>
6 <button @click="editStory(story)" class="btn btn-default">
7 Edit
8 </button>
9 <button @click="deleteStory(story)" class="btn btn-danger">
10 Delete
11 </button>
Integrando o vue-resource 168
12 </div>
13 <div class="btn-group" v-else>
14 <button class="btn btn-primary" @click="updateStory(story)">
15 Update Story
16 </button>
17 <button class="btn btn-success" @click="storeStory(story)">
18 Save New Story
19 </button>
20 <button @click="story.editing=false" class="btn btn-default">
21 Cancel
22 </button>
23 </div>
24 </td>
Um pequeno erro
Para contornar este problema, vamos reestruturar nossos botões. O botão Update será
apenas exibido quando a história for antiga. Consequentemente, o botão Save New
Story será apresentado quando a for uma nova história.
Você pode ter notado que todas as histórias obtidas do servidor têm um atributo id.
Vamos usar este detalhe para definir se uma história é nova ou não.
11 ">
12 Save New Story
13 </button>
14 <!--sempre exibir o cancelar-->
15 <button @click="story.editing=false" class="btn btn-default">
16 Cancel
17 </button>
18 </div>
Dica
Se a história vem do banco de dados, então ele terá um id.
Depois de criar, salvar e tentar editar uma nova história, vemos que o botão diz
“Salvar nova história” em vez de “Atualizar História”! Isso ocorre porque não estamos
buscando a nova história criada a partir do servidor, após enviá-lo, e ele ainda não
tem um id.
Para resolver esse problema, podemos novamente buscar as histórias do servidor,
logo depois de armazenar uma nova história no banco de dados.
Como não é bom repetir código, vamos extrair o procedimento de busca para um
método chamado fetchStories (). Depois disso, pode-se usar esse método para
buscar as histórias a qualquer momento.
O método fetchStories
1 var vm = new Vue({
2 el: '#v-app',
3 data : {
4 stories: [],
5 },
6 mounted: function(){
7 this.fetchStories()
8 },
9 methods: {
10 createStory: function(){
11 var newStory={
12 "plot": "",
13 "upvotes": 0,
14 "editing": true
15
16 };
17 this.stories.push(newStory);
18 },
19 fetchStories: function () {
20 this.$http.get('/api/stories')
21 .then(function (response) {
22 var storiesReady = response.data.map(function(st\
23 ory){
24 story.editing = false
Integrando o vue-resource 172
25 return story
26 })
27 Vue.set(vm, 'stories', storiesReady)
28 // or: vm.stories = storiesReady
29 });
30 },
31 }
32 });
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 storeStory: function(story){
6 this.$http.post('/api/stories/', story).then(function() {
7 story.editing = false;
8 vm.fetchStories();
9 });
10 },
11 }
12 ...
13 })
Armazenar e Atualizar
Uma maneira melhor de corrigir o problema anterior, é buscar apenas o recém-criado
story do banco de dados, em vez de buscar e substituir todas as histórias.
Se você verificar a resposta do servidor, para a solicitação POST, você verá que ele
retorna o story criado junto com seu id.
Integrando o vue-resource 173
A única coisa que temos a fazer, é atualizar nossa história para coincidir com a do
servidor. Então, vamos definir o id dos dados da resposta, para o atributo id da
história. Faremos isso dentro do retorno de chamada de sucesso do POST.
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 storeStory: function(story){
6 this.$http.post('/api/stories/', story).then(function(re\
7 sponse) {
8 Vue.set(story, 'id', response.data.id);
9 story.editing = false
10 });
11 },
12 }
13 ...
14 })
Como a nova história não tinha id, quando é enviada para o array de stories, o
DOM não será atualizado quando o id mudar, então não seremos capazes de usar o
novo id.
Dica
Quando você está adicionando uma nova propriedade que não estava
presente quando os dados foram observados, Vue.js não consegue
detectar a adição da propriedade. Portanto, se você precisar adicionar
ou remover propriedades em tempo de execução, use os métodos globais
Vue.set ou Vue.delete.
Se você ainda não baixou o repositório, você ainda pode acessar stories.html6 e
app.js7 no github.
stories.html
1 <html lang="en">
2 <head>
3 <title>Stories</title>
4 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/boo\
5 tstrap/3.3.2/css/bootstrap.min.css">
6 </head>
7
8 <body>
9 <main>
10 <div class="container">
11 <h1>Stories</h1>
12 <div id="v-app">
13 <table class="table table-striped">
14 <tr>
15 <th>#</th>
16 <th>Plot</th>
17 <th>Writer</th>
18 <th>Upvotes</th>
19 <th>Actions</th>
20 </tr>
21 <tr v-for="story in stories" is="story" :story="stor\
22 y"></tr>
23 </table>
24 <p class="lead">Here's a list of all your stories.
25 <button @click="createStory()" class="btn btn-primar\
26 y">
27 Add a new one?
28 </button>
29 </p>
6
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/apis/stories/public/stories.html
7
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/apis/stories/public/js/app.js
Integrando o vue-resource 176
65 Upvote
66 </button>
67 <button @click="editStory(story)" class="btn btn-def\
68 ault">
69 Edit
70 </button>
71 <button @click="deleteStory(story)"
72 class="btn btn-danger">
73 Delete
74 </button>
75 </div>
76 <div class="btn-group" v-else>
77 <!--If the story is taken from the db then it will h\
78 ave an id-->
79 <button v-if="story.id"
80 class="btn btn-primary"
81 @click="updateStory(story)">
82 Update Story
83 </button>
84
85 <!--If the story is new we want to store it-->
86 <button v-else class="btn btn-success"
87 @click="storeStory(story)">
88 Save New Story
89 </button>
90
91 <!--Always show cancel-->
92 <button @click="story.editing=false"
93 class="btn btn-default">
94 Cancel
95 </button>
96 </div>
97 </td>
98 </tr>
99 </template>
Integrando o vue-resource 178
1 Vue.component('story', {
2 template: '#template-story-raw',
3 props: ['story'],
4 methods: {
5 deleteStory: function (story) {
6 var index = this.$parent.stories.indexOf(story);
7 this.$parent.stories.splice(index, 1)
8 this.$http.delete('/api/stories/' + story.id)
9 },
10 upvoteStory: function (story) {
11 story.upvotes++;
12 this.$http.patch('/api/stories/' + story.id, story)
13 },
14 editStory: function (story) {
15 story.editing = true;
16 },
17 updateStory: function (story) {
18 this.$http.patch('/api/stories/' + story.id, story)
19 //Set editing to false to show actions again and hide th\
20 e inputs
21 story.editing = false;
22 },
23 storeStory: function (story) {
24 this.$http.post('/api/stories/', story)
25 .then(function (response) {
26 // After the the new story is stored in the database
Integrando o vue-resource 179
62 .then(function (response) {
63 // set data on vm
64 var storiesReady = response.data.map(function (s\
65 tory) {
66 story.editing = false;
67 return story
68 })
69 Vue.set(vm, 'stories', storiesReady)
70 });
71 },
72 }
73 });
Integrando o vue-resource 181
Código fonte
Você pode encontrar estes exemplos no GitHub8
12.6 Tarefa
Para se familiarizar com a criação de solicitações na web e o tratamento de respostas,
você deve replicar o que fizemos neste capítulo.
O que você tem a fazer é consumir uma API para:
preparamos o banco de dados e a API para você. Você só tem que escrever HTML e
JavaScript.
Configuração
Se você seguiu as instruções de [Capítulo 10] (# downloadcode), abra seu terminal e
execute:
cd ∼/themajestyofvuejs/apis/movies
sh setup.sh
mkdir ∼/themajestyofvuejs
cd ∼/themajestyofvuejs
git clone https://github.com/hootlex/the-majesty-of-vuejs .
cd ∼/themajestyofvuejs/apis/movies
sh setup.sh
Agora você tem um banco de dados preenchido com grandes filmes juntamente
com um servidor totalmente funcional executando em http://localhost:3000 !
Para garantir que tudo está funcionando bem, navegue até http://localhost:3000/api/movies
e você verá um Array de filmes no formato JSON.
API Endpoints
A API que você vai precisar é:
Seu código
Coloque o seu código HTML em ∼/themajestyofvuejs2/apis/movies/public/movies.html.
Você pode colocar o seu código JavaScript em js/app.js.
Para ver o seu código em funcionamento, acesse http://localhost:3000/movies.html no
navegador.
Espero que você aproveite!! Boa sorte!!
Integrando o vue-resource 183
Solução sugerida
Você pode encontrar uma sugestão de solução deste exercício aqui9 .
9
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/homework/Chapter12
13. Visão Geral do axios
13.1 Aposentando o vue-resource
Como vimos, o Vue tem o seu próprio cliente HTTP (vue-resource), mas a equipe de
desenvolvimento decidiu remover a sua recomendação oficial de uso, através do post
“Aposentando o vue-resource1 ” explicando os vários pontos do porquê disto.
Vamos fazer novamente todas as requisições web que fizemos no capítulo anterior,
mas agora utilizando axios2 .
De acordo com o post de Evan sobre vue-resource, É totalmente normal continuar a
usar o vue-resource se você está feliz com ele e você é livre para escolher o que você
preferir (até mesmo “$.ajax”), mas como axios é a recomendação mais recente, vamos
mostrar como é a sua integração.
1
https://medium.com/the-vue-point/retiring-vue-resource-871a82880af4#.lew43e17f
2
https://github.com/mzabriskie/axios
3
https://github.com/mzabriskie/axios
4
http://www.typescriptlang.org/
Visão Geral do axios 185
Dica
Se você quiser continuar usando this.$http como em vue-resource, você
pode simplesmente definirVue.prototype.$http = axios e você terá o
axios sem ter que mudar muito!
13.3 Migração
É hora de usar axios no nosso exemplo. Primeiro, temos que incluí-lo. Vamos
adicionar esta linha no arquivo HTML.
1 <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
mounted: function() {
// GET request
axios.get('/api/stories')
.then(function (response) {
Vue.set(vm, 'stories', response.data)
// Or we as we did before
// vm.stories = response.data
})
}
Nossa lista de histórias vem sem nenhum problema, ao usar a sintaxe acima. Por
conveniência, fornecemos alguns métodos suportados.
Métodos axios
1 axios.request(config)
2 axios.get(url[, config])
3 axios.delete(url[, config])
4 axios.head(url[, config])
5 axios.post(url[, data[, config]])
6 axios.put(url[, data[, config]])
7 axios.patch(url[, data[, config]])
Nota
Ao usar os métodos de alias url, method e data eles não precisam ser
especificados na configuração.
PATCH request
upvoteStory: function(story){
story.upvotes++;
axios.patch('/api/stories/' + story.id, story)
}
DELETE request
deleteStory: function(story){
var index = this.$parent.stories.indexOf(story);
this.$parent.stories.splice(index, 1)
axios.delete('/api/stories/' + story.id)
}
Informação
Como o componente story não tem acesso ao Array stories, acessa-
mos o Array usando this.$parent.stories. Também poderíamos usar
vm.stories ou emitir um evento para atualizar o Array dentro da instân-
cia Vue do pai.
13.4 Melhorando
Devemos adicionar um par a mais de recursos, para criar a nossa lista de histórias.
Podemos dar ao usuário a capacidade de alterar o enredo de uma história, seu
escritor, e também criar novas histórias.
Visão Geral do axios 188
Editando histórias
Vamos começar com a primeira tarefa e dar ao usuário algumas entradas para
manipular os atributos da história. Duas caixas de texto devem fazer o trabalho, mas
devemos exibi-los apenas quando o usuário está em edição.
Parece o tipo de trabalho que fizemos em capítulos anteriores.
Para definir se uma história está editando o estado, usaremos uma propriedade,
editing, que se tornará verdadeira quando o usuário clicar no Botão Editar.
1 <td>
2 <!--if editing story, display the input for plot-->
3 <input v-if="story.editing" v-model="story.plot" class="form-con\
4 trol">
5 </input>
6 <!--in other occasions, show the story's plot-->
7 <span v-else>
8 {{story.plot}}
9 </span>
10 </td>
11 <td>
12 <!-- if editing story, display the input for writer -->
13 <input v-if="story.editing" v-model="story.writer" class="form-c\
14 ontrol">
15 </input>
16 <!--in other occasions, show the story's writer-->
17 <span v-else>
18 {{story.writer}}
19 </span>
20 </td>
21 <td>
22 {{story.upvotes}}
23 </td>
24 <td>
25 <div v-if="!story.editing" class="btn-group">
26 <button @click="upvoteStory(story)" class="btn btn-primary">
Visão Geral do axios 189
27 Upvote
28 </button>
29 <button @click="editStory(story)" class="btn btn-default">
30 Edit
31 </button>
32 <button @click="deleteStory(story)" class="btn btn-danger">
33 Delete
34 </button>
35 </div>
36 </td>
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 editStory: function(story){
6 story.editing=true;
7 },
8 }
9 ...
10 })
Criamos então a tabela que permite editar uma história, que contém duas caixas
de texto e um botão. Utilizamos a função editStory para alterar a propriedade
story.editing para true, então o v-if irá deixar visível as caixas de texto e esconder
os botões de voto e remoção da história.
Agora, essa abordagem ainda não funciona! Parece que a DOM não está atualizando
após definirmos a propriedade story.editing para true. Mas porque isso acontece?
Acontece que, de acordo com esse artigo no blog do Vue.js5 , quando você está
adicionando uma nova propriedade que não estava registrada no objeto data, a
DOM não irá atualizar. A melhor prática é sempre declarar as propriedades que serão
reativas no contexto que está trabalhando. Isso significa que você deve adicionar
5
http://vuejs.org/2016/02/06/common-gotchas/
Visão Geral do axios 190
mounted: function() {
var vm = this;
// GET request
axios.get('/api/stories')
.then(function (response) {
// set data on vm
var storiesReady = response.data.map(function (story) {
story.editing = false;
return story
})
vm.stories = storiesReady
//Vue.set(vm, 'stories', storiesReady)
});
}
Informação
O método .map() chama uma função de callback para cada elemento do
Array e retorna um Array com os resultados. Você pode encontrar mais
informações sobre este método aqui6 .
Esta função adiciona o atributo editing a cada item do objeto story e então retorna
o Array atualizado.
6
https://msdn.microsoft.com/en-us/library/ff679976(v=vs.94).aspx
Visão Geral do axios 191
Formulário de edição
Então, vamos seguir em frente e adicionar dois novos botões, que devem ser exibidos
somente quando a história estiver sendo editada.
Além disso, um novo método chamado updateStory será criado. Ele vai atualizar a
história, após o botão Atualizar ser pressionado.
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 updateStory: function (story) {
6 axios.patch('/api/stories/' + story.id, story)
7 //Set editing to false to show actions again and hide th\
8 e inputs
9 story.editing = false;
10 },
11 }
12 ...
13 })
Atualizando histórias
Após a requisição PATCH terminar com sucesso, temos que alterar o valor de
story.editing de volta para false, para esconder as caixas de texto de edição e
os botões de ação.
Inicializaremos todos os atributos da história para null, exceto o editing. Uma vez
Visão Geral do axios 193
Informação
O método push() adiciona novos itens ao final de um Array e retorna
o novo comprimento. Você pode encontrar mais informações sobre o
método push() e sua sintaxe aqui7 .
Como o newStory.editing está definido como true, as entradas para plot e writer
junto com os botões de ação Edit, estão sendo renderizadas instantaneamente.
Além disso, o novo objeto story deve ser enviado ao servidor para ser armazenado
no banco de dados. Vamos executar uma solicitação POST dentro de um método
chamado storeStory.
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 storeStory: function(story){
6 axios.post('/api/stories/', story).then(function () {
7 story.editing = false;
8 });
9 },
10 }
11 ...
12 })
1 <td>
2 <div class="btn-group" v-if="!story.editing">
3 <button @click="upvoteStory(story)" class="btn btn-primary">
4 Upvote
5 </button>
6 <button @click="editStory(story)" class="btn btn-default">
7 Edit
8 </button>
9 <button @click="deleteStory(story)" class="btn btn-danger">
10 Delete
11 </button>
12 </div>
Visão Geral do axios 195
Um pequeno erro
Para contornar este problema, vamos reestruturar nossos botões. O botão Update será
apenas exibido quando a história for antiga. Consequentemente, o botão Save New
Story será apresentado quando a for uma nova história.
Você pode ter notado que todas as histórias obtidas do servidor têm um atributo id.
Vamos usar este detalhe para definir se uma história é nova ou não.
11 ">
12 Save New Story
13 </button>
14 <!--sempre exibir o cancelar-->
15 <button @click="story.editing=false" class="btn btn-default">
16 Cancel
17 </button>
18 </div>
Dica
Se a história vem do banco de dados, então ele terá um id.
Depois de terminar esta parte, ao testar a app nos traz outro erro.
Depois de criar, salvar e tentar editar uma nova história, vemos que o botão diz
“Salvar nova história” em vez de “Atualizar História”! Isso ocorre porque não estamos
Visão Geral do axios 198
buscando a nova história criada a partir do servidor, após enviá-lo, e ele ainda não
tem um id.
Para resolver esse problema, podemos novamente buscar as histórias do servidor,
logo depois de armazenar uma nova história no banco de dados.
Como não é bom repetir código, vamos extrair o procedimento de busca para um
método chamado fetchStories (). Depois disso, pode-se usar esse método para
buscar as histórias a qualquer momento.
O método fetchStories
1 var vm = new Vue({
2 el: '#v-app',
3 data : {
4 stories: [],
5 },
6 mounted: function(){
7 this.fetchStories()
8 },
9 methods: {
10 createStory: function(){
11 var newStory={
12 "plot": "",
13 "upvotes": 0,
14 "editing": true
15
16 };
17 this.stories.push(newStory);
18 },
19 fetchStories: function () {
20 var vm = this;
21 axios.get('/api/stories')
22 .then(function (response) {
23 var storiesReady = response.data.map(function (s\
24 tory) {
25 story.editing = false;
26 return story
Visão Geral do axios 199
27 })
28 // vm.stories = storiesReady
29 Vue.set(vm, 'stories', storiesReady)
30 // or: vm.stories = storiesReady
31 });
32 },
33 }
34 });
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 storeStory: function(story){
6 axios.post('/api/stories/', story).then(function () {
7 story.editing = false;
8 vm.fetchStories();
9 });
10 },
11 }
12 ...
13 })
Armazenar e Atualizar
Uma maneira melhor de corrigir o problema anterior, é buscar apenas o recém-criado
story do banco de dados, em vez de buscar e substituir todas as histórias.
Se você verificar a resposta do servidor, para a solicitação POST, você verá que ele
retorna o story criado junto com seu id.
Visão Geral do axios 200
A única coisa que temos a fazer, é atualizar nossa história para coincidir com a do
servidor. Então, vamos definir o id dos dados da resposta, para o atributo id da
história. Faremos isso dentro do retorno de chamada de sucesso do POST.
1 Vue.component('story',{
2 ...
3 methods: {
4 ...
5 storeStory: function(story){
6 axios.post('/api/stories/', story).then(function (re\
7 sponse) {
8 Vue.set(story, 'id', response.data.id);
9 story.editing = false
10 });
11 },
12 }
13 ...
14 })
Como a nova história não tinha id, quando é enviada para o array de stories, o
DOM não será atualizado quando o id mudar, então não seremos capazes de usar o
novo id.
Dica
Quando você está adicionando uma nova propriedade que não estava
presente quando os dados foram observados, Vue.js não consegue
detectar a adição da propriedade. Portanto, se você precisar adicionar
ou remover propriedades em tempo de execução, use os métodos globais
Vue.set ou Vue.delete.
Se você ainda não baixou o repositório, você ainda pode acessar stories.html8 e
app.js9 no github.
stories.html
1 <html lang="en">
2 <head>
3 <title>Stories</title>
4 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/boo\
5 tstrap/3.3.2/css/bootstrap.min.css">
6 </head>
7
8 <body>
9 <main>
10 <div class="container">
11 <h1>Stories</h1>
12 <div id="v-app">
13 <table class="table table-striped">
14 <tr>
15 <th>#</th>
16 <th>Plot</th>
17 <th>Writer</th>
18 <th>Upvotes</th>
19 <th>Actions</th>
20 </tr>
21 <tr v-for="story in stories" is="story" :story="stor\
22 y"></tr>
23 </table>
24 <p class="lead">Here's a list of all your stories.
25 <button @click="createStory()" class="btn btn-primar\
26 y">
27 Add a new one?
28 </button>
29 </p>
8
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/apis/stories/public/stories.html
9
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/apis/stories/public/js/app.js
Visão Geral do axios 203
65 Upvote
66 </button>
67 <button @click="editStory(story)" class="btn btn-def\
68 ault">
69 Edit
70 </button>
71 <button @click="deleteStory(story)"
72 class="btn btn-danger">
73 Delete
74 </button>
75 </div>
76 <div class="btn-group" v-else>
77 <!--If the story is taken from the db then it will h\
78 ave an id-->
79 <button v-if="story.id"
80 class="btn btn-primary"
81 @click="updateStory(story)">
82 Update Story
83 </button>
84
85 <!--If the story is new we want to store it-->
86 <button v-else class="btn btn-success"
87 @click="storeStory(story)">
88 Save New Story
89 </button>
90
91 <!--Always show cancel-->
92 <button @click="story.editing=false"
93 class="btn btn-default">
94 Cancel
95 </button>
96 </div>
97 </td>
98 </tr>
99 </template>
Visão Geral do axios 205
1 Vue.component('story', {
2 template: '#template-story-raw',
3 props: ['story'],
4 methods: {
5 deleteStory: function (story) {
6 var index = this.$parent.stories.indexOf(story);
7 this.$parent.stories.splice(index, 1)
8 axios.delete('/api/stories/' + story.id)
9 },
10 upvoteStory: function (story) {
11 story.upvotes++;
12 axios.patch('/api/stories/' + story.id, story)
13 },
14 editStory: function (story) {
15 story.editing = true;
16 },
17 updateStory: function (story) {
18 axios.patch('/api/stories/' + story.id, story)
19 //Set editing to false to show actions again and hide th\
20 e inputs
21 story.editing = false;
22 },
23 storeStory: function (story) {
24 axios.post('/api/stories/', story).then(function (respon\
25 se) {
26 //After the the new story is stored in the database \
27 fetch again all stories with
28 vm.fetchStories();
Visão Geral do axios 206
Código fonte
Você pode encontrar estes exemplos no GitHub10
13.7 Tarefa
Para se familiarizar com a criação de solicitações na web e o tratamento de respostas,
você deve replicar o que fizemos neste capítulo.
O que você tem a fazer é consumir uma API para:
preparamos o banco de dados e a API para você. Você só tem que escrever HTML e
JavaScript.### Configuração
Se você seguiu as instruções de [Capítulo 10] (# downloadcode), abra seu terminal e
execute:
cd ∼/themajestyofvuejs/apis/movies
sh setup.sh
mkdir ∼/themajestyofvuejs
cd ∼/themajestyofvuejs
git clone https://github.com/hootlex/the-majesty-of-vuejs .
cd ∼/themajestyofvuejs/apis/movies
sh setup.sh
10
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter12
Visão Geral do axios 209
Agora você tem um banco de dados preenchido com grandes filmes juntamente
com um servidor totalmente funcional executando em http://localhost:3000 !
Para garantir que tudo está funcionando bem, navegue até http://localhost:3000/api/movies
e você verá um Array de filmes no formato JSON.
API Endpoints
A API que você vai precisar é:
Seu código
Coloque o seu código HTML em ∼/themajestyofvuejs2/apis/movies/public/movies.html.
Você pode colocar o seu código JavaScript em js/app.js.
Para ver o seu código em funcionamento, acesse http://localhost:3000/movies.html no
navegador.
Espero que você aproveite!! Boa sorte!!
Visão Geral do axios 210
Solução sugerida
Você pode encontrar uma sugestão de solução deste exercício aqui11 .
11
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/homework/Chapter12
14. Paginação
No capítulo anterior, conseguimos buscar todos os registros do banco de dados e
exibi-los dentro de uma tabela.
Essa implementação é boa para um monte de registros, mas no mundo real, quando
você tem que trabalhar com milhares ou milhões de registros, você não pode
simplesmente colocá-los dentro de um Array.
Se você fizer isso, o navegador não será feliz para carregar tal quantidade de dados,
mas mesmo se ele consegue fazer isso, então eu garanto que nenhum usuário gosta
de lidar com uma tabela contendo 100.000 linhas.
Informação
Paginação é usada de alguma forma em quase todas as aplicações web para
dividir dados retornados e exibi-lo em várias páginas. Paginação também
inclui a lógica de preparar e exibir os links para as várias páginas e pode
ser manipulado do lado do cliente ou do lado do servidor. A paginação do
lado do servidor é mais comum.
{
"total": 10000,
"per_page": 50,
"current_page": 15,
"last_page": 200,
"next_page_url": "/api/stories?page=16",
"prev_page_url": "/api/stories?page=14",
"from": 751,
"to": 800,
"data": [...]
}
{
"data": [...],
"pagination": {
"total": 10000,
"per_page": 50,
"current_page": 15,
"last_page": 200,
"next_page_url": "/api/stories?page=16",
"prev_page_url": "/api/stories?page=14",
"from": 751,
"to": 800,
}
}
Paginação 213
14.1 Implementação
Vamos continuar a trabalhar com os exemplos de histórias do capítulo anterior,
usando a API com paginação. Então, vamos modificar o código, para poder acessar
e usar esses dados.
Se você der uma olhada no código do exemplo anterior, verá que o nosso método **
fetchStories ** é semelhante a este:
new Vue({
...
methods: {
...
fetchStories: function () {
var vm = this;
this.$http.get('/api/stories')
.then(function (response) {
var storiesReady = response.data.map(function (s\
tory) {
story.editing = false;
return story
})
Vue.set(vm, 'stories', storiesReady)
});
},
...
}
});
Se abrimos o arquivo HTML no navegador, como você já deve ter adivinhado, nossa
tabela não é processada corretamente.
Paginação 214
Isso acontece porque o Array stories agora são retornados dentro de um Array
chamado data **. Para corrigir isso, temos de alterar **response.data para
response.data.data (eu sei que isso é meio estranho, mas …).
Exceto pelo Array stories, também queremos salvar os dados da paginação dentro de
um objeto para poder implementar facilmente a funcionalidade de paginação.
Para descobrir como podemos acessar esses dados, vamos dar uma olhada na resposta
do servidor.
Paginação 215
Resposta do servidor
Para começar, nós não precisamos de todos esses dados. Então, vamos ficar com
current_page, last_page, next_page_url *, e *prev_page_url.
Nosso objeto de paginação será algo como isto:
pagination: {
"current_page": 15,
"last_page": 200,
"next_page_url": "/api/stories?page=16",
"prev_page_url": "/api/stories?page=14"
}
new Vue({
...
methods: {
...
fetchStories: function () {
var vm = this;
this.$http.get('/api/stories')
.then(function (response) {
var storiesReady = response.data.data.map(functi\
on (story) {
story.editing = false;
return story
})
//here we use response.data
var pagination = {
current_page: response.data.current_page,
last_page: response.data.last_page,
next_page_url: response.data.next_page_url,
prev_page_url: response.data.prev_page_url
}
Vue.set(vm, 'stories', storiesReady)
Vue.set(vm, 'pagination', pagination)
});
},
...
}
});
14.2 Links
Até agora, temos o nosso objeto pagination, mas sempre buscamos a primeira página
do Array stories, uma vez que estamos fazendo uma solicitação GET HTTP para
Paginação 217
api/ stories. Temos de alterar a página solicitada, com base na interação do usuário
(página seguinte, página anterior).
Primeiro vamos atualizar o método fetchStories para aceitar um argumento com a
página desejada. Se nenhum argumento for passado, ele buscará a primeira página.
Também criarei um novo método, makePagination, para tornar o código mais limpo.
new Vue({
...
methods: {
...
fetchStories: function (page_url) {
var vm = this;
page_url = page_url || '/api/stories'
this.$http.get(page_url)
.then(function (response) {
var storiesReady = response.data.data.map(functi\
on (story) {
story.editing = false;
return story
})
vm.makePagination(response.data)
Vue.set(vm, 'stories', storiesReady)
});
},
makePagination: function (data){
//here we use response.data
var pagination = {
current_page: data.current_page,
last_page: data.last_page,
next_page_url: data.next_page_url,
prev_page_url: data.prev_page_url
}
Vue.set(vm, 'pagination', pagination)
}
...
Paginação 218
}
}
Agora que nosso método está pronto, precisamos de uma maneira de chamá-lo
corretamente. Vamos adicionar 2 botões, um para o próximo e um para a página
anterior, no topo do nosso #app div.
Cada botão chamará o método fetchStories quando clicado, passando a url da
página correspondente.
1 <div class="pagination">
2 <button @click="fetchStories(pagination.prev_page_url)">
3 Previous
4 </button>
5 <button @click="fetchStories(pagination.next_page_url)">
6 Next
7 </button>
8 </div>
Se você tentar clicar nos botões, verá que eles funcionam conforme o esperado. Temos
a paginação num piscar de olhos. No entanto, será útil informar o usuário sobre qual
página ele está olhando atualmente e o número total de páginas.
Além disso, podemos desativar o botão anterior quando o usuário estiver na primeira
página e o próximo na última página.
1 <div class="pagination">
2 <button @click="fetchStories(pagination.prev_page_url)"
3 :disabled="!pagination.prev_page_url"
4 >
5 Previous
6 </button>
7 <span>Page {{pagination.current_page}} of {{pagination.last_page\
8 }}</span>
9 <button @click="fetchStories(pagination.next_page_url)"
10 :disabled="!pagination.next_page_url"
Paginação 219
11 >
12 Next
13 </button>
14 </div>
Código fonte
Você pode encontrar estes exemplos no GitHub1 .
14.3 Tarefa
Não há nada especial a fazer neste capítulo. Se você realmente quiser trabalhar neste
exemplo, vamos fornecer a API paginada.
1
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter13
Paginação 220
Se você tiver resolvido a tarefa do capítulo anterior, você está há apenas alguns
cliques de terminar esta tarefa. Se você não tiver, basta seguir [estas instruções] (#
downloadcode).
A API pagina está neste diretório ’∼/themajestyofvuejs2/apis/pagination/stories’
O arquivo HTML está neste diretório ’∼/themajestyofvuejs2/apis/pagination/stories/public’
Se você só quer ver o código final, você pode dar uma olhada nos arquivos no
GitHub2 .
2
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/apis/pagination/stories/public
III Criando aplicações em larga
escala
15. ECMAScript 6
Antes de dar um passo adiante e ver como podemos criar aplicativos em larga escala,
gostaria de familiarizá-lo com o ECMAScript 6.
Informação
ECMAScript é uma especificação da linguagem de script no lado do
cliente, que é a base de várias linguagens de programação, incluindo
JavaScript, ActionScript, e JScript.
15.1 Introdução
O ES6 possui diversas novas funcionalidades. Vamos analisar aquelas que usaremos
nos próximos capítulos. Se você está interessado em saber mais sobre o que é novo
no ES6, eu recomendo o livro “Compreendendo o ECMAScript 6” por Nicholas C.
Zakas disponível no leanpub2 . Existe uma versão online3 do livro, no qual você pode
ler gratuitamente.
Além disso, existem outros recursos úteis e tutoriais, como o Babel4 , um artigo no
tutsplus5 , um post6 escrito por Nicholas C. Zakas e uma série de coisas na web.
1
http://kangax.github.io/compat-table/es6/
2
https://leanpub.com/understandinges6/
3
https://leanpub.com/understandinges6/read
4
https://babeljs.io/docs/learn-es2015/
5
http://code.tutsplus.com/articles/use-ecmascript-6-today--net-31582
6
https://www.nczonline.net/blog/2013/09/10/understanding-ecmascript-6-arrow-functions/
ECMAScript 6 223
Compatibilidade
Não é de surpreender que o suporte varie de forma variável em cada engine, com a
Mozilla tendendo a liderar o caminho. A tabela de compatibilidade do ES67 é uma
fonte útil para verificar os navegadores que suportam ECMAScript 6.
Nota
Se você estiver usando o Chrome, a maioria dos recursos ES6 estão
escondidos atrás de uma configuração. Acesse chrome://flags, encontre a
seção intitulada “Ativar JavaScript Experimental” e ative-o para ativar o
suporte.
Declaração Let
let é o novo var. Você pode basicamente substituir var com let para declarar uma
variável quando desejar limitar o escopo da variável apenas para o bloco de código
atual.
É melhor usar a declaração let no início do bloco, para que fiquem disponíveis em
todo o escopo daquele bloco. Exemplo:
7
https://kangax.github.io/compat-table/es6/
ECMAScript 6 224
let dentro do if
1 let age = 22
2 if (age >= 18) {
3 let adult = true;
4 console.log(adult); //saída: true
5 }
6 //adult não está acessível aqui
7 console.log(adult);
8 //ERRO: Uncaught ReferenceError: adult is not defined
Let on top
1 let age = 22
2 let adult
3 if (age >= 18) {
4 adult = true;
5 console.log(adult); //saída: true
6 }
7 //agora adult é acessível aqui
8 console.log(adult); //outputs
Constantes
Constantes, como a declaração let, são declarações a nível de bloco. Existe uma
grande diferença entre let e const. Uma vez que tenha declarado uma variável com
const, ela é definida como uma constante, o que significa que você não pode alterar
o seu valor.
Informação
Como constantes em outras linguagens de programação, seu valor não
pode ser modificado mais tarde. No entanto, ao contrário de constantes
em outras linguagens, o valor que uma constante mantém pode ser
modificado se for um objeto.
// equivalent to:
Outro exemplo com Arrow Functions, obter 2 argumentos e retorna sua soma:
ECMAScript 6 226
// equivalente a:
Um exemplo de Arrow Function sem argumentos, e que usa mais de uma linha,
limitada pela declaração “{ }”:
// equivalente a:
15.4 Módulos
Esta é, para mim, uma das melhores melhorias na linguagem. ES6 agora suporta
exportar e importar módulos através de arquivos diferentes. O mais simples exemplo
é criar um arquivo .js com uma variável, e usá-la dentro de outro arquivo como a
seguir:
ECMAScript 6 227
module.js
main.js
Você também pode exportar variáveis junto com funções uma a uma.
module.js
main.js
Ou dentro de um objeto:
ECMAScript 6 228
module.js
main.js
15.5 Classes
As classes JavaScript foram introduzidas no ECMAScript 6 e são como melhorias
visuais no sistema de herança já existente definidos através do prototype.
A sintaxe de classe não é um novo modelo de orientação a objetos definido para o
JavaScript. Classes JavaScript proveem uma forma mais simples de criar objetos que
usam heranças.
Exemplo de classes
//parent class
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
calcArea() {
return this.height * this.width;
}
ECMAScript 6 229
//child class
class Square extends Rectangle{
constructor(side) {
//call parent's constructor
super(side, side)
}
}
console.log(square.area); //outputs 25
// equivalent to:
// equivalent to:
console.log('Olá '+ name )
// equivalent to:
console.log('If you have ' + a +' eggs and you buy ' + b +
'\nmore you\'ll have '+ add(a,b) + ' eggs!')
ECMAScript 6 231
// equivalent to:
console.log('If you have ' + a +' eggs and you buy ' + b +
'\nmore you\'ll have '+ (a + b)*1 + ' eggs!')
Informação
Um compilador source-to-source, também chamado de transcompiler ou
transpiler, é um tipo de compilador que leva o código-fonte de um
programa escrito em uma linguagem de programação, como sua entrada,
e produz o código fonte equivalente em outra linguagem de programação.
Antes de instalar babel, você precisa instalar Node.js. Para fazer isso, acesse Node’s
website1 e clique no botão de download, na última versão estável. Você será levado a
ao download de um arquivo .pkg para mac, .msi para windows ou .deb para sistemas
Linux baseados no Debian. Quando o download estiver terminado, abra o arquivo e
siga as instruções de instalação.
Node.js
1
https://nodejs.org/en/
Workflow Avançado 234
Instalação do Babel
Crie um novo diretório e crie um arquivo chamado package.json, contendo um
objeto JSON vazio ({}). Você pode fazer isso manualmente, ou executando este
comando:
mkdir babel-example
Nota do tradutor: Você pode usar o seguinte comando também npm init -y
Para instalar o Babel, execute o seguinte comando:
Saída no Terminal
package.js
{
"devDependencies": {
"babel-cli": "^6.18.0"
}
}
O que é package.json?
Um arquivo package.json contém metadados sobre sua aplicação ou
módulo. Mais importante, ele inclui uma lista de dependências para serem
instaladas quando executar o comando npm install. Se você conhece o
Composer, ele é familiar ao arquivo composer.json.
Para saber mais sobre o package.json dê uma olhada na documentação
npm2 .
Diretório do Projeto
Configuração
Agora que temos o babel instalado, precisamos dizer explicitamente quais as trans-
formações a serem executadas na compilação. Como queremos transformar o código
ES2015, instalaremos o ES2015-Preset3 .
Vamos também criar o arquivo .babelrc para ativar esta configuração:
Dica
Se o segundo comando falhar, inclua o conteúdo do arquivo dentro de
aspas como esta:
echo '{ "presets": [ ["es2015"] ]}' > .babelrc
package.js
{
"scripts": {
"build": "babel src -d assets/js"
},
"devDependencies": {
"babel-cli": "^6.8.0",
"babel-preset-es2015": "^6.18.0"
}
}
Isso irá funcionar como um alias (apelido). Quando executarmos o comando npm run
build estaremos executando na verdade o comando babel src -d assets/js. Este
comando diz ao Babel para “transpilar” o código do diretório src para o diretório
assets/js.
Antes de executar o comando build precisamos fazer algumas coisas. Primeiro, crie
os diretórios mencionados, src e assets/js.
Uso
Vamos continuar e colocar algum arquivo dentro do diretório src. Vamos criar um
simples arquivo com a função sum e chamá-lo de sum.js.
Workflow Avançado 238
src/sum.js
Quando executar isso, você verá no terminal que o arquivo src\sum.js será compi-
lado para assets\js\sum.js e se parecerá como isso:
assets/js/sum.js
"use strict";
De agora em diante, sempre que quiser compilar seu código ES6, você pode fazê-lo
executando o comando npm run build.
É hora de ver o resultado sum.js no navegador. Crie o arquivo sum.html e inclua o
js.
Workflow Avançado 239
sum.html
<!DOCTYPE html>
<html>
<head>
<title>Babel Example</title>
</head>
<body>
<h1>Babel Example</h1>
<script src="assets/js/sum.js"></script>
</body>
</html>
Saída no navegador
Workflow Avançado 240
Como você pode ver, o resultado da função sum é impresso com sucesso no console.
Informação
Quando você quer testar um arquivo .js, mas não deseja testar no navega-
dor, você pode executá-lo com Node.js.
No exemplo sum.js onde há um console.log(sum(5,3)), se você digitar
no terminal node sum.js você verá o resultado 8.
Tarefa
Este exercício visa ajudá-lo a lembrar o que aprendeu ao reproduzir o exemplo que
construímos. Em vez do sum.js continue a usar o ES6 para criar um arquivo Ninja.js
que irá conter uma classe chamada Ninja.
A classe Ninja deverá ter um propriedade chamada name e um método chamado
announce (anunciar) no qual irá alertar a presença do Ninja.
For example
new Ninja('Leonardo').announce()
//alerts "Ninja Leonardo is here!"
Dica
Você pode encontrar exemplos da criação de classes no capítulo anterior
Dica 2
Não se esqueça de executar npm run build cada vez que fizer alterações
no seu arquivo js, ou então ele não vai ser atualizado no navegador.
Workflow Avançado 241
Solução sugerida
Você pode encontrar uma sugestão de solução deste exercício aqui4 .
4
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/homework/Chapter15/chapter15.1
Workflow Avançado 242
Task Runners
Se você dedicou algum tempo para desenvolver a aplicativo do exercício anterior,
provavelmente descobriu que é um pouco irritante ter que executar npm run build
toda vez que você faz uma alteração em seu código.
É aí que Task Runners, como [Gulp] (http://gulpjs.com/) ou [Grunt] (http://gruntjs.com/)
são úteis. Os Task Runners permitem que você automatize e aprimore seu fluxo de
trabalho.
Gulp
Gulp vs Grunt
Grunt, como Gulp, é uma ferramenta para executar tarefas. A principal
diferença entre Grunt e Gulp é que Grunt define tarefas usando objetos
de configuração enquanto o Gulp define tarefas como Funções de
JavaScript. Uma vez que o Gulp usa Javascript, fornece mais flexibilidade
para escrever suas tarefas.
Ambos têm uma enorme biblioteca de plugins, onde você pode encontrar um que
implementa uma tarefa que você precisa.
Instalação
Vou mostrar-lhe um exemplo de como você pode usar o Gulp para assistir as
mudanças em seus arquivos js e executar automaticamente o comando build.
Primeiro temos que instalar o Gulp globalmente:
gulpfile.js
const gulp = require('gulp');
gulp.task('default', function() {
// place the code for your default task here
});
Uso
Quando executamos o gulp no nosso console, ele começa, mas ainda não faz nada.
Temos que configurar uma tarefa padrão.
Para executar o babel diretamente, vou instalar um plugin chamado [gulp-babel]
(https://www.npmjs.com/package/gulp-babel).
Vou adicionar uma nova tarefa gulp chamada babel e configurá-la como a tarefa
padrão. Meu gulpfile ficará assim:
gulpfile.js
const gulp = require('gulp');
const babel = require('gulp-babel');
gulp.task('default', ['babel']);
Watch (Observador)
Atualmente, o gulp no seu console tem o mesmo efeito com npm run build. O que
queremos alcançar aqui é executar esta tarefa sempre que um arquivo js foi alterado.
Para fazer isso, vamos configurar um watcher (observador) dentro do nosso arquivo
gulpfile como este:
gulpfile.js
gulp.task('default', ['watch']);
Tarefa
Este exercício segue o anterior. Se você não fez o anterior, nunca será tarde demais
para começar!
Uma vez que esta parte do capítulo é dedicada a Task Runners, você precisa
configurar um watcher com o Gulp e compilar seu código com Babel, quando uma
mudança for detectada.
Nota
Você já percebeu que, ao executar o Gulp, imprime mensagens no terminal
(“Iniciando” - “Terminado”), então não seja tão precipitado e aguarde
que as mudanças sejam aplicadas.
Solução
Você pode encontrar uma sugestão de solução deste exercício aqui5 .
5
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/homework/Chapter15/chapter15.2
Workflow Avançado 247
Module Bundlers
Nosso workflow está indo bem com o código atual de sum.js. Vamos ampliar suas
características, para calcular o custo de uma pizza e uma cerveja e exibir para o
cliente.
src/sum.js
const pizza = 10
const beer = 5
Este código parece bom, mas assumindo que nem todos os clientes se chamam Alex,
vamos criar um novo arquivo, client.js, que fornecerá o nome do cliente.
Workflow Avançado 248
src/client.js
src/sum.js
const pizza = 10
const beer = 5
Saída de sum.js
assets/js/sum.js
Webpack
Webpack é um Module Bundler. Ele lida com os módulos em JavaScript, compre-
endendo suas dependências, e junta todas elas e produz um único arquivo que
representa estes módulos.
Usaremos o Webpack nos próximos exemplos. Usando o que chamamos de loaders,
podemos fazer com que o Webpack transforme todo o tipo de arquivo, antes de criar
o arquivo final.
6
https://webpack.github.io/
7
http://browserify.org/
Workflow Avançado 251
Instalação
Vamos instalar o Webpack globalmente e depois adicioná-lo como dependência em
nosso projeto.
Dica
No momento em que criávamos esta obra, existia um erro conhecido se
você estiver utilizando o Vagrant8 no Windows, executando npm install
pode acarretar e, falha. Para resolver este problema, saia do Vargrant e use
o terminal do Windows para executar npm install dele.
Uso
Para compilar o Javascript, devemos dar ao Webpack um ponto de origem e uma
saída. No nosso caso, o ponto de origem é assets/js/sum.js e a saída é assets/web-
packed/app.js.
8
https://www.vagrantup.com/
Workflow Avançado 252
Saída do Webpack
Automação
Se você é preguiçoso, como eu, e você não gosta de ter que executar webpack toda
vez que fizer uma mudança, pode automatizar todo o processo feito até agora. Você
pode configurar o Webpack para observar o código fonte e refazer a tarefa, quando
qualquer um dos seus arquivos mudar. Não vou fazer isso aqui. Em vez disso, vou
integrá-lo no Gulp, para demonstrar como você pode combinar várias ferramentas.
Leia também
Se você quiser saber mais sobre como o Webpack funciona e como você
pode configurá-lo, acesse “Beginner’s guide to Webpack”9 escrito por
Nader Dabit10 .
9
https://medium.com/@dabit3/beginner-s-guide-to-webpack-b1f1a3638460
10
https://twitter.com/dabit3
Workflow Avançado 253
Após a instalação, criarei uma nova tarefa com o nome de ‘webpack’ e direi ao Gulp
para executá-lo sempre que detecte uma alteração, imediatamente depois de executar
a tarefa ‘babel’.
gulpfile.js
gulp.task('default', ['watch']);
})
}
}))
.pipe(gulp.dest('assets/webpacked'));
})
Esta solução não é a ideal. É apenas uma demonstração de como você pode vincular
tudo o que você aprendeu até agora. Em ambiente de produção, existem maneiras
muito melhores de automatizar suas tarefas com o webpack.
Workflow Avançado 255
Webpack no Gulp
16.4 Resumo
Quando você deseja compilar ES6, você pode usar Babel12 .
Para automatizar operações como esta e muitos outras (como minify, compilação de
SASS / LESS, etc.), você precisa de Task Runenrs como Gulp13 ou Grunt14 .
12
http://babeljs.io/
13
http://gulpjs.com/
14
http://gruntjs.com/
Workflow Avançado 256
Nota
Se você achou este capítulo difícil de entender, não se preocupe. Você não
precisa se lembrar de todas essas coisas. Esta foi apenas uma demonstração
para dar uma melhor compreensão de como as coisas funcionam. No
próximo capítulo usaremos project-templates. In the next chapter we will
use project-templates. Lá, coisas como bundle, automation tasks **,
**compilar na alteração do código e muito mais, já estão implementados
e nós vamos aproveitá-los.
15
https://webpack.github.io/
16
http://browserify.org/
17. Trabalhando com Single File
Components
Neste capítulo vamos analisar uma funcionalidade do Vue chamada Single File
Components. Para usar esta funcionalidade precisamos de uma ferramenta como
Webpack com vue-loader, ou Browserify com vueify. Para os nossos exemplos,
vamos usar Webpack, no qual já vimos como funciona. Se você preferir Browserify,
sinta-se livre para usá-lo.
Single File Components encapsula o estilo CSS, o template e o código JavaScript, tudo
em um arquivo usando um arquivo com a extenção .vue. E é aí que o webpack entra,
engloba esse novo tipo de arquivo com os outros arquivos.
Webpack usa vue-loader1 para transformar os componentes do Vue em módulos
JavaScript. vue-loader também fornece um conjunto muito bom de recursos, como
ES2015 habilitado por padrão, CSS com o escopo somente para o componente, e muito
mais.
17.1 O vue-cli
Para evitar a configuração do Webpack e criar tudo de novo a partir do nada,
usaremos vue-cli.
Informação
vue-cli2 é uma simples ferramenta de linha de comando para criar projetos
Vue.js
Esta ótima ferramenta é a maneira mais rápida de obter uma aplicação vue pré
configurada. Ela oferece templates com hot-reload, lint-on-save, unit testing, e muito
1
https://github.com/vuejs/vue-loader
2
https://github.com/vuejs/vue-cli
Trabalhando com Single File Components 258
mais. Atualmente, ela oferece templates para webpack e browserify, mas se precisar,
você pode criar o seu próprio template3 .
Templates Vue’s
O que o CLI faz, é baixar os templates do repositório Vue.js oficial4 no qual existem
5 templates até o momento. Eu acredito que este número crescerá no futuro. Você
pode verificar se eles incluíram algo pelo GitHub.
Todos os templates tem um arquivo package.json, no qual gerencia as dependências
de projeto e vem com scripts NPM prontos para uso.
Usando estes templates, você obtém um monte de funcionalidades trabalhando em
conjunto. Por exemplo, o template “webpack” diz: “Uma solução completa com
Webpack + vue-loader e hot reload, linting, testing & css extraction.”
Instalação
Para usar a configuração do Webpack vamos instalar o vue-cli globalmente usando
o seguinte comando:
Uso
Usando o CLI você pode executar vue init <template-name> <project-name>
onde o <template-name> é o nome do template (oficial ou personalizado) e o
<project-name> é o nome do diretório/projeto que você estará criando.
Dica
Use vue list para ver todos os templates oficiais disponíveis.
Trabalhando com Single File Components 260
Informação
Quando você inicia um novo projeto será perguntado sobre alguns deta-
lhes, como o nome do projeto, a versão, autor etc. Toda vez que usarmos
o CLI para criar um novo projeto, iremos escolher a opção Runtime +
Compiler, porque precisamos compilar o template em tempo real. Você
pode encontrar explicações detalhadas de diferentes compilações no guia5
oficial.
Em algum momento, você será perguntado sobre Pick an ESlint preset. feross/stan-
dard6 e airbnb/javascript7 .
Sobre o ESLint, criei uma tabela para comparar os dois estilos, para que você obtenha
uma melhor compreensão das regras de cada estilo e de como elas se aplicam.
Standard vs Airbnb
As regras da tabela são algumas das mais aplicadas em cada estilo. Para
considerar e decidir o que lhe convém o melhor, verifique seus repositórios
Github.
5
https://vuejs.org/v2/guide/installation.html#Explanation-of-Different-Builds
6
https://github.com/feross/standard
7
https://github.com/airbnb/javascript
8
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas
Trabalhando com Single File Components 261
Depois de selecionar um estilo, você obterá algumas instruções sobre como instalar
várias ferramentas como Karma-Mocha9 e Nightwatch10 . Não vamos precisar dessas
ferramentas no momento, então responda “no” as questões e continue.
9
https://github.com/karma-runner/karma-mocha
10
http://nightwatchjs.org/
Trabalhando com Single File Components 262
Informação
Karma é um plugin para o framework de testes Mocha11 .
Nightwatch permite a gravação de testes automatizados do navegador,
que são executados no servidor Selenium12 .
cd stories-classic-project
npm install
Servidor executando…
Atenção
Seja cuidadoso, você precisa escrever código bem formatado. Caso contrá-
rio, você receberá erros por linhas extras entre blocos, espaços em branco,
identação excessiva etc., e outras coisas que não seguem as regras de estilo.
Trabalhando com Single File Components 264
Erros
Nota
Se você usar webpack-simple você ainda terá as funcionalidades básicas,
mas o erro não será exibida no navegador, então verifique o terminal para
quaisquer erros.
Estrutura do Projeto
Depois de concluir as etapas acima, você deve ter um diretório de projeto preenchido
com todos os arquivos necessários.
Trabalhando com Single File Components 265
Estrutura Webpack
1. index.html
2. main.js
3. arquivos dentro de src e src/components
index.html
Vamos começar com o index.html. Ele deve ser semelhante a:
Trabalhando com Single File Components 266
index.html
<html>
<head>
<meta charset="utf-8">
<title>stories-classic-project</title>
</head>
<body>
<app></app>
<!-- built files will be auto injected -->
</body>
</html>
Como você pode ver, é um html bastante básico com um componente já incluído.
O comentário refere-se ao script, app.js, no qual é o resultado do Webpack.
Significa basicamente que depois que o Webpack compilou os scripts, ele injetará
automaticamente o “script”, para que você não precise incluí-lo manualmente.
Hello.vue
Continuando, vá até src/components e abra o arquivo Hello.vue para ver como um
arquivo .vue se parece.
src/components/Hello.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h2>Essential Links</h2>
...
</div>
</template>
<script>
export default {
Trabalhando com Single File Components 267
name: 'hello',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
...
</style>
Atenção
Cada arquivo .vue não deve conter mais de um bloco <script> . Todo
template deve conter exatamente um único elemento raiz, como <div
id=hello>...</div> que encapsula todos os outros elementos.
ES6 nos permite ter qualquer número de exportações, mas não é o caso do single file
components.
Se você tentar fazer exports adicionais, você receberá uma erro semelhante a este:
[vue-loader] src/components/Hello.vue: named exports in /*.vue files are
ignored.
O bloco <style> define os estilos CSS.
App.vue
O arquivo App.vue localizado no diretório src contém o template principal da apli-
cação. Este componente geralmente é responsável por incluir os outros componentes.
App.vue tem mais algumas linhas a mais com textos e estilos, mas, como nos
concentramos na estrutura, reduzimos ele um pouco:
src/App.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<hello></hello>
</div>
</template>
<script>
import Hello from './components/Hello'
export default {
name: 'app',
components: {
Hello
}
}
</script>
<style>
Trabalhando com Single File Components 269
...
</style>
Temos a mesma estrutura que o arquivo Hello.vue que vimos antes. Por padrão,
existe a propriedade components que contém o componente Hello . Dentro desta
propriedade, vamos importar quaisquer novos componentes. No template, há a tag
<hello></hello>, e portanto o template do componente Hello será exbido.
Página do projeto
main.js
O arquivo main.js no diretório src, como você deve imaginar, é o nosso script
principal.
Trabalhando com Single File Components 270
src/main.js
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
Nota
A opção de template: '<App/>' representa o template que será injetado
no arquivo index.hmtl.
<App/> tem o mesmo resultado que <App></App> ou <app></app>.
Informação
Você pode encontrar mais informações sobre o Estrutura do projeto do
template Webpack na documentação13
13
http://vuejs-templates.github.io/webpack/structure.html
Trabalhando com Single File Components 271
Vimos como é formado um projeto Vue and e como ele funciona. É hora de criar um
pequeno cenário para testarmos todo o processo.
Suponha que queremos criar algum tipo rede social ou fórum, onde os usuários
publicam suas histórias e experiências.
Para criar a nossa app, vamos precisar de 2 formulários, um para registro e login, e
uma página para exibir as histórias dos usuários.
Antes de começarmos, incluímos o Bootstrap globalmente para que possamos usar
os estilos dentro de todos os componentes. Para fazer isso, vamos atualizar o arquivo
index.html.
index.html
<html>
<head>
<meta charset="utf-8">
<title>stories-classic-project</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/boo\
tstrap/3.3.6/css/bootstrap.min.css">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
src/components/Login.vue
<template>
<div id="login">
<h2>Sign in</h2>
<input type="email" placeholder="Email address">
<input type="password" placeholder="Password">
<button class="btn">Sign in</button>
</div>
</template>
<script>
export default {
created () {
console.log('login')
}
}
</script>
src/App.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<hello></hello>
</div>
</template>
<script>
import Login from './components/Login.vue'
import Hello from './components/Hello'
Trabalhando com Single File Components 273
export default {
name: 'app',
components: {
Hello,
Login
}
}
</script>
<style>
...
</style>
Se você recarregar o seu navegador, você ainda não verá o componente Login, porque
precisamos referência-lo. Coloque-o abaixo do componente <hello></hello> e você
verá um formulário de login!
src/App.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<hello></hello>
<login></login>
</div>
</template>
...
...
Trabalhando com Single File Components 274
Componente de Login
Se você abrir o console do navegador, você verá a mensagem login que estamos
emitindo quando o componente é criado. Se você estiver usando o plugin vue-
devtools, o que é altamente recomendado, você também deve vê-lo na exibição de
árvore de componentes.
Trabalhando com Single File Components 275
Árvore de componentes
src/components/Register.vue
<template>
<div id="register">
<h2>Register Form</h2>
<input placeholder="First Name" class="form-control">
<input placeholder="Last Name" class="form-control">
<input placeholder="Email address" class="form-control">
<input placeholder="Pick a password" class="form-control">
<input placeholder="Confirm password" class="form-control">
<button class="btn">Sign up</button>
</div>
</template>
Trabalhando com Single File Components 276
<script>
export default {
created () {
console.log('register')
}
}
</script>
src/App.vue
<template>
<div id="app">
...
<!-- <hello></hello> -->
<!-- <login></login> -->
<register></register>
...
</div>
</template>
<script>
// import Hello from './components/Hello'
// import Login from './components/Login'
import Register from './components/Register'
export default {
name: 'app',
components: {
// Hello,
// Login,
Register,
}
}
Trabalhando com Single File Components 277
</script>
...
...
Componente register
Nota
Os outros componentes estão comentados porque não queremos exibir
eles um abaixo do outro. O componente Hello está lá por padrão, mas não
vamos mais usá-lo nos exemplos seguintes, então vamos removê-lo.
Dissemos que estamos criando em uma rede social (ou algo relevante), então
queremos um lugar para exibir as histórias.
Portanto, vamos criar um componente Stories que quando é renderizado, trará todas
as histórias contadas pelos usuários.
src/components/Stories.vue
<template>
<ul class="list-group">
<li v-for="story in stories" class="list-group-item">
{{ story.writer }} said "{{ story.plot }}"
Story upvotes {{ story.upvotes }}.
</li>
</ul>
</template>
<script>
export default {
data () {
Trabalhando com Single File Components 278
return {
stories: [
{
plot: 'My horse is amazing.',
writer: 'Mr. Weebl',
upvotes: 28,
voted: false
},
{
plot: 'Narwhals invented Shish Kebab.',
writer: 'Mr. Weebl',
upvotes: 8,
voted: false
},
{
plot: 'The dark side of the Force is stronger.',
writer: 'Darth Vader',
upvotes: 52,
voted: false
},
{
plot: 'One does not simply walk into Mordor',
writer: 'Boromir',
upvotes: 74,
voted: false
}
]
}
}
}
</script>
src/App.vue
<template>
<div id="app">
<img class="logo" src="./assets/logo.png">
<!-- <login></login> -->
<!-- <register></register> -->
<stories></stories>
</div>
</template>
<script>
// import Login from './components/Login.vue'
// import Register from './components/Register.vue'
import Stories from './components/Stories.vue'
export default {
components: {
Login,
Register,
Stories
}
}
</script>
<style>
...
</style>
Componente Stories
Aninhado Componentes
Gostaríamos de poder exibir as histórias mais “famosas”, em qualquer lugar da
aplicação. Então, após a criação do componente Famous, devemos poder usá-lo em
qualquer lugar.
src/components/Famous.vue
<template>
<div id="famous">
<h2>Trending stories<strong>({{famous.length}})</strong></h2>
<ul class="list-group">
<li v-for="story in famous" class="list-group-item">
{{ story.writer }} said "{{ story.plot }}".
Story upvotes {{ story.upvotes }}.
</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
famous () {
return this.stories.filter(function (item) {
return item.upvotes > 50
})
}
},
data () {
return {
stories: [
{
plot: 'My horse is amazing.',
writer: 'Mr. Weebl',
upvotes: 28,
Trabalhando com Single File Components 281
voted: false
},
{
plot: 'Narwhals invented Shish Kebab.',
writer: 'Mr. Weebl',
upvotes: 8,
voted: false
},
{
plot: 'The dark side of the Force is stronger.',
writer: 'Darth Vader',
upvotes: 52,
voted: false
},
{
plot: 'One does not simply walk into Mordor',
writer: 'Boromir',
upvotes: 74,
voted: false
}
]
}
}
}
</script>
Nota
O Array stories está “hard coded” aqui e os dados são os mesmos que
antes. Esta é uma má prática, encontraremos um caminho melhor mais
tarde para definir o Array stories uma única vez e compartilhar com
todos os outros componentes.
Trabalhando com Single File Components 282
Mas onde podemos usar esse componente? Uma idéia é tê-lo dentro da página de
registro, Então o usuário pode ler as histórias mais tendenciosas e ficar curioso. Isso
significa - no projeto atual - que precisamos ter o componente Famous dentro do
componente Register.
Bem, isso pode ser feito da mesma maneira que fizemos no App.vue.
Então, abra Register.vue, importe-o, e faça referência ao template.
src/components/Register.vue
<template>
<div id="register">
<h2>Register Form</h2>
...
<famous></famous>
</div>
</template>
<script>
import Famous from './Famous.vue'
export default {
components: {
Famous
},
created () {
console.log('register')
}
}
</script>
Trabalhando com Single File Components 283
isso!
Código Fonte
Você pode encontrar o código fonte deste capítulo em GitHub14 .
14
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter16/16.3
18. Eliminando Dados
Duplicados
Nos exemplos anteriores, nós adicionamos os dados diretamente na propriedade data
do objeto Vue, o Array de histórias, dentro de cada componente. Esta não é uma
maneira correta de trabalhar com dados.
Quando criamos mais de um componente que usa os mesmos dados, é uma boa
prática criar/buscar o Array de dados uma única vez, e então encontrar uma maneira
de compartilhá-lo entre os componentes do aplicativo.
Stories.vue e Famous.vue estão usando o mesmo Array stories. Analisaremos duas
maneiras de compartilhar os dados:
src/App.vue
1 <script>
2 ...
3
4 export default {
5 components: {
6 ...
7 },
8 data () {
Eliminando Dados Duplicados 286
src/components/Stories.vue
1 <script>
2 export default {
3 props: ['stories']
4 }
5 </script>
src/App.vue
1 <template>
2 <div id="app">
3 ...
4 <stories :stories="stories"></stories>
5 ...
6 <p>
7 Welcome to your Vue.js app!
8 </p>
9 </div>
10 </template>
src/App.vue
1 <template>
2 <div id="app">
3 ...
4 <register :stories="stories"></register>
5 ...
6 </div>
7 </template>
src/components/Register.vue
1 <template>
2 <h2>Register Form</h2>
3 ...
4 <famous :stories="stories"></famous>
5 </template>
6
7 <script>
8 import Famous from './Famous'
9
10 export default {
11 components: {
12 Famous
13 },
14 props: ['stories']
15 }
16 </script>
Eliminando Dados Duplicados 290
src/components/Famous.vue
1 <script>
2 export default {
3 props: ['stories'],
4
5 computed: {
6 famous () {
7 return this.stories.filter(function (item) {
8 return item.upvotes > 50
9 })
10 }
11 }
12 }
13 </script>
src/store.js
Atenção
A propriedade stories deve ser removido de todos os arquivos, porque
mudamos o modo de armazenamento de dados e pode haver conflitos.
Eliminando Dados Duplicados 292
src/components/Stories.vue
1 <script>
2 import {store} from '../store.js'
3
4 export default {
5 data () {
6 return {
7 //will give us access to store.stories
8 store
9 }
10 },
11 created () {
12 console.log('stories')
13 }
14 }
15 </script>
Já que estamos importando o Array store também temos que mudar o template do
componente.
src/components/Stories.vue
1 <template>
2 <ul class="list-group">
3 <li v-for="story in store.stories" class="list-group-item">
4 {{ story.writer }} said "{{ story.plot }}"
5 Story upvotes {{ story.upvotes }}.
6 </li>
7 </ul>
8 </template>
Poderíamos fazer o mesmo sem ter que mudar o template, ligando o atributo stories
ao Array store.stories diretamente.
src/components/Stories.vue
1 <script>
2 data () {
3 return {
4 // Bind directly to stories
5 stories: store.stories,
6 }
7 }
8 </script>
src/components/Famous.vue
1 <script>
2 import {store} from '../store.js'
3
4 export default {
5 data () {
6 return {
7 stories: store.stories
8 }
9 },
10 computed: {
11 famous () {
12 return this.stories.filter(function (item) {
13 return item.upvotes > 50
14 })
15 }
16 }
17 }
18 </script>
Eliminando Dados Duplicados 294
Código Fonte
Você pode encontrar os exemplos de código deste capítulo no GitHub1 .
1
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter17
19. Alternando Components
Usar componentes .vue é uma das formas mais simples para se criar um SPA (Single
Page Application). Já vimos até agora como configurar um novo projeto, criar um
arquivo .vue e gerenciar dados duplicados. Agora é hora de revisar uma maneira de
alternar os componentes para que somente um seja exibido.
Nos exemplos anteriores, nós criamos 3 componentes no componente App.vue E
alguns outros mais dentro deles. Precisamos encontrar uma maneira de trocar os
componentes dinamicamente, então eles não serão renderizados na página simulta-
neamente.
O Atributo Especial is
Podemos usar a tag reservada <component> e alternar dinamicamente entre vários
componentes, através do atributo especial is.
src/App.vue
<template>
<div id="app">
<component is="hello"></component>
<p>
This is very useful...
</p>
</div>
</template>
<script>
import Hello from './components/Hello'
Alternando Components 296
src/Greet.vue
<template>
<div class="greet">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'No! I want to use the <component> element!'
}
}
}
</script>
Alternando Components 297
src/App.vue
<template>
<div id="app">
<img class="logo" src="./assets/logo.png">
<component :is="currentComponent">
<!-- component changes when this.currentComponent changes!
</component>
<p>
This is very useful...
</p>
<a href="#" @click="currentComponent = 'hello'">Show Hello</a>
<a href="#" @click="currentComponent = 'greet'">Show Greet</a>
</div>
</template>
<script>
import Hello from './components/Hello'
import Greet from './components/Greet'
export default {
components: {
Hello,
Greet
},
data () {
return {
currentComponent: 'hello'
}
}
}
</script>
Alternando Components 298
Greet.vue
Bom, como podemos ver, quando ligamos o atributo especial is para current-
Component, quando este valor muda, o componente exibido é alterado também.
Para alternar entre os componentes, o usuário clica no link para alterar o valor de
currentComponent.
Essa maneira dinâmica de alternar entre vários componentes pode ser muito útil.
Navegação
No exemplo anterior, usamos arquivos .vue para simular uma rede social com
os componentes Login, Registration etc. Agora podemos navegar através destes
componentes através de um menu com abas.
Vamos alocar o componente Stories.vue em uma aba, Register.vue em outra e
Alternando Components 299
src/App.vue
1 <template>
2 <div id="app">
3 <img class="logo" src="./assets/logo.png">
4 <h1>Welcome to dynamic Components!</h1>
5 <ul class="nav nav-tabs">
6 <!-- set 'active' class conditionally -->
7 <li v-for="page in pages" :class="isActivePage(page) ? 'ac
8 ''">
9 <!-- use links to change between tabs -->
10 <a @click="setPage(page)">{{page | capitalize}}</a
11 </li>
12 </ul>
13 <component :is="activePage"></component>
14 </div>
15 </template>
16
17 <script>
18 import Vue from 'vue'
19 import Login from './components/Login.vue'
20 import Register from './components/Register.vue'
21 import Stories from './components/Stories.vue'
22
23 Vue.filter('capitalize', function (value) {
24 return value.charAt(0).toUpperCase() + value.substr(1)
25 })
26
27 export default {
28 components: {
29 Login,
30 Register,
Alternando Components 300
31 Stories
32 },
33 data () {
34 return {
35 // the pages we want to render each time
36 pages: [
37 'stories',
38 'register',
39 'login'
40 ],
41 activePage: 'stories'
42 }
43 },
44 methods: {
45 setPage (newPage) {
46 this.activePage = newPage
47 },
48 isActivePage (page) {
49 return this.activePage === page
50 }
51 }
52 }
53
54 </script>
Alternando Components 301
Código Fonte
Você pode encontrar o código fonte deste capítulo no GitHub1 .
1
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter18
20. Vue Router
Rotas, em geral, referem-se na determinação de como a aplicação responde a uma
requisição do cliente. Uma requisição do navegador não poderia ser redirecionada
para a sua aplicação sem algum tipo de roteamento.
As rotas ajudam o servidor a obter uma apropriada e exata informação para o usuário.
É como uma estação de trem qye informa para onde o trem deve seguir.
A forma como alternamos os componentes anteriormente mostram o caminho para o
entendimento sobre rotas. O plugin oficial de rotas do Vue é chamado de vue-router.
Ele é intimamente integrado ao núcleo do Vue.js para que possamos rapidamente
criar um SPA. Este plugin é fácil de entender, instalar e usar.
As principais características são:
• Mapeamento de rotas
• Modular, baseado em componentes
• Possui parâmetreos de roteamento, filtros e wildcards
• Os efeitos de transição são integrados ao de transição do Vue.js
• Controle de navegação eficiente
• Suporte às classes CSS para links ativos
• HTML5 em “history mode”, com auto-fallback no IE9
• Restaura a posição de rolagem ao voltar no modo histórico
Informação
Estes são apenas alguns dos recursos fornecidos, Você pode ver mais em
Github1 . Além disso, aqui está a Documentação oficial2 .
1
https://github.com/vuejs/vue-router
2
http://router.vuejs.org/en/index.html
Vue Router 304
20.1 Instalação
Existem as formas usuais de instalar o plugin: usando cdn, NPM, and Bower. Nós
vamos usar o terminal para instalá-lo através do NPM.
Digite este comando no seu terminal para ter o vue-router instalado no diretório
node_modules dentro do seu projeto. Depois da instalação complete, vá até o seu
arquivo main.js e adicione as seguintes linhas.
src/main.js
Você pode instalar um pluguin do Vue usando Vue.use() como exibido no código
anterior. Para maiores informações, dê uma olhada neste guia3 .
20.2 Uso
A primeiro passo é criar uma instância do router, onde iremos repassar opções no
futuro, mas vamos menter mais simple por enquanto.
3
http://vuejs.org/api/#Vue-use
Vue Router 305
src/main.js
...
const router = new VueRouter({
routes // short for routes: routes
})
Agora, temos que definir algumas rotas. Cada rota deverá apontar para um com-
ponente, o que significa que estaremos criando rotas para os arquivos *.vue que
criamos ao longo dos exemplos.
A forma principal para se definir os roteamentos é criar um Array chamado routes
no qual podemos repassar objetos contendo as configurações das rotas.
src/main.js
Vue.use(VueRouter)
const routes = [
{ path: '/', component: Hello },
{ path: '/login', component: Login }
]
Informação
Nós estamos usando { path: '/login', component: Login } porque o
componente Login é importado no início do arquivo. Você pode usar
require() também se quiser, da seguinte forma: { path: '/login',
component: require('Login') }
src/main.js
...
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<app></app>',
components: {
App
}
})
Definimos o template para <app></app> então o Vue irá atuar na div com o id app
no componente App.vue.
Se você negligenciar o comentário /* eslint-disable no-new */ você receberá um
erro do eslint:
http://eslint.org/docs/rules/no-new Do not use ‘new’ for side effects
O que fizemos aqui foi desativar essa regra.
Dica
Toda vez que você encontrar problemas com alguma regra do eslint, você
pode desativá-la adicionando um comentário como o acima. Por exemplo
/* eslint-disable eqeqeq */. Você pode encontrar uma lista completa
com as regras aqui4 .
4
http://eslint.org/docs/rules/
Vue Router 307
Agora, precisamos definir alguns links para a navegação. Abra o arquivo App.vue,
para fazer as mudanças necessárias.
src/App.vue
<template>
<div id="app">
<img class="logo" src="./assets/logo.png">
<h1>Welcome to Routing!</h1>
<router-link to="/">Home</router-link>
<router-link to="/login">Login</router-link>
<!-- route outlet -->
<router-view></router-view>
</div>
</template>
Nota
Se você está usando o template do vue-cli, no qual possui um router
incluído, você precisa definir suas rotas no Array dentro do arquivo
router/index.js.
de mais opções à medida que seu projeto cresça. Por exemplo, se decidimos depois
mudar a url /login para /signin, teremos de atualizar todos os links direcionados
para a página de login. Para evitar que isso aconteça, podemos dar a cada rota um
nome.
Para nomear uma rota, devemos alterar a configuração da rota.
src/main.js
...
const routes = [
{
path: '/',
name: 'home',
component: Hello
},
{
path: '/login',
name: 'login',
component: Login
}
]
Podemos dar um nome a uma rota adicionando a propriedade name e usá-la como
identificador nos futuros links.
src/App.vue
<template>
<div id="app">
...
<router-link :to="{ name: 'home'}">Home</router-link>
<router-link :to="{ name: 'login'}">Login</router-link>
<!-- route outlet -->
<router-view></router-view>
</div>
</template>
Vue Router 309
Observe que em vez de usar uma string para definir o destino do link (to="/home"),
estamos usando um objeto (:to="{ name: 'home'}"). Vamos abordar sobre isso mais
tarde.
src/main.js
URLs sem #
Informação
Verifique a lista detalhada das opções disponíveis na documentação do
vue-router5
5
https://router.vuejs.org/en/api/options.html
Vue Router 311
src/main.js
Vue.use(VueRouter)
const routes = [
{
path: '/',
component: Hello
Vue Router 312
},
{
path: '/login',
component: Login
},
{
path: '/stories',
component: StoriesPage,
children: [
{
path: '',
name: 'stories.all',
component: StoriesAll
},
{
path: 'famous',
name: 'stories.famous',
component: StoriesFamous
}
]
}
]
Veja que a propriedade path é vazia. Isso significa que o componente filho é o padrão
no qual será carregado quando a URL combinar com /stories. Você também pode
usar '/' para definir uma rota padrão.
O conteúdo de StoriesFamous será renderizado quando /stories/famous for com-
binado.
Neste ponto, não há necessidade de mostrar o que está dentro desses componentes.
Nosso componente pai, StoriesPage, contém 2 links e a marca <router-view>, para
renderizar o conteúdo de seus componentes filho.
Vue Router 313
src/StoriesPage.vue
<template>
<div>
<h2>Stories</h2>
<!-- navigation -->
<router-link :to="{name: 'stories.all'}">All</router-link>
<router-link :to="{name: 'stories.famous'}">Trending</router-lin\
k>
<!-- route outlet -->
<router-view></router-view>
</div>
</template>
src/App.vue
<style type="text/css">
.router-link-active {
color: green;
}
</style>
Então, agora, toda vez que visitamos uma página, o link correspondente fica verde.
Se você tentar isso no navegador, você notará que o link Home é sempre verde. Isso
acontece porque o caminho de Home é /, então quando você visita por exemplo
/login, Home continua ativo. Para eliminar esse comportamento, podemos adicionar
o suporte exato a este link específico.
Nossos links de navegação ficarão assim:
Vue Router 314
src/App.vue
<template>
<div>
...
<router-link :to="{ name: 'hello'}" exact>Home</router-link>
<router-link :to="{ name: 'login'}">Login</router-link>
<router-link :to="{ name: 'stories.all'}">Stories</router-link>
<router-view></router-view>
</div>
</template>
Classe Ativa
Propriedade Descrição
path Uma string que é igual ao caminho da rota atual,
sempre resolvida como um caminho absoluto.
params Um objeto que contém pares chave / valor de
segmentos dinâmicos.
query Um objeto que contém pares chave / valor da
seqüência de valores da url . Por exemplo, para
/foo?user=1, temos $route.query.user == 1.
Vue Router 317
Propriedade Descrição
hash O hash da rota corrente (sem #), se existir um. Se
nenhum hash estiver presente, o valor será uma string
vazia.
fullPath A URL completa, incluindo consulta e hash.
matched Um Array contendo registros de rota para todos os
segmentos de caminho aninhados da rota atual. Os
registros de rota são as cópias dos objetos na
configuração de rotas.
name O nome da rota atual, se houver um.
Informação
Os segmentos de URL são partes de um URL ou caminho delimitado por
barras. Se você tivesse o caminho /user/:id/posts, então user, :id, and
posts seriam cada parte de um segmento.
src/main.js
1 const routes = [
2 // other routes
3 {
4 path: ':id/edit',
5 name: 'stories.edit',
6 component: StoriesEdit
7 }
8 ...
9 ]
Nota
Criei o arquivo StoriesEdit.vue em segundo plano. Você vai ver isso em
breve, depois que o roteamento for completo.
src/component/StoriesAll.vue
1 <template>
2 <div class="">
3 <h3>All Stories ({{stories.length}})</h3>
4 <ul class="list-group">
5 <li v-for="story in stories" class="list-group-item">
6 <div class="row">
7 <h4>{{ story.writer }} said "{{ story.plot }}"
8 <span class="badge">{{ story.upvotes }}</span>
9 </h4>
10 <router-link
11 :to="{ name: 'stories.edit'}" tag="button"
12 class="btn btn-default" exact
13 >
14 Edit
Vue Router 319
15 </router-link>
16 </div>
17 </li>
18 </ul>
19 </div>
20 </template>
21
22 <script>
23 import {store} from '../store.js'
24
25 export default {
26 data () {
27 return {
28 stories: store.stories
29 }
30 },
31 mounted () {
32 console.log('stories')
33 }
34 }
35 </script>
Nota
Nossos arquivos de exemplo são quase iguais aos anteriores, com pequenas
alterações, principalmente em estilos, que não afetam sua funcionalidade.
Uma mudança notável é a adição de um id para cada história store.js.
Adicionamos um botão para vincular a rota stories.edit. Bem, isso não é suficiente,
porque também precisamos passar o id da história para a rota.
Para fazer isso, vamos editar a propriedade to e fazer o :id se transformar no id
correspondente de cada história.
Vue Router 320
src/component/StoriesAll.vue
1 <template>
2 <div>
3 <h3>All Stories ({{stories.length}})</h3>
4 <ul class="list-group">
5 <li v-for="story in stories" class="list-group-item">
6 <h4>{{ story.writer }} said "{{ story.plot }}"
7 <span class="badge">{{ story.upvotes }}</span>
8 </h4>
9 <router-link
10 :to="{ name: 'stories.edit', params: { id: story.id }}"
11 tag="button" class="btn btn-default" exact>
12 Edit
13 </router-link>
14 </li>
15 </ul>
16 </div>
17 </template>
src/components/StoriesEdit.vue
1 <template>
2 <div class="row">
3 <h3>Editing</h3>
4 <form>
5 <div class="form-group col-md-offset-2 col-md-8">
6 <input class="form-control" v-model="story.plot">
7 </div>
8 <div class="form-group col-md-12">
9 <button @click="saveChanges(story)" class="btn btn-success">
10 Save changes
11 </button>
12 </div>
13 </form>
14 </div>
15 </template>
16
17 <script>
18 import {store} from '../store.js'
19
20 export default {
21 data () {
22 return {
23 story: {}
24 }
25 },
26 methods: {
27 isTheOne (story) {
28 return story.id === this.id
29 },
30 saveChanges (story) {
31 // we will use that later
32 }
33 },
34 mounted () {
Vue Router 322
35 this.story = store.stories.find(this.isTheOne)
36 }
37 }
38 </script>
Isso funciona bem se você visitar a página StoriesEdit usando o botão mas se você
tentar digitar a URL diretamente no navegador, por exemplo /stories/2/edit, você
vai obter um erro de rendererização do componente.
A razão por trás disso é que usamos “strict equality7 ”, o operador (===), para
encontrar a história ativa correta. Ao visitar a página diretamente, id é passado como
uma string (e não um número). Então, isTheOne sempre retorna falso.
7
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Identity
Vue Router 324
src/components/StoriesEdit.vue
isTheOne (story) {
// Retorna falso quando this.id não é um número
return story.id === this.id
}
src/components/StoriesEdit.vue
export default {
...
mounted () {
this.id = Number(this.$route.params.id)
}
}
src/main.js
const routes = [
...
{
path: ':id/edit',
props: (route) => ({ id: Number(route.params.id) }),
name: 'stories.edit',
component: StoriesEdit
},
...
]
Vue Router 325
src/components/StoriesEdit.vue
<script>
import {store} from '../store.js'
export default {
props: ['id'],
data () {
return {
story: {}
}
},
methods: {
isTheOne (story) {
return story.id === this.id
}
},
mounted () {
this.story = store.stories.find(this.isTheOne)
}
}
</script>
É isso aí. Agora, o componente StoriesEdit está desacoplado da rota e podemos usá-
lo em qualquer lugar como por exemplo: <stories-edit :id="story.id"></stories-
edit>.
Dentro de $route
src/main.js
{
path: 'famous',
name: 'stories.famous',
// match '/famous' as if it is '/stories/famous'
alias: '/famous',
component: StoriesFamous
}
Usando alias
Programmatic navigation
src/components/StoriesEdit.vue
1 <script>
2 import {store} from '../store.js'
3
4 export default {
5 props: ['id'],
6 data () {
7 return {
8 story: {}
9 }
10 },
11 methods: {
12 saveChanges (story) {
13 console.log('Saved!')
14 this.$router.push('/stories')
15 },
16 isTheOne (story) {
17 return story.id === this.id
18 }
19 },
20 mounted () {
21 this.story = store.stories.find(this.isTheOne)
22 }
23 }
24 </script>
Se você deseja redirecionar o usuário para a URL que visitou anteriormente, em vez
de uma URL específica, você pode usar router.back().
No nosso caso, podemos adicionar o botão e chamar a função router.back, ao invés
de router.push, e desta vez o usuário poderá navegar até a página anterior (Seja
como for, por exemplo https://google.com).
src/components/StoriesEdit.vue
Outra maneira de fazer isso é usar o método router.go(n), que usa um único número
inteiro como parâmetro que indica por quantos passos avançar ou ir para trás na pilha
do histórico8 .
goBack () {
this.$router.go(-1)
}
Atenção
Usando $router.back(), ou qualquer outro método, estamos acoplando o
componente ao roteador. Se você quiser mantê-lo desacoplado, você pode
usar window.history.back().
8
http://router.vuejs.org/en/essentials/history-mode.html
Vue Router 331
20.11 Transições
Introdução
Cada vez que navegamos para outra página da nossa aplicação, nada sofisticado
acontece. Podemos mudar isso usando uma transição para animar o componente
que entra na página e também o que sai.
Vue fornece uma variedade de maneiras de aplicar efeitos de transição quando os
itens são inseridos, atualizados ou removidos do DOM.
Neste ponto, apenas abordaremos a entrada e saída usando classes CSS. Se você
estiver interessado em aprender mais sobre transições, verifique o guia11 .
Para usar uma transição, temos que envolver o elemento correspondente dentro do
componente transition. No nosso caso, o componente router-view.
O Vue irá adicionar a classe CSS v-enter ao elemento antes de inserir e v-enter-
active durante a fase de entrada. v-enter-to é a última classe a ser anexada antes
que a transição seja concluída. Este é realmente o estado final para entrar.
Assim, quando o elemento está sendo removido do DOM, v-leave, v-leave-active,
e v-leave-to serão aplicados.
9
https://daneden.github.io/animate.css/
10
http://velocityjs.org/
11
https://vuejs.org/v2/guide/transitions.html
Vue Router 332
Classes de transição
Uso
Vamos criar uma transição, chamada fade, para a nossa saída de rotas.
src/App.vue
<template>
<div>
...
<transition name="fade">
<router-view></router-view>
</transition>
</div>
</template>
<style type="text/css">
.fade-enter{
Vue Router 333
opacity: 0
}
.fade-enter-active {
transition: opacity 1s
}
.fade-enter-to {
opacity: 0.8
}
</style>
Dê uma olhada nas classes CSS. A transição começará com a opacidade 0, que
aumentará gradualmente durante 1 segundo. .fade-enter-to não é necessário,
defini-lo assim criará um salto brutal, de 0.8 para 1, pouco antes da transição
terminar.
Para criar a animação inversa quando um componente parte, devemos alterar nosso
CSS para:
<style type="text/css">
.fade-enter, .fade-leave-to{
opacity: 0
}
.fade-enter-active, .fade-leave-active {
transition: opacity 1s
}
.fade-enter-to, .fade-leave {
opacity: 0.8
}
</style>
Animações 3rd-party
Criar uma animação a partir do zero e projetar em geral é uma tarefa difícil para
mim. Eu sempre prefiro confiar em bibliotecas de terceiros. Felizmente para mim (e
Vue Router 334
Animate.css
2. Adicione a classe animated para o elemento que deseja animar. Você também
pode querer incluir a classe infinite para um loop infinito.
3. Finalmente, você precisa adicionar uma das classes disponíveis, como bounce,
rollIn, fadeIn, etc. Você pode encontrar uma lista com todas as classes
disponíveis aqui13 .
src/App.vue
<template>
<div>
...
<transition enter-active-class="animated rollIn">
<router-view></router-view>
</transition>
</div>
</template>
Também podemos aplicar uma animação quando o componente sai do DOM, adici-
onando a classe leave-active-class, como esta: leave-active-class="animated
rollOut".
src/main.js
Aqui, aplicamos uma regra para que o router não deixe os usuários prosseguir para
qualquer página, exceto login. Certifique-se de sempre chamar a função next(),
Caso contrário, o filtro nunca será resolvido.
Código Fonte
Você pode encontrar o código fonte deste capítulo no GitHub14 .
14
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter19
Vue Router 337
20.13 Tarefa
Ao longo deste capítulo, analisamos muitas coisas e faz um tempo desde que nós lhe
atribuímos alguma tarefa!
O aplicativo que você precisa construir é um mini Pokédex.
A página inicial mostrará uma lista de categorias de Pokémon, como Fire, Water,
etc.. A partir daí, o usuário poderá navegar em uma categoria, ver seus Pokémon e
adicionar novos.
Suas rotas podem ser algo assim:
Route Description
/ Mostrar Categorias.
/category/:name Mostrar os Pokémons da categoria.
/category/:name/pokemons/new Adicionar novo Pokémon à
categoria.
Cada transição deve ser registrada no console. Por exemplo, quando o usuário decide
navegar na categoria Fire, uma mensagem precisa ser registrada, informando o
usuário que ele vai visitar /category/Fire.
Criamos o objeto Pokédex para ajudá-lo a começar. Você pode encontrá lo aqui15 .
Informação
A rota /category/:name/pokemons/new é uma suborta de
/category/:name.
Dica 1
Para acessar Pokémon de uma categoria específica, considere usar o
método find16 .
Dica 2
Para registrar mensagens no console antes de cada uso de transição, use
router.beforeEach().
16
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
Vue Router 339
Exemplo
Vue Router 340
Exemplo
17
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/homework/Chapter19