Vous êtes sur la page 1sur 366

The Majesty of Vue.

js 2 (Portuguese)
Alex Kyriakidis, Kostas Maniatis and Daniel Schmitz
Esse livro está à venda em http://leanpub.com/vuejs2-portuguese

Essa versão foi publicada em 2017-06-19

Esse é um livro Leanpub. A Leanpub dá poderes aos autores e editores a partir do


processo de Publicação Lean. Publicação Lean é a ação de publicar um ebook em
desenvolvimento com ferramentas leves e muitas iterações para conseguir
feedbacks dos leitores, pivotar até que você tenha o livro ideal e então conseguir
tração.

© 2017 Alex Kyriakidis, Kostas Maniatis and Daniel Schmitz


Conteúdo

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

Comparação com Outros Frameworks . . . . . . . . . . . . . . . . . . . . x


Angular 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . x
Angular 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
React . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
Ember . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
Polymer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvi
Riot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii
CONTEÚDO

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

Gerenciando Eventos usando Métodos . . . . . . . . . . . . . . 41


Atalho para v-on . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.2 Modificadores de Evento . . . . . . . . . . . . . . . . . . . . . . 43
5.3 Modificadores de Teclas . . . . . . . . . . . . . . . . . . . . . . . 48
5.4 Propriedades Computadas . . . . . . . . . . . . . . . . . . . . . 49
5.5 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

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

8. Eventos Customizados . . . . . . . . . . . . . . . . . . . . . . . . . . . 104


8.1 Enviar e Escutar . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Ciclo de Eventos do Vue . . . . . . . . . . . . . . . . . . . . . . 106
8.2 Comunicação entre Pai e Filho . . . . . . . . . . . . . . . . . . . 107
8.3 Passando Argumentos . . . . . . . . . . . . . . . . . . . . . . . . 109
8.4 Comunicação sem ser Pai/Filho . . . . . . . . . . . . . . . . . . . 115
8.5 Removendo Event Listeners . . . . . . . . . . . . . . . . . . . . . 120
8.6 De Volta ao Componente de Histórias . . . . . . . . . . . . . . . 120
8.7 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124

9. Bindings em classes e estilos . . . . . . . . . . . . . . . . . . . . . . . 126


9.1 Binding em classes . . . . . . . . . . . . . . . . . . . . . . . . . 126
CONTEÚDO

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

II Consumindo uma API . . . . . . . . . . . . . . . . 139

10. Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140


10.1 CRUD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
10.2 API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Download do Código Fonte . . . . . . . . . . . . . . . . . . . . 141
API Endpoints . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

11. Trabalhando com Dados Reais . . . . . . . . . . . . . . . . . . . . . . . 146


11.1 Obter Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
11.2 Refatorando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
11.3 Atualizando Dados . . . . . . . . . . . . . . . . . . . . . . . . . 153
11.4 Removendo Dados . . . . . . . . . . . . . . . . . . . . . . . . . . 155

12. Integrando o vue-resource . . . . . . . . . . . . . . . . . . . . . . . . . 158


12.1 Visão Geral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
12.2 Migração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
12.3 Melhorando Algumas Funcionalidades . . . . . . . . . . . . . . . 161
Editando Histórias . . . . . . . . . . . . . . . . . . . . . . . . . 161
Criar uma Nova História . . . . . . . . . . . . . . . . . . . . . . 165
Armazenar e Atualizar . . . . . . . . . . . . . . . . . . . . . . . 172
12.4 Arquivo Javascript . . . . . . . . . . . . . . . . . . . . . . . . . . 174
12.5 Código fonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
12.6 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
Configuração . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
API Endpoints . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Seu código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
CONTEÚDO

13. Visão Geral do axios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184


13.1 Aposentando o vue-resource . . . . . . . . . . . . . . . . . . . . 184
13.2 Integrando axios . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
13.3 Migração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
13.4 Melhorando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Editando histórias . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Criar uma nova história . . . . . . . . . . . . . . . . . . . . . . 192
Armazenar e Atualizar . . . . . . . . . . . . . . . . . . . . . . . 199
13.5 Arquivo Javascript . . . . . . . . . . . . . . . . . . . . . . . . . . 201
13.6 Código fonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
13.7 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
API Endpoints . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Seu código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209

14. Paginação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211


14.1 Implementação . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
14.2 Links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
14.3 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219

III Criando aplicações em larga escala . . 221

15. ECMAScript 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222


15.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
Compatibilidade . . . . . . . . . . . . . . . . . . . . . . . . . . 223
15.2 Declarando Variáveis . . . . . . . . . . . . . . . . . . . . . . . . 223
Declaração Let . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
15.3 Arrow Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 225
15.4 Módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
15.5 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
15.6 Parâmetros padrão . . . . . . . . . . . . . . . . . . . . . . . . . . 229
15.7 Templates Literais (Template literals) . . . . . . . . . . . . . . . . 230

16. Workflow Avançado . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232


16.1 Compilando ES6 com Babel . . . . . . . . . . . . . . . . . . . . . 232
CONTEÚDO

Instalação do Babel . . . . . . . . . . . . . . . . . . . . . . . . . 234


Configuração . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
Criando Apelidos para a Compilação . . . . . . . . . . . . . . . 237
Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
16.2 Automatização do Workflow com Gulp . . . . . . . . . . . . . . 242
Task Runners . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
Instalação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
Watch (Observador) . . . . . . . . . . . . . . . . . . . . . . . . 245
Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
16.3 Module Bundling com Webpack . . . . . . . . . . . . . . . . . . 247
Module Bundlers . . . . . . . . . . . . . . . . . . . . . . . . . . 247
Webpack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Instalação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
Automação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
16.4 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255

17. Trabalhando com Single File Components . . . . . . . . . . . . . . . . 257


17.1 O vue-cli . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
Templates Vue’s . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Instalação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
17.2 Template Webpack . . . . . . . . . . . . . . . . . . . . . . . . . 262
Estrutura do Projeto . . . . . . . . . . . . . . . . . . . . . . . . 264
index.html . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Hello.vue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
App.vue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
main.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
17.3 Criando Arquivos .vue . . . . . . . . . . . . . . . . . . . . . . . 271
Aninhado Componentes . . . . . . . . . . . . . . . . . . . . . . 280

18. Eliminando Dados Duplicados . . . . . . . . . . . . . . . . . . . . . . 285


18.1 Compartilhando propriedades . . . . . . . . . . . . . . . . . . . 285
18.2 Store Global . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
CONTEÚDO

19. Alternando Components . . . . . . . . . . . . . . . . . . . . . . . . . . 295


19.1 Componentes dinâmicos . . . . . . . . . . . . . . . . . . . . . . 295
O Atributo Especial is . . . . . . . . . . . . . . . . . . . . . . . 295
Navegação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298

20. Vue Router . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303


20.1 Instalação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
20.2 Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
20.3 Rotas Nomeadas . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
20.4 Modo “History” . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
20.5 Rotas aninhadas . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
20.6 Definindo uma Classe CSS Ativa . . . . . . . . . . . . . . . . . . 313
Classe Ativa Personalizada . . . . . . . . . . . . . . . . . . . . . 315
20.7 Objeto Route . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
20.8 Segmentos dinâmicos . . . . . . . . . . . . . . . . . . . . . . . . 317
20.9 Apelidos para o Router . . . . . . . . . . . . . . . . . . . . . . . 326
20.10 Navegando de Forma Programática . . . . . . . . . . . . . . . . 328
20.11 Transições . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Animações 3rd-party . . . . . . . . . . . . . . . . . . . . . . . . 333
20.12 Filtros em Transições . . . . . . . . . . . . . . . . . . . . . . . . 335
20.13 Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
Bem vindo
Sobre o livro
Este livro irá guiá-lo rumo ao aprendizado do já difundido Framework JavaScript
chamado Vue.js!
Há algum tempo atrás, nós começamos um novo projeto baseado no Laravel e no
Vue.js. Depois de ler todo o guia do Vue.js e alguns tutoriais, nós descobrimos que há
muito pouco conteúdo disponível a respeito dele na web. Durante o desenvolvimento
do nosso projeto, ganhamos muita experiência, então surgiu a ideia de escrever este
livro a fim de compartilhar com o mundo nosso conhecimento adquirido. Agora que
o Vue.js 2 foi lançado, decidimos que era hora de atualizar nosso livro para publicar
uma segunda versão, onde todos os exemplos e conteúdos foram reescritos.
Este livro está escrito em um formato informal, intuitivo e de fácil acompanhamento,
onde todos os exemplos são suficientemente detalhados de forma a fornecer a todos
uma orientação adequada.
Vamos começar a partir do básico e através de muitos exemplos trataremos dos
recursos mais significativos do Vue.js. Até o final deste livro, você será capaz de
criar rapidamente aplicações front-end e aumentar o desempenho de seus projetos
existentes com a integração do Vue.js 2.

Para quem é este livro


Todos que acompanham o desenvolvimento web moderno, já estão familiarizados
com frameworks Javascript/CSS, tais como JQuery e Bootstrap. Este livro é para qual-
quer pessoa interessada em aprender um Framework JavaScript leve e simples. Não
é necessário nenhum conhecimento excessivo, embora seja bom estar familiarizado
com HTML e JavaScript. Se você não sabe qual a diferença entre uma String e um
objeto talvez você precise fazer uma pesquisa antes.
Bem vindo ii

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.

Name Email Twitter


The Majesty of Vue.js hello@tmvuejs.com @tmvuejs
Alex Kyriakidis alex@tmvuejs.com @hootlex
Kostas Maniatis kostas@tmvuejs.com @kostaskafcas
Daniel Schmitz danieljfa@gmail.com @daniel_schmitz

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 }

Um código no texto é exibido da seguinte forma: “Use .container para um contêiner


de largura fixa responsiva”
2
https://github.com/hootlex/the-majesty-of-vuejs-2
3
https://github.com/hootlex/the-majesty-of-vuejs-2/archive/master.zip
4
https://github.com/hootlex/the-majesty-of-vuejs-2
Bem vindo iv

Termos novos e palavras importantes são mostrados em negrito.


Dicas, notas e avisos são mostrados assim:

Isto é um aviso
Este elemento indica um aviso ou atenção.

Isto é uma dica


Este elemento significa uma dica ou sugestão.

Isto é uma caixa de informações


Algumas informações especiais aqui.

Isto é uma nota


Uma nota sobre o assunto

Isto é uma dica


Uma dica sobre o assunto.

Isto é um Comando de Terminal


Comandos a serem executados no terminal.

Isto é um texto de comparação


Um pequeno texto comparando coisas relativas ao assunto.
Bem vindo v

Isto é um link para o Github.


Os links para o repositório deste livro, onde você pode encontrar os códigos
fonte e as potenciais soluções dos exercícios de cada capítulo.
Introdução
Sobre o Vue.js
Visão Geral
Vue (pronuncia-se /vjuː/, como view) é um framework progressivo para criar in-
terfaces. Ao contrário de outros frameworks monolíticos, Vue é projetado a partir
do zero para ser adaptado progressivamente. O núcleo da biblioteca está focado na
camada de visualização e é muito fácil a sua integração com outras bibliotecas
ou projetos existentes. Por outro lado, o Vue também é perfeitamente capaz de
criar aplicativos sofisticados de uma única página (Single-Page Applications) quando
usados em combinação com ferramentas modernas e bibliotecas de suporte5 .
Se você é um desenvolvedor front-end experiente e quer saber como comparar
o Vue.js com outras bibliotecas/frameworks, confira o capítulo Comparação com
Outros Frameworks.
Se você está interessado em saber mais informações sobre o núcleo do Vue.js, dê uma
olhada em Guia oficial do Vue.js6 .

O que as pessoas estão dizendo sobre o Vue.js

“O Vue.js é o que me faz amar JavaScript. É extremamente fácil e agradável de usar.


Ele tem um grande ecossistema de plugins e ferramentas capazes de estender seus
serviços básicos. Você pode rapidamente incluí-lo em qualquer projeto, pequeno ou
grande, escrever algumas linhas de código e pronto. O Vue.js é rápido, leve e é o futuro
do desenvolvimento *Front-end*!”
—Alex Kyriakidis

5
https://github.com/vuejs/awesome-vue#libraries--plugins
6
https://br.vuejs.org/v2/guide/
Sobre o Vue.js viii

“Quando eu comecei a aprender Javascript fiquei empolgado aprendendo uma


tonelada de possibilidades, mas quando meu amigo sugeriu aprender Vue.js e eu segui
os seus conselhos, as coisas ficaram uma loucura. Enquanto eu lia e assistia tutoriais
eu ficava pensando em todas as coisas que tinha feito até agora e como seria mais
fácil se eu tivesse investido tempo para aprender Vue mais cedo. Minha opinião é que
se você quer fazer seu trabalho de forma rápida, agradável e fácil, Vue é o Framework
JS que você precisa.”
—Kostas Maniatis

“Escreva o que te digo: O Vue.js disparará em popularidade em 2016. E isso é bom.”


— Jeffrey Way

“O Vue é o que eu sempre quis em um framework JavaScript. Ele é um framework que


cresce na mesma proporção que você como desenvolvedor. Você pode usar um ou outro
recurso em uma página ou construir um aplicativo avançado de uma única página
com *Vuex* e *Vue Router*. É realmente o framework JavaScript mais impecável que
eu já vi.”
— Taylor Otwell

“Vue.js é o primeiro framework que encontrei que se torna natural de usar em


um aplicativo *server-rendered*, assim como um SPA completo tem que ser. Se eu
só preciso de um pequeno *widget* numa única página ou estou construindo um
complexo cliente JavaScript, ele sempre dará conta do recado.”
— Adam Wathan
Sobre o Vue.js ix

“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

“O motivo pelo qual eu gosto do Vue.js é porque sou um híbrido de designer e


desenvolvedor. Eu dei uma olhada no *React*, *Angular* e alguns outros, mas a curva
de aprendizado e terminologia tem sempre me deixado de fora. O Vue.js é o primeiro
framework JS que eu entendo. Além disso, ele não é apenas fácil de aprender para os
desenvolvedores JS menos experientes, como eu, mas tenho reparado desenvolvedores
avançados de *Angular* e *React* tomando notas, e gostando do Vue.js. Isso é muito
sem precedentes no mundo JS e por esta razão eu comecei o *Meetup Vue.js* em
Londres.”
—Jack Barham
Comparação com Outros
Frameworks
Angular 1
Algumas das sintaxes do Vue parecerão muito similar às do Angular (e.g. v-if vs
ng-if). Isto porque havia um monte de coisas do Angular que deram certo e estas
foram uma inspiração para o desenvolvimento do Vue desde cedo. Há também muitos
problemas que vêm com o Angular, onde o Vue, no entanto, tentou oferecer uma
melhoria significativa.

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

Em termos de desempenho, ambos os frameworks são excepcionalmente rápidos e


não há dados suficientes de casos de uso do mundo real para dar o veredito. No
Comparação com Outros Frameworks xii

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:

• utilizam um DOM virtual;


• fornecem componentes reativos e agrupáveis;
• mantêm o foco na biblioteca central, contendo outras bibliotecas complemen-
tares, tais como roteamento e gerenciamento de estado global.

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 é:

1. Minimizar o número de mutações DOM necessárias. Tanto o React quanto


o Vue usam abstrações virtuais do DOM para realizar isso e ambas as
implementações funcionam igualmente bem.
2. Adicionar o mínimo possível de sobrecarga sobre estas manipulações do DOM.
Essa é uma área onde o Vue e o React diferem. No React, digamos que a
sobrecarga adicional de renderização de um elemento é 1 e a sobrecarga de
um componente médio é 2. No Vue, a sobrecarga de um elemento seria mais
para 0,1, enquanto a sobrecarga de um componente médio seria 4, devido à
configuração necessária para o sistema de reatividade.

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:

• O Vue oferece reatividade não obstrutiva em objetos JavaScript puro e propri-


edades calculadas totalmente automáticas. No Ember, você precisa envolver
tudo em Objetos Ember e declarar manualmente as dependências para propri-
edades calculadas.
• A sintaxe de templates do Vue aproveita toda a capacidade das expressões
JavaScript, enquanto a sintaxe de expressões e helpers do Handlebars são
intencionalmente bastante limitadas em comparação.
• Em relação ao desempenho, o Vue supera o Ember por uma margem con-
siderável mesmo após a última atualização da engine Glimmer no Ember
2.0. O Vue realiza automaticamente atualizações em lote, enquanto que no
Ember você precisa gerenciar manualmente loops de execução em situações
de desempenho crítico.
Comparação com Outros Frameworks xvi

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:

• Verdadeira renderização condicional. O Riot renderiza todos os nós e simples-


mente os exibe/oculta;
• Um roteador muito mais poderoso. A API de roteamento do Riot é extrema-
mente minimalista;
• Mais maturidade no suporte de ferramentas. O Vue fornece suporte oficial
para Webpack, Browserify e SystemJS, enquanto o Riot depende do suporte da
comunidade para a integração de sistemas de build;
• Sistema de efeito de transição. Riot não possui nenhum;
• Melhor desempenho. Apesar de anunciar o uso de um DOM virtual, o Riot
utiliza dirty checking e por isso sofre dos mesmos problemas de desempenho
do Angular 1.

Para comparações atualizadas verifique no guia do Vue.js10 .

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.

1.1 Versão Standalone

Download no Site vuejs.org


Para instalar o Vue você pode simplesmente realizar o download e incluí-lo com a
tag script. Vue será registrado como uma variável global.
Você pode realizar o download de duas versões do Vue.js:

1. Versão de desenvolvimento: http://vuejs.org/js/vue.js1 .


2. Versão de produção: http://vuejs.org/js/vue.min.js2 .

Dica: Não use a versão de produção durante o desenvolvimento. Você vai


perder todos os warnings amigáveis para erros comuns.

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

O Vue.js também pode ser encontrado em jsdelivr5 ou cdnjs6 .

Leva algum tempo para sincronizar a versão mais recente, então você deve
verificar atualizações frequentemente.

1.2 Download usando NPM


NPM é o método de instalação recomendado quando for desenvolver aplicações
em larga escala com Vue.js. Este método combina bem com um CommonJS module
bundler como o Webpack7 ou Browserify8 .

1 # última versão estável


2 $ npm install vue
3 # última versão estável + CSP-compliant
4 $ npm install vue@csp
5 # versão dev (diretamente do GitHub):
6 $ npm install vuejs/vue#dev

1.3 Download usando Bower


1 # última versão estável
2 $ bower install vue

Para maiores informações sobre a instalação e atualizações dê uma olhada


a mais no Guia de Instalação do Vue.js9

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.

2.1 Olá mundo


Vamos criar um novo arquivo e inserir um código padrão (chamado de boilerplate).
Você pode nomeá-lo da forma que quiser, como por exemplo hello.html.

1 <html>
2 <head>
3 <title>Hello Vue</title>
4 </head>
5 <body>
6 <h1>Greetings your Majesty!</h1>
7 </body>
8 </html>

Este é um simples arquivo HTML com uma mensagem de boas vindas.


Agora vamos continuar e fazer a mesma coisa usando o Vue.js. Primeiro, vamos
incluir o Vue.js e criar uma nova instância.
Começando 5

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 })

Para exibir a mensagem na página, precisamos apenas envolver a variável message


em colchetes duplos.
Então, o que estiver dentro da mensagem, aparecerá automaticamente na tag h1.

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

2.2 Two-way Binding


O legal do Vue é que ele torna tudo mais fácil. Assumindo que precisamos mudar a
mensagem em um campo de texto, como poderíamos fazer isso de forma bem fácil?
No exemplo a seguir usamos v-model, uma diretiva do Vue (iremos falar sobre
diretivas em um próximo capítulo). Então usamos o two-way data binding para
dinamicamente alterar o valor da mensagem enquanto o usuário altera a mensagem
dentro da caixa de texto. Os dados são sincronizados à medida que o usuário digita
o texto.

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

Two-way data binding

O termo Two-way data binding significa que se você alterar o valor do modelo no
formulário, tudo será atualizado.

2.3 Comparação com jQuery.


Provavelmente, todos vocês tem um pouco de experiência com jQuery. Se não, tudo
bem, o uso de jQuery neste livro é mínimo. Quando nós o usamos, é apenas para
demonstrar como as coisas podem ser feitas com o Vue ao invés do jQuery e nos
certificaremos que tudo será entendível.
De qualquer forma, a fim de entender melhor como o data-binding ajuda a criar
aplicações, tire um tempo e pense em como seria o exemplo anterior usando jQuery.
Você provavelmente iria criar a caixa de texto e dar um valor a um id ou class,
então você poderia obtê-la e modificá-la.
Depois disso, você deveria chamar uma função que muda o elemento para combinar
com o valor de entrada, sempre que o evento keyup for executado. é assim que
funciona, sério!
Seu código se pareceria mais ou menos com o código a seguir.
Começando 9

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:

• v-show é usado para exibir ou não um elemento, através de uma condição;


• v-if pode ser usado ao invés do v-show;
• v-else exibe um elemento quando v-if retorna falso.

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>

Aqui nós temos um arquivo HTML. Conhecemos o div id="app" e o textarea.


Dentro do textarea nós iremos exibir a nossa mensagem. Claro que, o textarea
ainda não está ligado ao message e neste ponto ainda não temos nenhum resultado
esperado.
Você também pode ter notado que neste exemplo não estamos usando a versão
minificada do Vue.js. Como mencionamos antes, a versão minificada não deve
ser usada durante a fase de desenvolvimento porque você irá perder importantes
mensagens de erro para os erros mais comuns.
De agora em diante, estaremos utilizando esta versão (de desenvolvimento) no livro,
mas é claro que você pode usar da forma que achar melhor.
Um Leque de Diretivas 13

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.

1 <h1 v-show="!message">You must send a message for help!</h1>


2 <textarea v-model="message"></textarea>
3 <button v-show="message">
4 Send word to allies for help!
5 </button>

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

Usando a configuração do exemplo anterior, temos a diretiva v-if no elemento


template, alterando a existência de todos os elementos filhos.

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.

3.4 v-if vs. v-show


Mesmo que já mencionamos uma diferença entre v-if e v-show, nós podemos nos
aprofundar um pouco mais. Algumas questões podem surgir com o seu uso:

• Existe uma grande diferença entre v-show e v-if?


• Existe alguma situação em que o desempenho é afetado?
Um Leque de Diretivas 21

• Exitem problemas onde é melhor usar um em relação ao outro?

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.

Quando usamos v-if, se a condicional for falsa na renderização inicial,


nada será feito - o bloco da condicional não será renderizado enquanto
a condição não for verdadeira pela primeira vez. Em suma, v-if tem um
custo maior de alteração enquanto que v-show tem um custo maior na
inicialização da renderização. Portanto, prefira v-show se você precisar
alternar algo muito frequentemente, e prefira v-if se a condição não
sofrer alterações frequentes em tempo de execução.

Então, deve-se usar cada uma dependendo de suas necessidades.

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:

1. Desenhar uma lista de itens baseada em um Array;


2. Repetir um template;
3. Iterar através das propriedades de um objeto.

4.1 Instalação & uso do Bootstrap


Para deixar tudo mais bonito, vamos instalar o Bootstrap.

Informação
Bootstrap é o framework HTML/CSS/JS mais popular para o desenvolvi-
mento responsivo, compatível com mobile, na web.

Para instalar, acesse http://getbootstrap.com/1 e clique no botão download. Se quiser


começar de uma forma mais fácil, use a versão CDN2 conforme a necessidade.
Por exemplo, precisamos inicialmente de um arquivo, vamos chamá-lo de: css/bo-
otstrap.min.css. Quando usamos somente o .css na aplicação, temos acesso a todas
as estruturas e estilos pré formatados. Apenas inclua-o na tag head da sua página e
pronto.
O Bootstrap possui dois tipos de “containers”, que é um elemento que irá conter todo
o conteúdo do site e formatar o grid system.

• Use .container para uma largura fixa

1
http://getbootstrap.com/
2
https://www.bootstrapcdn.com/
Renderização de Listas 24

1 <div class="container">
2 ...
3 </div>

• Use .container-fluid para o container ocupar toda a página

1 <div class="container-fluid">
2 ...
3 </div>

Neste ponto, gostaríamos de fazer um exemplo do Vue.js com o Bootstrap. Natu-


ralmente, nao é necessário muito estudo para se fazer isso com o Vue e o Bootsrap
combinados.

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.

O singular primeiro e o plural depois.

Range para o v-for


A diretiva v-for também pode ter um número inteiro. Sempre que um número é
passado ao invés de um Array/Objeto, o laço será repetido na quantidade de vezes
do número informado.

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>

O código acima mostra a tabuada de multiplicação do 4.


Renderização de Listas 28

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

4.3 Renderização de Array

Loop através de um Array


No próximo exemplo, vamos configurar o seguinte Array de histórias dentro do
objeto data do Vue, e vamos mostrá-los todos, um por um.
Renderização de Listas 29

stories: [
"I crashed my car today!",
"Yesterday, someone stole my bag!",
"Someone ate my chocolate...",
]

O que precisamos fazer aqui é renderizar uma lista. Especificamente, um Array de


strings.

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

Renderização de um Array usando v-for.

Usando v-for é possível exibir as histórias em uma lista não ordenada. É realmente
muito simples.

Loop através de um Array de Objetos


Agora, alteramos o Array de histórias para conter um objeto story. O objeto story
possui duas propriedades: plot e writer. Iremos fazer a mesma coisa que fizemos
antes, mas agora ao invés de exibir o valor de story imediatamente, vamos exibir
story.plot e story.writer.
Renderização de Listas 32

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

36 plot: "Yesterday, someone stole my bag!",


37 writer: "John"
38 },
39 {
40 plot: "Someone ate my chocolate...",
41 writer: "John"
42 },
43 {
44 plot: "I ate someone's chocolate!",
45 writer: "Alex"
46 },
47 ]
48 }
49 })
50 </script>
51 </html>

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>

O index dentro das chaves, representa claramente o índice do item iterado no


exemplo.
Renderização de Listas 34

Array renderizado com index


Renderização de Listas 35

4.4 Objeto v-for


Você pode usar v-for para iterar através das propriedades de um objeto. Menciona-
mos anteriormente que você pode exibir o index do Array, mas você também pode
fazer o mesmo ao iterar um objeto. Além do index, o escopo terá acesso a outra
propriedade especial: key.

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

Pode-se fornecer um segundo e terceiro argumentos, para o key e index, respectiva-


mente.

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á:

iterar através das propriedades do objeto


Renderização de Listas 37

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.

5.1 Gerenciando Eventos


Eventos HTML ocorrem nos elementos da DOM. Quando o Vue.js é usado em
páginas HTML, ele pode reagir a estes eventos. Eventos podem representar tudo, desde
interações básicas do usuário até coisas que acontecem na renderização do modelo.
Estes são alguns exemplos:

• Uma página web que terminou de carregar


• Uma caixa de texto com o seu valor alterado
• Um botão quando clicado
• Um formulário quando enviado

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

Gerenciando Eventos Inline


Chega de conversa, vamos seguir em frente e ver os eventos em ação. Abaixo, há um
botão ‘Upvote’ que aumenta o número de votos (upvote) toda vez que ele é clicado.

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++.

Gerenciando Eventos usando Métodos


Agora vamos fazer exatamente a mesma coisa que antes, só que usando métodos. Um
método no Vue.js é um bloco de código designado a executar uma tarefa particular.
Interatividade 42

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>

Estamos vinculando o evento de click ao método chamado ‘upvote’. Ele funciona da


mesma forma que antes, só que está mais limpo e fácil de entender.
Interatividade 43

Atenção
Event handlers são restritos para executar em apenas uma única decla-
ração.

Atalho para v-on


Quando você usa muitos v-on em todo o seu projeto, perceberá que o seu código fica
extremamente sujo. Felizmente, existe um atalho para v-on que é o simbolo @.
O @ substitui todo o v-on e, ao usá-lo, o código parece muito mais limpo. Usar a
abreviação é totalmente opcional.
Com o uso do @, o botão dos exemplos anteriores fica assim:

Usando v-on:

<button v-on:click="upvote">
Upvote! {{upvotes}}
</button>

Usando @

<button @click="upvote">
Upvote! {{upvotes}}
</button>

5.2 Modificadores de Evento


Agora vamos seguir em frente e criar a aplicação calculadora. Para isso, usaremos
um formulário com duas entradas e uma caixa de seleção dropdown para selecionar
a operação desejada.
Mesmo que o código a seguir pareça correto, a calculadora não funcionará como
esperávamos.
Interatividade 44

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

Usando modificadores de evento para criar a calculadora

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

Então, usando .prevent, o botão do formulário irá mudar para:

1 <button type="submit" @click="calculate">Calculate</button>

para:
Interatividade 48

1 <!-- the submit event will no longer reload the page -->
2 <button type="submit" @click.prevent="calculate">Calculate</button>

Podemos remover agora, de forma segura, o event.preventDefault() do método


calculate.

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 .

5.3 Modificadores de Teclas


Quando o foco do formulário estiver em um dos campos, você pode chamar o método
calculate pressionando a tecla enter.
Ao “escutar” estes eventos do teclado, precisamos conhecer os seus respectivos
códigos. Por exemplo, para a tecla enter o código é 13. Então podemos fazer assim:

1 <input v-model="a" @keyup.13="calculate">

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

1 <input v-model="a" @keyup.enter="calculate">


2 <input v-model="b" @keyup.enter="calculate">

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.

Por exemplo: <form @submit.prevent="calculate">

Finalmente, a calculadora está funcionando como deveria.

5.4 Propriedades Computadas


As expressões inline do Vue.js são bastante convenientes, mas quando existe uma ló-
gica um pouco mais complicada, você deve usar propriedades computadas (computed
properties)
Em suma, propriedades computadas são variáveis cujo valor depende de outros
fatores.
Propriedades computadas funcionam como funções que você pode tratar como se
fossem propriedades. Mas existe uma diferença significativa entre uma propriedade
computada e uma função. Toda vez que alguma dependência mudar dentro da
propriedade calculada, o seu valor será recalculado.
No Vue.js, você define uma propriedade computada dentro do objeto computed na
instância Vue.
Interatividade 50

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>

Criamos duas variáveis, a primeira a possui o valor 1 e a segunda b é uma propriedade


computada que retorna o valor de a + 1.
Interatividade 51

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

atualizar o resultado de b. Mas, devido a um detalhe, o resultado apresentado não é


o que nós esperávamos.
Se digitarmos 5 na caixa de texto, espera-se que o valor de b seja 6, mas, ao invés
disso, obtemos o resultado 51.
Por que isso acontece? Bem, como você já deve saber, b pega o valor dado pela variável
a, que neste momento é uma string, e adiciona o número 1 no final. Uma possível
solução para este problema é usar o método parseFloat() que irá pegar a string e
converter para um número do tipo float.

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>

O modificador number nos dá o resultado esperado, sem a necessidade de im-


plementação extra na propriedade computada. Para demostrar um uso maior das
propriedades computadas, vamos recriar a calculadora que já fizemos antes, mas
agora usando propriedades computadas, ao invés de métodos.
Vamos começar com um simples exemplo, onde a propriedade computada c contém
a soma de a mais b.

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

Calculadora usando propriedades computadas.

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:

1. Filtrar um Array de itens.


2. Ordenar um Array de itens.
3. Aplicar um filtro customizado.
4. Usar bibliotecas.

O plano é fazer alguns exemplos semelhantes aos que foram feitos antes, combinando
algumas técnicas que já vimos.

6.1 Filtrando Resultados


As vezes precisamos exibir um Array com os dados filtrados, mas sem alterar os
dados originais. Continuando com o exemplo anterior, Loop através de um Array de
objetos, podemos exibir uma lista com as histórias escritas pelo Alex e outra com as
histórias escritas pelo John.
Podemos fazer isso, criando um método que filtrará o Array e retornará os resultados
para serem renderizados.

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).

Criamos o método chamado storiesBy que possui o argumento writer e retorna


um novo Array filtrado com as histórias do autor (writer). Nós podemos usar isso
para exibir a história de cada escritor usando a diretiva v-for no formato story in
storiesBy('Alex'), como pode ser visto no exemplo anterior.

Histórias filtradas pelo autor

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.

Simples assim, certo?

Usando Propriedades Computadas


Uma propriedade computada pode também ser usada como um filtro. Usar proprie-
dades computadas para realizar um filtro lhe dá um maior controle e flexibilidade, já
que é Javascript puro, e lhe permite acessar o resultado do filtro em outros lugares.
Por exemplo, você pode obter o tamanho do Array filtrado em qualquer parte do seu
código.
Primeiro vamos criar uma nova propriedade chamada upvotes. Então, vamos filtrar
as histórias mais famosas (famous).
A variável famous define as histórias que possuem mais de 25 votos. Desta vez, vamos
criar uma propriedade computada que irá retornar este Array.

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

plot: "Someone ate my chocolate...",


writer: "John",
upvotes: 51
},
{
plot: "I ate someone's chocolate!",
writer: "Alex",
upvotes: 74
},
]
},
computed: {
famous: function() {
return this.stories.filter(function(item){
return item.upvotes > 25;
});
}
}
})

No código HTML, ao invés do Array stories, vamos usar a propriedade computada


famous.

<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

Busca por ‘choco’.

Não é incrível??
Filtros 69

6.2 Ordenar resultados


As vezes, precisamos exibir os itens de um Array ordenados através de algum critério.
Podemos usar uma propriedade computada para exibir o nosso Array, ordenado pela
quantidade de votos de cada história.
Para ordenar a Array, vamos usar o método Javascript sort3 , o qual ordena os
elementos e retorna um novo Array.
Quanto mais famosa uma história é, mais à frente ela fica.

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>

Array de histórias ordenadas pela votação.

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:

• Se compareFunction(a, b) é menor que 0, coloque a antes do b.


• Se compareFunction(a, b) é 0, não faça nada.
• Se compareFunction(a, b) é maior que 0, coloque b antes do a.

Neste caso, a função compareFunction(a, b) será:

compareFunction

function(a, b){
return a.upvotes - b.upvotes;
}

Então, para mudar a ordem de crescente para decrescente, podemos multiplicar o


resultado por -1 (return (a.upvotes - b.upvotes) * -1).
Nós podemos alterar a ordem dinamicamente, usando a variável order.
Um botão poderá ser usado alternando o valor da variável order entre -1 e 1.

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 })

Nós inicializamos a variável order com o valor -1 e passamos para a propriedade


computada, então toda vez que o botão é clicado, a variável muda de valor e o Array
muda de ordem.
Filtros 73

Array em ordem decrescente.


Filtros 74

6.3 Filtros Customizados


Para demonstrar os filtros customizados, vamos fazer um novo e simples exemplo.
Suponhamos que agora estamos no comando do jornal de Gotham, chamado “The
Gotham Gazette”. Nosso primeiro trabalho é espalhar a notícia das identidades
secretas dos heróis. Nós sabemos o primeiro e último nome deles, e nós queremos
fazer uma lista onde cada identidade secreta será exposta.
É aqui que o método global Vue.filter() é usado para criar um filtro que pode
pegar um herói e retornar todas as suas informações para exibição, sem poluir o
código HTML.
Para registrar um filtro, podemos usar um filterID e um filterFunction, os quais
retornam um valor devidamente processado.
Então podemos usar o filtro como na forma a seguir:

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

Filtros customizados em ação

6.4 Bibliotecas Utilitárias


Neste ponto, gostaríamos de salientar que quando for preciso ordenar/filtrar/indexar
dados de forma mais avançada, você pode considerar o uso de bibliotecas JavaScript.
Existem algumas boas bibliotecas prontas, como o Lodash4 , Underscore5 , Sugar6 , etc.
Para um melhor entendimento, vamos usar o Lodash e atualizar o exemplo anterior.
Antes de continuar, certifique-se de incluir o Lodash via CDN no seu arquivo HTML.
O método orderBy do Lodash retorna uma nova Array ordenada. Vamos usá-lo
dentro da nossa propriedade computada para ordenar o Array stories.

4
https://lodash.com
5
http://underscorejs.org/
6
https://sugarjs.com/
Filtros 77

Sintaxe
A sintaxe do orderBy no Lodash é:

_.orderBy(collection, [iteratees=[_.identity]], [orders])

Não deixe que o segundo argumento o confunda. É realmente simples. O primeiro


argumento representa o Array que você precisa escolher. O segundo argumento
recebe um Array de chaves, na qual a ordenação será baseada. O terceiro
argumento recebe o Array de ordenação para cada chave.
Por exemplo, se tivermos o Array:

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:

_.orderBy(kids, ['intelligence', 'strength'], ['desc', 'asc'])

Nosso Array terá a seguinte ordenação:

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

Já que o Array é primeiramente ordenado pela propriedade inteligence em


ordem decrescente e depois pela propriedade strength em ordem ascendente.

Vamos usar _.orderBy dentro da nossa propriedade computada. Por exemplo:

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])
}
}

O botão será quase o mesmo:


Filtros 79

<button v-on:click="reverseOrder">

É isso aí! Criamos uma funcionalidade de ordenação parecida com o primeiro


exemplo, mas agora usando o lodash.

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>

É isso aí! Não há necessidade de escrever nenhum método Javascript.

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.

7.2 Usando Componentes


Vamos começar com um simples componente. Primeiro, precisamos registrá-lo. Uma
maneira de registrar o componente é através do método Vue.component e passar dois
parâmetros: tag e constructor.
Pense que a tag é como se fosse o nome do componente e o constructor como se
fosse as opções. No nosso caso, nomeamos o componente story e vamos definir a
propriedade story (de novo).
A opção template (é como queremos que a história seja exibida), está dentro do
constructor, onde outras opções serão adicionadas também.

O componente story é registrado da seguinte forma:


Componentes 83

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

Como mencionamos no início do capítulo, os componentes são reutilizáveis, o que


significa que você pode acrescentar tantos elementos <story> quanto quiser.
O seguinte HTML exibirá nossa história 3 vezes.

<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/template" id="story-template">


<h1>My horse is amazing!</h1>
</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>

Exibições diferentes das histórias.

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

<story plot="My horse is amazing." writer="Mr. Weebl"></story>

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.

Podemos usar o elemento personalizado <story> dentro do HTML e passar cada


história, como fizemos antes, com a tag :story. Desta vez, nós a usaremos dentro
da diretiva v-for.
Então o nosso código deve ser:
Componentes 92

<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>

Se você tentar executar este código receberá o seguinte aviso:


Vue warn: Unknown custom element: <story> - did you register the component
correctly? For recursive components, make sure to provide the “name” option.
Componentes 93

Aviso do Vue

Para resolver isso, nós precisamos registrar o Componente novamente. Esta é a


hora de fazer algumas mudanças no template do componente. Nós iremos alterar
o atributo plot para body e a tag <h1> para a tag li, de modo que atenda as nossas
necessidades.
Então, o nosso template fica assim:

<template id="story-template">
<li class="list-group-item">
{{ story.writer }} said "{{ story.body }}"
</li>
</template>

O componente permanece o mesmo.

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>

O próximo passo permite ao usuário dar um voto para uma história.


Para aplicar este limite de um voto por história, vamos mostrar o botão de votação
somente se o usuário ainda não votou. Então, cada história tem uma propriedade
chamada voted, que se torna true quando o método upvote é executado.
Componentes 96

<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

writer: 'Mr. Weebl',


upvotes: 8,
voted: false,
},
{
plot: 'The dark side of the Force is stronger.',
writer: 'Darth Vader',
upvotes: 49,
voted: false,
},
{
plot: 'One does not simply walk into Mordor',
writer: 'Boromir',
upvotes: 74,
voted: false,
},
]
}
})
Componentes 98

Pronto para votar

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

Método setFavorite sem funcionar

Hmmm, favorite continua sem atualizar quando o método setFavorite é execu-


tado. O botão some como esperado, e o ícone de estrela aparece, mas a variável
favorite continua nula. Com esse resultado o usuário pode favoritar todas as
histórias.
O problema nesta abordagem é que nós não mantemos as coisas sincronizadas. Por
padrão, todas as propriedades do formulário são one-way-down binding entre a
propriedade filha e seu respectivo pai. Quando a propriedade pai atualiza ele será
atualizada para o filho, mas não o contrário.
Se não podemos sincronizar as propriedades entre componentes pai e filhos, precisa-
mos conhecer um pouco mais do Vue. Então, vamos dar uma pausa e estudar eventos,
antes de prosseguir.

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:

• Escutar um evento usando $on(event).


• Disparar um evento usando $emit(event).

Pode também:

• Escutar um evento, mas somente uma vez, usando $once(event).


• Remover a escuta de um evento usando $off().

8.1 Enviar e Escutar


Vamos começar com um exemplo simples.
O seguinte código representa uma página com um contador e um botão de votar.
Quando o botão é clicado, ele emite um evento, chamado voted. Há também um
Event Listener (“escutador” de eventos) para o evento, que irá aumentar o número
de votos quando o evento for disparado.

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ós registramos o event listener dentro do método created do ciclo de eventos do


Vue. O this é ligado à instância do Vue dentro do método vote e do evento created.
Então, nós podemos acessar $on e $emit usando this.$on e this.$emit.

Ciclo de Eventos do Vue


Os ciclos de eventos do Vue são funções executadas quando ocorrem eventos
relacionados ao Vue.
No Vue 2, temos os seguintes eventos:
Eventos Customizados 107

Evento Quando é chamado


beforeCreate Após a instância ter sido inicializada, antes da
configuração de events e watchers.
created Depois que a instância é criada.
beforeMount Logo antes de começar o mount.
mounted Depois que a instância acabou de ser montada na
DOM.
beforeUpdate Quando os dados mudam, antes do Virtual DOM ser
redesenhado e atualizado.
updated Quando os dados mudam, depois do Virtual DOM
ser redesenhado e atualizado.
activated Quando um componente é ativado.
deactivated Quando um componente é desativado.
beforeDestroy No momento antes da instância Vue ser destruída.
destroyed No momento depois da instância Vue ser destruída.

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 .

8.2 Comunicação entre Pai e Filho


As coisas são um pouco diferentes quando um componente pai precisa escutar um
evento de um componente filho. Não podemos usar this.$on/this.$emit desde que
this esteja ligada a diferentes instâncias.

Lembra do event listener v-on (@) ?


Um componente pai pode escutar os eventos emitidos por um componente filho
usando v-on diretamente no template, onde o componente filho é usado. Seguindo o
exemplo anterior, criamos um componente que possui a propriedade name. Em seu
template, ele mostrará um botão exibindo a propriedade name. Quando o botão é
clicado, precisamos emitir o evento vote.

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')
}
},
})

Food Component’s Template


<template id="food">
<button class="btn btn-default" @click="vote">{{ name }}</button>
</template>

Na instância pai, o <button> será substituído por <food @voted="countVote"></food>.


@voted="countVote" significa que quando o componente filho emitir o evento voted,
o método countVote será executado. Também pode-se remover o listener this.$on,
já que não precisamos mais dele.

Parent Component
new Vue({
el: '.container',
data: {
votes: 0
},
methods:
{
countVote: function () {
this.votes++
},
}
})
Eventos Customizados 109

Parent Component’s template


<div class="container text-center">
<p style="font-size: 140px;">
{{ votes }}
</p>
<food @voted="countVote" name="Cheeseburger"></food>
</div>

Se você executar este exemplo no navegador, você verá a seguinte saída:

Comunicação Pai/Filho de Componentes

8.3 Passando Argumentos


Vamos criar três instâncias do componente food. Cada instância terá o seu próprio
número de votos. Quando qualquer um dos componentes for votado, ele irá aumentar
o seu número de votos e emitir um evento para atualizar o total de votos, que está
localizado no componente pai.
Eventos Customizados 110

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

Múltiplas Instâncias do Componente

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.

Então, vamos passar uma delas para a função $emit.


Eventos Customizados 114

Food Component

Vue.component('food', {
...
methods: {
vote: function () {
this.votes++
this.$emit('voted', event.srcElement.textContent)
}
}
})

Dentro do pai, iremos enviar o voto para o Array de log.

new Vue({
el: '.container',
data: {
votes: 0,
log: []
},
methods:
{
countVote: function (food) {
this.votes++
this.log.push(food + ' received a vote.')
}
}
})

Para exibir o log, podemos adicionar uma lista no template HTML.


Eventos Customizados 115

<h1>Log:</h1>
<ul class="list-group">
<li class="list-group-item" v-for="vote in log"> {{ vote }} </li>
</ul>

Log de Votos

Bem fácil, não é mesmo?

8.4 Comunicação sem ser Pai/Filho


Vamos pegar o exemplo anterior e dar um passo adiante adicionando um botão de
reset. Quando clicado, ele irá reiniciar todos os contadores de votos. Como você pode
imaginar, o botão reset vai emitir um evento que deverá ser gerenciado por todos os
componentes. Mas como faremos para pegar este evento dentro de cada componente
filho?
Eventos Customizados 116

Olhando para o <food @voted="countVote">, podemos escutar através do evento


voted, mas nós não temos nenhum meio de emitir eventos para os componentes
filhos.
Para que todos os componentes consigam se comunicar entre si, podemos usar uma
instância do Vue como uma central de eventos. Então, dentro dos componentes, no
evento created, vamos registrar um listener usando bus.$on em vez de this.$on.
Consequentemente, usaremos o bus.$emit para disparar todos os eventos.

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

1 var bus = new Vue()


2
3 Vue.component('food', {
4 template: '#food',
5 props: ['name'],
6 data: function () {
7 return {
8 votes: 0
9 }
10 },
11 methods: {
12 vote: function (event) {
13 // instead of using this.name
14 // we can access event's element's text
15 var food = event.srcElement.textContent;
16 this.votes++
17 bus.$emit('voted', food)
18 },
19 reset: function () {
20 this.votes = 0
21 }
22 },
23 created () {
24 bus.$on('reset', this.reset)
25 }
26 })
27 new Vue({
28 el: '.container',
29 data: {
30 votes: {
31 count: 0,
32 log: []
33 }
34 },
Eventos Customizados 118

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

Comunicação entre Componentes

Atenção
Observe aqui que estamos usando:

bus.$on('voted', this.countVote)

Em vez disso, usar algo como:

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

8.5 Removendo Event Listeners


Para remover um ou mais eventos listeners, podemos usar $off. O método $off([event,
callback]) pode ser usado de várias maneiras.

1. $off(), sem argumentos, remove todos os event listeners.


2. $off([event]), remove todos os event listeners, dado um evento específico.
3. $off([event, callback]) remove o event listener de um callback específico.

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'])
}
}
})

Se você clicar no botão Stop, o código bus.$off(['voted']) será executado e os


votos não serão mais adicionados ao contador. Além disso, eles não serão exibidos
no log. No entanto, se você clicar no botão Reset Votes, os votos do componente Food
serão reiniciados para zero.
Para desabilitar também o botão reset, nós podemos remover todos os event listeners,
usando bus.$off().

8.6 De Volta ao Componente de Histórias


Lembra do exemplo de histórias dos capítulos anteriores?
Eventos Customizados 121

Estávamos prestes a sincronizar os dados do componente com os dados do seu


componente pai. A solução parece óbvia agora.
Vamos refatorar o componente Story para:

<story v-for="story in stories"


:story="story"
:favorite="favorite"
@update="updateFavorite">
</story>

Dentro do componente Story, emitiremos o evento update quando uma história é


marcada como favorita. A história tornando-se favorita, deverá ser passada como
um argumento no emit.

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;
}
},
})

Favoritando somente uma história


Eventos Customizados 123

Agora, o resultado desejado é alcançado e o usuário pode escolher apenas uma


história como a sua favorita, enquanto ele pode votar em tantas histórias quanto
quiser.

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.

Mais especificamente, o texto do botão deve exibir:

• ‘Pick Chariot’, antes que o usuário tenha escolhido qualquer carruagem;


• ‘Dismiss Horses’, quando a carruagem tem menos cavalos que a carruagem
selecionada;
• ‘Hire Horses’, quando a carruagem tem mais cavalos que a carruagem seleci-
onada;
• ‘Riding!’, quando a carruagem é a selecionada (este botão precisa ser desati-
vado).
• O usuário deverá ser capaz de escolher uma carruagem e, em seguida, escolher
qualquer outra que precisar.
• Por exemplo: O usuário escolheu uma carruagem com 2 cavalos e seu botão
assume o texto ‘Riding!’ (andar).
• Uma carruagem com 3 cavalos tem um cavalo a mais, então seu botão diz ‘Hire
Horses’ (contratar cavalo).
• Uma carruagem com 1 cavalo tem um cavalo a menos que a carroça do usuário,
então o botão diz ‘Dismiss Horses’ (dispensar cavalo).

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

Aplicamos a classe box para cada div, para a nossa conveniência.


O que este código faz é trocar a cor das caixas com o clique do botão.
Quando pressionado, ele chama a função flipColor, que reverte a o valor original
de color para true. Então, a diretiva v-bind:class troca a classe de red para blue
Bindings em classes e estilos 130

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.

<div v-bind:class="['classA', 'classsB', anotherClass]"></div>

Aplicando uma condição a uma classe, pode também ser feito com o uso do if
inline, dentro do Array

<div v-bind:class="['classA', condition ? 'classsB' : '']"></div>

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.

Usando o if inline, o exemplo de troca de cores fica semelhante a:


Bindings em classes e estilos 131

1 <div class="container text-center">


2 <div class="box" v-bind:class="[ color ? 'red' : 'blue' ]"></di\
3 v>
4 <div class="box" v-bind:class="[ color ? 'purple' : 'green' ]">\
5 </div>
6 <div class="box" v-bind:class="[ color ? 'red' : 'blue' ]"></di\
7 v>
8 <div class="box" v-bind:class="[ color ? 'purple' : 'green' ]">\
9 </div>
10 <button v-on:click="flipColor" class="btn btn-block btn-success">
11 Flip color!
12 </button>
13 </div>

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

9.2 Binding em estilos

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 <!-- shorthand -->


2 <div :style="niceStyle"></div>

1 data: {
2 niceStyle:
3 {
4 color: 'blue',
5 fontSize: '20px'
6 }
7 }

Também podemos declarar as propriedades de estado dentro de um objeto :style=”…“.

<div :style="{'color': 'blue', fontSize: '20px'}">...</div>

Podemos até referenciar variáveis dentro de um objeto:

<!-- Variável 'niceStyle' é a mesma utilizada nos exemplos anteriore\


s -->
<div :style="{'color': niceStyle.color, fontSize: niceStyle.fontSize\
}">
</div>
Bindings em classes e estilos 133

Style object binding

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 <!-- shorthand -->


2 <div :style="[niceStyle, badStyle]"></div>
Bindings em classes e estilos 134

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 .

9.3 Bindings em ação

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>

O exemplo acima possui um Array de objetos chamados de tasks e uma string


styleObject que contém somente uma propriedade. Com o uso do v-for, a lista de
tarefas é renderizada e cada tarefa tem a propriedade done com um valor booleano.
Dependendo do valor do atributo done, a classe é aplicada condicionalmente como
antes. Se a tarefa está completa, então o estilo css é aplicado, e a tarefa recebe o css
text-decoration com o valor line-through

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

Estilo das tarefas completas

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:

• Create: Criar novas histórias no banco de dados


• Read: Ler histórias existentes
• Update: Atualizar histórias existentes (como a votação)
• Delete: Apagar histórias

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.

Download do Código Fonte


Para usar a nossa API, você precisa realizar o download do código e iniciar o servidor.
Para fazer isso, siga as instruções a seguir:

1. Abra o terminal e crie um diretório (nós iremos criar ‘∼/themajestyofvuejs2’)

mkdir ∼/themajestyofvuejs2

1. Faça o download do código pelo GitHub

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

1. Navegue até o capítulo atual no diretório ‘apis’

cd ∼/themajestyofvuejs2/apis/stories

1. Execute o script de instalação

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 é:

Script de instalação: setup.sh


# navigate to chapter directory
$ cd ~/themajestyofvuejs2/apis/stories

# install dependencies
$ composer install

# Create the database


$ touch database/database.sqlite;

# Migrate & Seed


$ php artisan migrate;
$ php artisan db:seed;

# 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;

você poderá acessar 192.168.10.10:3000.

Se você baixou nossa API de demonstração, você pode continuar para a


próxima seção.

Se teve algum problema na instalação, certifique-se de instalar o PHP e o


Composer no seu sistema operacional.

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:

Nome da Coluna Tipo


id Inteiro, Auto Incremento
plot Texto (String)
writer Texto (String)
upvotes Inteiro

Não esqueça de adicionar alguns dados na tabela.

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

Método URI Ação


GET/HEAD api/stories Obtém todas as histórias
GET/HEAD api/stories/{id} Obtém uma história específica
POST api/stories Cria uma nova história
PUT/PATCH api/stories/{id} Atualiza uma história existente
DELETE api/stories/{id} Apaga uma específica história

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.

11.1 Obter Dados


Vamos dar uma olhada no último exemplo do capítulo de Componentes. Como
podemos ver, informamos o Array de histórias manualmente, dentro do objeto data
na instância Vue.

Array de histórias manual

new Vue({
data: {
stories: [
{
plot: 'My horse is amazing.',
writer: 'Mr. Weebl',
},
{
plot: 'Narwhals invented Shish Kebab.',
Trabalhando com Dados Reais 147

writer: 'Mr. Weebl',


},
...
]
}
})

Desta vez, queremos buscar as histórias existentes a partir do servidor.


Para fazer isso, vamos executar uma solicitação HTTP GET, usando jQuery no início.
Mas adiante migraremos para vue-resource1 para ver as diferenças entre os dois.
Para fazer a chamada AJAX vamos usar $.get(), uma função jQuery que carrega
dados do servidor através da solicitação HTTP GET. A documentação completa para
$.get() pode ser encontrada [aqui] (https://api.jquery.com/jquery.get/).

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.

A sintaxe do método $.get() é:

$.get(
url,
success
);

que é uma abreviação para:

1
https://github.com/vuejs/vue-resource
Trabalhando com Dados Reais 148

$.ajax({
url: url,
success: success
});

Então, o que fazemos agora? Queremos obter as histórias do servidor, usando


$.get('/api/stories'), e armazenar este resultado no Array stories do objeto
data do Vue.

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
})
}

Vamos ver a chamada Ajax em ação:


Trabalhando com Dados Reais 149

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>

Começamos informando a biblioteca jQuery pelo cdn2 . Em seguida, dentro do


evento mounted, realizamos a requisição GET ao endereço /api/stories. Quando a
requisição está concluída, armazenamos os dados da resposta (dentro do callback -
variável data) ao Array stories.

Obter histórias

Observe que, dentro do callback que referenciamos a variável stories,


usa-se vm.stories ao invés de this.stories. Fazemos isso porque a
variável this não está ligada a instância Vue dentro do callback. Então,
referenciamos a instância Vue através da variável vm, a fim de ter acesso à
ela a partir de qualquer lugar dentro do callback. Para ler mais a respeito
disso, dê uma olhada na documentação3 .
2
https://cdnjs.com/libraries/jquery/
3
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
Trabalhando com Dados Reais 151

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

A tabela não é processada corretamente, mas porquê?

Alguns elementos HTML, por exemplo <table>, têm restrições sobre


quais elementos podem aparecer dentro deles. Elementos personalizados
que não estão na lista serão removidos e, portanto, não serão renderi-
zados adequadamente. Em tais casos, você deve usar o atributo is para
indicar um elemento customizado.
Trabalhando com Dados Reais 153

Portanto, para resolver este problema, temos que usar o atributo especial is.

<table>
<tr is="my-component"></tr>
</table>

Nosso exemplo torna-se:

<tr v-for="story in stories" is="story" :story="story"></tr>

Tabela renderiza adequadamente

Bem, isso parece melhor!

11.3 Atualizando Dados


Tínhamos uma função que permitisse ao usuário votar em qualquer história que ele
quisesse. Mas agora queremos algo mais. Queremos que o servidor seja informado
Trabalhando com Dados Reais 154

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 })

Trouxemos de volta o método upvote e o colocamos dentro do componente story.


Fazendo uma requisição PATCH, fornecendo os novos dados, o servidor atualiza a
contagem de upvotes.
Trabalhando com Dados Reais 155

Votação

11.4 Removendo Dados


Vamos prosseguir para uma outra parte da funcionalidade, que é remover uma
história. Para remover a história do Array e da DOM, temos que procurá-la no Array
stories e removê-la de lá.

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

Anexamos um botão Excluir na coluna de ações, ligado a um método para remover


a história. O método deleteStory será:

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

É isso por enquanto.


Vamos continuar nosso exemplo no próximo capítulo, adicionando a funcionalidade
para Criar novas histórias, Editar histórias atuais e muito mais. Mas antes de tudo,
substituiremos jQuery por vue-resource.
12. Integrando o vue-resource
12.1 Visão Geral
Vue-resourse é um plugin para o Vue.js responsável em realizar chamadas Ajax.
O plugin fornece serviços para fazer requisições web e gerenciar as respostas, usando
XMLHttpRequest ou JSONP.
Vamos fazer novamente todas as requisições web que fizemos antes, mas agora
usando este plugin. Desta forma podemos abordar as diferenças entre os dois e você
poderá escolher o que lhe interessar melhor. jQuery é uma ótima biblioteca, mas se
você usá-la apenas para realizar requisições Ajax, talvez você possa considerar em
não usá-la.
Neste link1 você poderá encontrar instruções sobre a instalação e documentação do
vue-resource. Como estamos fazendo ao longo do livro, vamos adicionar esse plugin
através do cdn2 .
Para obter dados, usamos o método $http através da seguinte sintaxe:

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.

Ao invés de repassar o tipo de requisição como método, existe um atalho disponível


para todos os tipos suportados.

Atalho para os tipos de requisição

1 this.$http.get(url, [data], [options]).then(successCallback, errorCa\


2 llback);
3 this.$http.post(url, [data], [options]).then(successCallback, errorC\
4 allback);
5 this.$http.put(url, [data], [options]).then(successCallback, errorCa\
6 llback);
7 this.$http.patch(url, [data], [options]).then(successCallback, error\
8 Callback);
9 this.$http.delete(url, [data], [options]).then(successCallback, erro\
10 rCallback);

12.2 Migração

É hora de usar o vue-resource no nosso exemplo. Primeiro, devemos incluí-lo no


arquivo HTML.

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
})
}

A lista de histórias é carregada sem nenhum problema ao usar a sintaxe acima.


Vamos agora alterar as requisições PATH e DELETE, usando os atalhos do vue-
resource.

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

12.3 Melhorando Algumas Funcionalidades


Devemos adicionar algumas funcionalidades a mais, para criar a lista de histórias.
Podemos dar ao usuário a habilidade de alterar uma história, o seu escritor, ou até
mesmo criar novas histórias.

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
})

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 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.

<!-- Se a história estiver sob edição, exiba este grupo de botõe\


s -->
<div class="btn-group" v-else>
<button @click="updateStory(story)" class="btn btn-primary">
Atualizar
</button>
<button @click="story.editing=false" class="btn btn-default">
Cancelar
</button>
</div>
Integrando o vue-resource 165

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.

Criar uma Nova História


Agora, para uma tarefa um pouco mais complexa, vamos dar ao usuário a capacidade
de criar uma nova história e salvá-la no servidor.
Primeiro, devemos fornecer as caixas de texto para que a nova história possa ser
digitada. Para fazer isso, vamos criar uma história vazia e vamos anexá-la ao Array
stories usando o método javascript push().

Inicializaremos todos os atributos da história para null, exceto o editing. Uma vez
Integrando o vue-resource 166

que queremos manipular imediatamente a nova história, a propriedade editing será


definida como true.

1 var vm = new Vue({


2 ...
3 methods: {
4 createStory: function(){
5 var newStory={
6 "plot": "",
7 "upvotes": 0,
8 "editing": true
9 };
10 this.stories.push(newStory);
11 },
12 }
13 })

1 <p class="lead">Here's a list of all your stories.


2 <button @click="createStory()" class="btn btn-primary">
3 Adicionar novo
4 </button>
5 </p>

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 .

Criamos a nova função createStory e a colocamos em nossa instância Vue.


Logo abaixo da lista de histórias, adicionamos o botão “Adicionar novo”. Quando o
botão é clicado, o método createStory é invocado.
5
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
Integrando o vue-resource 167

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 })

Usamos o método this.$http.post para executar a solicitação POST. Na função


de callback, definimos a variável editing como false para mostrar novamente os
botões ação e ocultar as entradas do formulário e os botões de edição. Abaixo, vamos
atualizar o botão groups, de acordo com o novo método.

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>

Observamos um pequeno erro neste bloco de código. Quando estamos no modo


editing (bloco v-else), vemos que os botões update e save. são mostrados juntos,
mas só precisamos de um para cada história, uma vez que cada história será criada
ou atualizada. Não se pode fazer as duas coisas. Então, se a história é antiga e o
usuário está prestes a editá-la, precisamos do botão de atualização. Por outro lado,
se a história é nova, precisamos do botão de salvar.
Integrando o vue-resource 169

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.

1 <div class="btn-group" v-else>


2 <!--Se a história é antiga, então queremos atualizá-la
3 DICA: Se a história vem do banco de dados, então ele terá um id
4 -->
5 <button v-if="story.id" class="btn btn-primary" @click="updateSt\
6 ory(story)">
7 Update Story
8 </button>
9 <!--Se a história é nova, queremos armazená-la-->
10 <button v-else class="btn btn-success" @click="storeStory(story)\
Integrando o vue-resource 170

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.

Adicionando uma nova história

Agora sim! Não foi tão difícil, certo?


Depois de terminar esta parte, ao testar a app nos traz outro erro.
Integrando o vue-resource 171

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 });

Nesta situação, chamaremos fetchStories() dentro do callback.

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 })

É isso! Agora podemos criar e editar qualquer história que quisermos.

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

Resposta do servidor depois de criar nova história

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 })

Usamos Vue.set(story, 'id', response.data.id) ao invés de story.id = res-


Integrando o vue-resource 174

ponse.data.id porque dentro de tabela mostramos o id de cada história.

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.

12.4 Arquivo Javascript


Como você deve ter notado, nosso código está começando a se tornar grande.
Com o projeto em crescimento, está ficando mais difícil de manter. Para começar,
separaremos o código JavaScript do HTML.
Vamos criar um arquivo chamado app.js e salvá-lo no diretório js.
Todo o código JavaScript deve estar dentro desse arquivo de agora em diante. Para
incluir o script recém-criado em qualquer página HTML, basta adicionar essa tag:

<script src='/js/app.js' type="text/javascript"></script>

12.5 Código fonte


Abaixo está o código-fonte completo do exemplo anterior de Gerenciamento de
Histórias.
Se você tiver baixado nosso repositório, sugiro que abra seus arquivos locais com o
seu editor de texto favorito, porque o código é bastante grande. Os arquivos estão
localizados em * ∼/themajestyofvuejs2/apis/stories/public*.
Integrando o vue-resource 175

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

30 <pre>{{ $data }}</pre>


31 </div>
32 </div>
33 </main>
34 <template id="template-story-raw">
35 <tr>
36 <td>
37 {{story.id}}
38 </td>
39 <td class="col-md-6">
40 <input v-if="story.editing"
41 v-model="story.plot"
42 class="form-control">
43 </input>
44 <!--in other occasions show the story plot-->
45 <span v-else>
46 {{story.plot}}
47 </span>
48 </td>
49 <td>
50 <input v-if="story.editing"
51 v-model="story.writer" class="form-control">
52 </input>
53 <!--in other occasions show the story writer-->
54 <span v-else>
55 {{story.writer}}
56 </span>
57 </td>
58 <td>
59 {{story.upvotes}}
60 </td>
61 <td>
62 <div class="btn-group" v-if="!story.editing">
63 <button @click="upvoteStory(story)"
64 class="btn btn-primary">
Integrando o vue-resource 177

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

100 <script src="http://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.js"\


101 ></script>
102 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/0.7\
103 .0/vue-resource.js"></script>
104 <script src='/js/app.js' type="text/javascript"></script>
105 </body>
106 </html>

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

27 // fetch again all stories with


28 // vm.fetchStories();
29 // Or Better, update the id of the created story
30 Vue.set(story, 'id', response.data.id);
31
32 //Set editing to false to show actions again and hid\
33 e the inputs
34 story.editing = false;
35 });
36 },
37 }
38 })
39
40 new Vue({
41 el: '#v-app',
42 data: {
43 stories: [],
44 story: {}
45 },
46 mounted: function () {
47 this.fetchStories()
48 },
49 methods: {
50 createStory: function () {
51 var newStory = {
52 plot: "",
53 upvotes: 0,
54 editing: true
55 };
56 this.stories.push(newStory);
57 },
58 fetchStories: function () {
59 console.log('fetsi')
60 var vm = this;
61 this.$http.get('/api/stories')
Integrando o vue-resource 180

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:

• Criar uma tabela e exibir filmes existentes


• modificar filmes existentes
• armazenar novos filmes no banco de dados
• excluir filmes do banco de dados

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

Se você não faz isso, você deve executar este comando:


8
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/codes/chapter12
Integrando o vue-resource 182

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 é:

HTTP Method URI Action


GET/HEAD api/movies Obtém todos os filmes
GET/HEAD api/movies/{id} Obtém um filme específico
POST api/movies Cria um novo filme
PUT/PATCH api/movies/{id} Atualiza um filme existente
DELETE api/movies/{id} Remove um filme específico

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

Exemplo do gerenciamento de filmes

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.

13.2 Integrando axios


Axios3 é um cliente HTTP (baseado em Promise) para o navegador e o Node.js.
Além disso, abrange quase tudo que vue-resource fornece e possui uma API muito
semelhante, e é universal, suporta cancelamento, e possui o que chamamos de
TypeScript Definitions4
Realizando uma requisição GET com axios:

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

// Faz uma requisição para a url */user* informando um ID


axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

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>

Para buscar as histórias, faremos uma requisição GET no formulário correspondente:


Visão Geral do axios 186

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.

Vamos continuar, agora com DELETE e PATCH, usando os referentes métodos.


Visão Geral do axios 187

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)
}

Nós substituímos os métodos AJAX com estes, sem esforço!

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

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
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

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.

1 <!-- If story is under edit, display this group of buttons -->


2 <div class="btn-group" v-else>
3 <button @click="updateStory(story)" class="btn btn-primary">
4 Update Story
5 </button>
6 <button @click="story.editing=false" class="btn btn-default">
7 Cancel
8 </button>
9 </div>
Visão Geral do axios 192

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.

Criar uma nova história


Agora, para uma tarefa um pouco mais complexa, vamos dar ao usuário a capacidade
de criar uma nova história e salvá-la no servidor.
Primeiro, devemos fornecer as caixas de texto para que a nova história possa ser
digitada. Para fazer isso, vamos criar uma história vazia e vamos anexá-la ao Array
stories usando o método javascript push().

Inicializaremos todos os atributos da história para null, exceto o editing. Uma vez
Visão Geral do axios 193

que queremos manipular imediatamente a nova história, a propriedade editing será


definida como true.

1 var vm = new Vue({


2 ...
3 methods: {
4 createStory: function(){
5 var newStory={
6 "plot": "",
7 "upvotes": 0,
8 "editing": true
9 };
10 this.stories.push(newStory);
11 },
12 }
13 })

1 <p class="lead">Here's a list of all your stories.


2 <button @click="createStory()" class="btn btn-primary">
3 Add a new one?
4 </button>
5 </p>

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 .

Criamos a nova função createStory e a colocamos em nossa instância Vue.


Logo abaixo da lista de histórias, adicionamos o botão “Adicionar novo”. Quando o
botão é clicado, o método createStory é invocado.
7
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
Visão Geral do axios 194

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 })

Na função de callback, definimos a variável editing como false para mostrar


novamente os botões ação e ocultar as entradas do formulário e os botões de edição.
Abaixo, vamos atualizar o botão groups, de acordo com o novo método.

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

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>

Observamos um pequeno erro neste bloco de código. Quando estamos no modo


editing (bloco v-else), vemos que os botões update e save. são mostrados juntos,
mas só precisamos de um para cada história, uma vez que cada história será criada
ou atualizada. Não se pode fazer as duas coisas. Então, se a história é antiga e o
usuário está prestes a editá-la, precisamos do botão de atualização. Por outro lado,
se a história é nova, precisamos do botão de salvar.
Visão Geral do axios 196

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.

1 <div class="btn-group" v-else>


2 <!--Se a história é antiga, então queremos atualizá-la
3 DICA: Se a história vem do banco de dados, então ele terá um id
4 -->
5 <button v-if="story.id" class="btn btn-primary" @click="updateSt\
6 ory(story)">
7 Update Story
8 </button>
9 <!--Se a história é nova, queremos armazená-la-->
10 <button v-else class="btn btn-success" @click="storeStory(story)\
Visão Geral do axios 197

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.

Adicionando uma nova história

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 });

Nesta situação, chamaremos fetchStories() dentro do callback.

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 })

É isso! Agora podemos criar e editar qualquer história que quisermos.

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

Resposta do servidor depois de criar nova história

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 })

Usamos Vue.set(story, 'id', response.data.id) ao invés de story.id = res-


Visão Geral do axios 201

ponse.data.id porque dentro de tabela mostramos o id de cada história.

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.

13.5 Arquivo Javascript


Como você deve ter notado, nosso código está começando a se tornar grande.
Com o projeto em crescimento, está ficando mais difícil de manter. Para começar,
separaremos o código JavaScript do HTML.
Vamos criar um arquivo chamado app.js e salvá-lo no diretório js.
Todo o código JavaScript deve estar dentro desse arquivo de agora em diante. Para
incluir o script recém-criado em qualquer página HTML, basta adicionar essa tag:

<script src='/js/app.js' type="text/javascript"></script>

13.6 Código fonte


Abaixo está o código-fonte completo do exemplo anterior de Gerenciamento de
Histórias.
Se você tiver baixado nosso repositório, sugiro que abra seus arquivos locais com o
seu editor de texto favorito, porque o código é bastante grande. Os arquivos estão
localizados em * ∼/themajestyofvuejs2/apis/stories/public*.
Visão Geral do axios 202

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

30 <pre>{{ $data }}</pre>


31 </div>
32 </div>
33 </main>
34 <template id="template-story-raw">
35 <tr>
36 <td>
37 {{story.id}}
38 </td>
39 <td class="col-md-6">
40 <input v-if="story.editing"
41 v-model="story.plot"
42 class="form-control">
43 </input>
44 <!--in other occasions show the story plot-->
45 <span v-else>
46 {{story.plot}}
47 </span>
48 </td>
49 <td>
50 <input v-if="story.editing"
51 v-model="story.writer" class="form-control">
52 </input>
53 <!--in other occasions show the story writer-->
54 <span v-else>
55 {{story.writer}}
56 </span>
57 </td>
58 <td>
59 {{story.upvotes}}
60 </td>
61 <td>
62 <div class="btn-group" v-if="!story.editing">
63 <button @click="upvoteStory(story)"
64 class="btn btn-primary">
Visão Geral do axios 204

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

100 <script src="https://unpkg.com/vue@2.1.10/dist/vue.js"></script>


101 <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
102 <script src='/js/app.js' type="text/javascript"></script>
103 </body>
104 </html>

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

29 Or Better, update the id of the created story


30 //Vue.set(story, 'id', response.data.id);
31 //Set editing to false to show actions again and hid\
32 e the inputs
33 story.editing = false;
34 });
35 },
36 }
37 })
38
39 // Vue.prototype.$http = axios
40
41 new Vue({
42 el: '#v-app',
43 data: {
44 stories: [],
45 story: {}
46 },
47 mounted: function () {
48 this.fetchStories()
49 },
50 methods: {
51 createStory: function () {
52 var newStory = {
53 plot: "",
54 upvotes: 0,
55 editing: true
56 };
57 this.stories.push(newStory);
58 },
59 fetchStories: function () {
60 var vm = this;
61 axios.get('/api/stories')
62 .then(function (response) {
63 // set data on vm
Visão Geral do axios 207

64 var storiesReady = response.data.map(function (s\


65 tory) {
66 story.editing = false;
67 return story
68 })
69 // vm.stories = storiesReady
70 Vue.set(vm, 'stories', storiesReady)
71 });
72 },
73 }
74 });
Visão Geral do axios 208

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:

• Criar uma tabela e exibir filmes existentes


• modificar filmes existentes
• armazenar novos filmes no banco de dados
• excluir filmes do banco de dados

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

Se você não faz isso, você deve executar este comando:

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 é:

HTTP Method URI Action


GET/HEAD api/movies Obtém todos os filmes
GET/HEAD api/movies/{id} Obtém um filme específico
POST api/movies Cria um novo filme
PUT/PATCH api/movies/{id} Atualiza um filme existente
DELETE api/movies/{id} Remove um filme específico

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

Exemplo do gerenciamento de filmes

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.

Em situações como esta, os desenvolvedores projetam a API para dividir os dados


retornados em páginas.
A resposta HTTP irá conter alguns simples meta-dados, ao lado dos dados principais,
para informá-lo sobre o total de itens, itens por página itens, etc.
Para ajudá-lo a percorrer as páginas, a api fornecerá informações como a página
atual, a próxima página e a página anterior.
Paginação 212

Exemplo de resposta com dados paginados

{
"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": [...]
}

Os dados da paginação também poderiam estar dentro de um objeto próximo ao


data, ou em qualquer lugar que os desenvolvedores da API tenham decidido.

Exemplo de resposta com dados paginados

{
"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

Histórias não estão aparecendo

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"
}

Vamos modificar nosso método fetchStories para atualizar o objeto pagination ,


Paginação 216

cada vez que ele busca histórias do banco de dados.

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>

Botão anterior desativado

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.

ECMAScript 6 (ES6), também conhecido como ES2015, é a versão mais recente do


padrão ECMAScript. A especificação ES6 foi finalizada em junho de 2015. É uma
atualização significativa para a linguagem e a primeira grande atualização desde
que ES5 foi padronizada em 2009. A implementação de recursos ES6 nas principais
engines de JavaScript está em andamento1 .

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.

De agora em diante, desenvolveremos nossos exemplos usando os recursos ES6.

15.2 Declarando Variáveis

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.

1 const name = "Alex"


2
3 name = "Kostas" //throws error
ECMAScript 6 225

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.

15.3 Arrow Functions


Uma das partes mais interessantes do ECMAScript 6 são as Arrow Functions. Arrow
Functions são funções definidas com uma nova sintaxe que utiliza uma “seta” (⇒).
Diferente de functions “normais”, as Arrows Functions compartilham o mesmo this
da aplicação dentro do seu escopo.
Por exemplo, o código a seguir usa uma Arrow Function para pegar um simples
argumento e incrementá-lo em 1:

var increment = value => value + 1;


increment(5) //returns 6

// equivalent to:

var increment = function(value) {


return value + 1;
};

Outro exemplo com Arrow Functions, obter 2 argumentos e retorna sua soma:
ECMAScript 6 226

var sum = (a, b) => a + b;


sum(5, 10) //retorna 15

// equivalente a:

var sum = function(a, b) {


return a + b;
};

Um exemplo de Arrow Function sem argumentos, e que usa mais de uma linha,
limitada pela declaração “{ }”:

var sayHiAndBye = () => {


console.log('Hi!');
console.log('Bye!');
};
sayHiAndBye()

// equivalente a:

var sayHiAndBye = function() {


console.log('Hi!');
console.log('Bye!');
};

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

1 export name = 'Alex'

main.js

1 import {name} from './module'


2 console.log('Hello', name)
3 //outputs "Hello Alex"

Você também pode exportar variáveis junto com funções uma a uma.

module.js

1 export var name = 'Alex'


2 export function getAge(){
3 return 22;
4 }

main.js

1 import {name, getAge} from './module'


2 console.log(name, 'is', getAge())

Ou dentro de um objeto:
ECMAScript 6 228

module.js

1 var name = 'Alex'


2 function getAge(){
3 return 22;
4 }
5 export default {name, getAge}

main.js

1 import person from './module'


2 console.log( person.name, 'tem', person.getAge() )
3 //saídaoutputs "Alex tem 22"

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

//To create a getter, use the keyword get.


get area() {
return this.calcArea();
}
//To create a setter, you do the same, using the keyword set.
}

//child class
class Square extends Rectangle{
constructor(side) {
//call parent's constructor
super(side, side)
}
}

var square = new Square(5);

console.log(square.area); //outputs 25

15.6 Parâmetros padrão


Com o ES6 você pode-se definir valores padrão nos parâmetros.

function divide(x, y = 2){


return x/y;
}

// equivalent to:

function divide(x, y){


y = y == undefined ? 2 : y;
return x/y;
}
ECMAScript 6 230

15.7 Templates Literais (Template literals)


Template literals são strings em que é possível anexar expressões. Você pode usar
estas strings em várias linhas ou em uma somente uma (chamamos isso de de
interpolação). Nas especificações mais antigas (ES2015) os Template literals eram
chamadas de “template strings”.
Template literals são “ativados” pelo caractere aspas invertidas (`) ao invés de aspas
duplas. Com eles podemos usar ${expression}, onde expression pode ser uma
função, variável ou expressão.

Template literals - Usando variáveis

let name = 'Alex'


console.log(`Olá ${name}`)

// equivalent to:
console.log('Olá '+ name )

Template literals - Usando funções

add = (a, b) => a + b

let [a, b] = [10, 2]

console.log(`If you have ${a} eggs and you buy ${b}


more you'll have ${add(a,b)} eggs!`)

// equivalent to:
console.log('If you have ' + a +' eggs and you buy ' + b +
'\nmore you\'ll have '+ add(a,b) + ' eggs!')
ECMAScript 6 231

Template literals - Usando expressões

let [a, b] = [10, 2]

console.log(`If you have ${a} eggs and you buy ${b}


more you'll have ${a + b} eggs!`)

// equivalent to:
console.log('If you have ' + a +' eggs and you buy ' + b +
'\nmore you\'ll have '+ (a + b)*1 + ' eggs!')

É possível dividir uma mensagem em múltiplas linhas usando '\n'.


16. Workflow Avançado
Todas estas características do ES6 podem te animar, mas há um problema aqui.
Como mencionado anteriormente, nem todos os navegadores suportam totalmente
os recursos ES6/ES2015.
Para poder escrever esta nova sintaxe JavaScript hoje, precisamos ter um interme-
diário que pegará nosso código e o converterá (chamamos isso de transpile) para
[Vanilla JS] (http://vanilla-js.com/), que todo navegador entende. Este procedimento
é muito realmente importante em ambientes de produção.
Deixe-me contar uma história. Alguns anos atrás, um colega de trabalho começou a
usar alguns recursos legais da JS que não eram totalmente suportados por todos os
navegadores. Poucos dias depois, nossos usuários começaram a se queixar de algumas
páginas do nosso site não mostradas corretamente, mas não conseguimos descobrir o
porquê. Nós testamos em diferentes PCs, telefones Android, iPhones, etc., e foi 100%
funcional em todos os nossos navegadores.
Mais tarde, ele descobriu que as versões mais antigas do Safari móvel não suportaram
o seu código. Não seja esse cara!
Algumas vezes é muito difícil saber se o código que você escreve vai funcionar
bem em todos os navegadores, incluindo o navegador do Facebook, que é usado
quando clicamos em um link dentro da app mobile do Facebook.

16.1 Compilando ES6 com Babel


Babel será nosso intermediário. O Babel é um compilador de JavaScript, que nos
permite usar a próxima geração de JavaScript, já que todo o código é convertido para
o JavaScript Vanilla. Este tipo de compilador é chamado de source-to-source.
Workflow Avançado 233

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

echo {} > package.json

Nota do tradutor: Você pode usar o seguinte comando também npm init -y
Para instalar o Babel, execute o seguinte comando:

npm install babel-cli --save-dev

Saída no Terminal

Quando terminar, seu arquivo package.json deve ser algo assim:


Workflow Avançado 235

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 .

O diretório do seu projeto ser semelhante a:


2
https://docs.npmjs.com/files/package.json
Workflow Avançado 236

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:

npm install babel-preset-es2015 --save-dev

echo { "presets": [ ["es2015"] ]} > .babelrc


3
https://babeljs.io/docs/plugins/preset-es2015/
Workflow Avançado 237

Dica
Se o segundo comando falhar, inclua o conteúdo do arquivo dentro de
aspas como esta:
echo '{ "presets": [ ["es2015"] ]}' > .babelrc

Criando Apelidos para a Compilação


Em vez de executar o Babel diretamente da linha de comando, vamos colocar
nossos comandos dentro de scripts do npm. Adicione o campo scripts no arquivo
package.json e crie um comando Babel como uma build, da seguinte forma:

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

const sum = (a, b) => a + b;


console.log(sum(5,3));

É isso Agora podemos executar:

npm run build

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";

var sum = function sum(a, b) {


return a + b;
};
console.log(sum(5, 3));

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!"

Não se esqueça de compilar o seu * js * usando o Babel antes de incluí-lo no seu


HTML.

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

16.2 Automatização do Workflow com Gulp

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

Por que usar um task runner?


Em uma palavra: Automação. Quanto menos trabalho você tiver que fazer
ao executar tarefas repetitivas, como “minificação”, compilação, teste de
unidade, instalação, etc., mais fácil será seu trabalho.
Workflow Avançado 243

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:

npm install gulp-cli --global

Então vamos instalar o Gulp no devDependencies do nosso projeto:

npm install gulp --save-dev

Agora que temos o gulp instalado, criaremos um arquivo gulpfile.js na raiz do


nosso projeto:
Workflow Avançado 244

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).

npm install gulp-babel --save-dev

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']);

//basic babel task


gulp.task('babel', function() {
return gulp.src('src/*.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(gulp.dest('assets/js/'))
})
Workflow Avançado 245

Esta tarefa basicamente diz ao babel para transformar todos os arquivos js no


diretório src usando es2015 e colocá-los dentro do diretório assets/js.

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

const gulp = require('gulp');


const babel = require('gulp-babel');

gulp.task('default', ['watch']);

//basic babel task


gulp.task('babel', function() {
return gulp.src('src/*.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(gulp.dest('assets/js/'))
})

//the watch task


gulp.task('watch', function() {
gulp.watch('src/*.js', ['babel']);
})

Quando executamos gulp watch no terminal, o gulp está observando as mudanças


em todos os nossos arquivos .js no diretório especificado. Toda vez que fazemos uma
mudança, gulp executa a tarefa babel e os arquivos em assets/js serão atualizados.
Workflow Avançado 246

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

Gulp observando o arquivo js

16.3 Module Bundling com Webpack

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

const sum = (a, b) => a + b + '$';


console.log( `Alex, you have to pay ${sum(pizza, beer)}`)

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

export const name = 'Alex'

Vamos usar o nome do cliente a partir deste arquivo

src/sum.js

import { name } from './client'

const pizza = 10
const beer = 5

const sum = (a, b) => a + b + '$';


console.log(`${name} you have to pay ${sum(pizza, beer)}`)

Ótimo! se você executar node assets/js/sum.js temos o seguinte resultado:

Saída de sum.js

Esperamos que o mesmo comportamento se aplique quando abrimos o arquivo html


no navegador, mas isso não acontece! Teremos um erro.
Workflow Avançado 249

Erro: “require is not defined”

Verifique o node assets/js/sum.js e observe o var _client = require ('./ cli-


ent'); no início do arquivo. A razão pela qual recebemos o erro no navegador é
porque require() não existe no navegador/JavaScript do lado do cliente. O que
devemos fazer é agrupar os módulos em um único arquivo para que ele possa ser
incluído dentro de uma tag <script>.
Workflow Avançado 250

assets/js/sum.js

É aqui que precisamos dos Module Bundlers como Webpack6 ou Browserify7 .

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

O que o Webpack faz

Instalação
Vamos instalar o Webpack globalmente e depois adicioná-lo como dependência em
nosso projeto.

npm install webpack -g

npm install webpack --save-dev

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.

webpack assets/js/sum.js assets/webpacked/app.js

8
https://www.vagrantup.com/
Workflow Avançado 252

Saída do Webpack

Após a conclusão da compilação, podemos usar o js, assets/webpacked/app.js,


dentro do sum.html. Também podemos executá-lo no terminal usando node as-
sets/webpacked/app.js.

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

Para integrar o Webpack ao Gulp, eu usei um plugin chamado webpack-stream11 .

npm install webpack-stream --save-dev

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

const gulp = require('gulp');


const babel = require('gulp-babel');
const webpack = require('webpack-stream');

gulp.task('default', ['watch']);

//basic babel task


gulp.task('babel', function() {
return gulp.src('src/*.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(gulp.dest('assets/js/'))

})

//basic webpack task


gulp.task('webpack', ['babel'], function() {
return gulp.src('assets/js/sum.js')
.pipe(webpack({
output: {
path: "/assets/webpacked",
filename: "app.js"
11
https://www.npmjs.com/package/webpack-stream
Workflow Avançado 254

}
}))
.pipe(gulp.dest('assets/webpacked'));
})

//the watch task


gulp.task('watch', function() {
gulp.watch('src/*.js', ['babel', 'webpack']);
})

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

Para juntar coisas você usa o Webpack15 or Browserify16 .


No próximo capítulo, mergulharemos nos “Componentes de Arquivo Único” do Vue
e usamos várias ferramentas que o Vue oferece, juntamente com as ferramentas que
aprendemos.

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:

npm install vue-cli -g

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.

Então, se você executar


3
https://github.com/vuejs/vue-cli#custom-templates
4
https://github.com/vuejs-templates
Trabalhando com Single File Components 259

vue init webpack-simple simple-project

Você terá um diretório chamado simple-project com a seguinte estrutura:

Estrutura do template webpack-simple

Para o nosso exemplo, usaremos o template completo de webpack, então nosso


comando será assim:

vue init webpack stories-classic-project

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.

Rules feross/standard airbnb/javascript


Indentação 2 espaços 2 espaços
Ponto-e-vírgula Não! Sim
Variáveis não utilizadas Não permitido Não permitido
Aspas Simples Simples
Usa === ao invés de == Sim Sim
Número de linhas vazias 1 2
permitidas
Espaço após o nome da função Sim Não
Comece uma linha com [ Não Sim
Feche arquivos com um Sim Sim
caractere de nova linha
Trailing commas8 permitido Não Não

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.

Instalação do Vue’s Template

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 .

17.2 Template Webpack


Para completar a configuração do nosso projeto, precisamos instalar suas dependên-
cias. Vamos fazer o seguinte:

cd stories-classic-project

npm install

npm run dev

O terminal mostra Listening at http://localhost:8080. Você deve esperar até que a


mensagem webpack: bundle is now VALID apareça.
11
https://mochajs.org/
12
http://www.seleniumhq.org/
Trabalhando com Single File Components 263

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

Os arquivos com os quais você costuma trabalhar são:

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>

Dentro da tag <script>, o componente contém apenas data. Não há necessidade de


definir um template. Ele será automaticamente vinculado ao bloco <template>. A tag
<template> define o template do componente. Pense que <template> é como uma
tag <template-hello> no seu HTML.
Poderíamos ter apenas o bloco <script>, mas desta forma, não aproveitamos os
benefícios do Single File Components.
Lembre do export, uma funcionalidade do ES6, que é gerenciada através das
ferramentas (transpilers + module bundlers) que foram instaladas.

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.

O script deve exportar um objeto Vue.js. Exportar um construtor que herda de


Vue.extend() também é suportado, mas preferimos usar um objeto Vue.js.
Trabalhando com Single File Components 268

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

import Vue from 'vue'


import App from './App'

/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})

Ele importa o Vue como um módulo do node_modules e o componente App do


diretório src.
Sempre que você precisa importar um script ou um componente globalmente, você
pode colocá-lo dentro de main.js.

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

17.3 Criando Arquivos .vue

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>

Vamos criar o formulário de login, e um novo arquivo chamado Login.vue.


Trabalhando com Single File Components 272

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>

E aí está! Para visualizar o arquivo no navegador temos que incluir o componente


Login em algum lugar. Então, vamos importá-lo no componente App e anexá-lo a
propriedade compoents.

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

Vamos criar outro componente, desta vez para registro.

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>

Então podemos importá-lo no arquivo App.vue.

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>
...
...

O componente Register aparece, quando verificamos o navegador.

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>

Este é o arquivo Stories.vue. Podemos usá-lo em nosso arquivo principal App.vue.


Neste ponto, as histórias serão criadas para se ter mais simplicidade. É hora de
importar o componente, assim como fizemos antes.
Trabalhando com Single File Components 279

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

Ótimo! Agora temos uma página para exibir todas as listagens.


Trabalhando com Single File Components 280

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>

Este é o arquivo Famous.vue. Temos o array de stories filtradas usando propriedades


computadas, assim como vimos em capítulos anteriores, e criamos o template para
exibí-los.

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

Página de registro com as principais histórias

Preste atenção ao caminho do arquivo de importação. Agora que os dois arquivos


estão no mesmo diretório, você tem que usar ./Famous em vez do caminho completo.
Este é um erro fácil de acontecer, especialmente se você não está familiarizado com
Trabalhando com Single File Components 284

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:

1. Usando as propriedades do componente.


2. Usando um store global.

18.1 Compartilhando propriedades


A primeira coisa que vamos fazer é mover o Array ‘stories’ para o componente App.

src/App.vue

1 <script>
2 ...
3
4 export default {
5 components: {
6 ...
7 },
8 data () {
Eliminando Dados Duplicados 286

9 // here we place the stories Array


10 return {
11 stories: [
12 {
13 plot: 'My horse is amazing.',
14 writer: 'Mr. Weebl',
15 upvotes: 28,
16 voted: false
17 },
18 {
19 plot: 'Narwhals invented Shish Kebab.',
20 writer: 'Mr. Weebl',
21 upvotes: 8,
22 voted: false
23 },
24 {
25 plot: 'The dark side of the Force is stronger.',
26 writer: 'Darth Vader',
27 upvotes: 52,
28 voted: false
29 },
30 {
31 plot: 'One does not simply walk into Mordor',
32 writer: 'Boromir',
33 upvotes: 74,
34 voted: false
35 }
36 ]
37 }
38 }
39 }
40 </script>

O próximo passo é remover os componentes data() de Stories e Famous e declarar


a propriedade stories.
Eliminando Dados Duplicados 287

Vamos fazer isso no primeiro componente.

src/components/Stories.vue

1 <script>
2 export default {
3 props: ['stories']
4 }
5 </script>

Temos de atualizar a maneira como referenciamos nosso componente dentro do


App.vue.

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>

Aqui nós ligamos a propriedade stories no Array stories.


Eliminando Dados Duplicados 288

A mesma saída, usando props

Conseguimos nossas “histórias” novamente, obtidas do componente pai!


Não podemos fazer o mesmo no componente Famous ainda, porque não é referenciado
dentro de App.vue. Teremos que passar a nossa matriz para o componente Register
para aí então repassar para o componente Famous.
Eliminando Dados Duplicados 289

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>

Esta implementação funciona, mas não é eficiente, porque o componente Famous


nnão e independente. Isso significa que não podemos usá-lo sempre que quisermos,
a menos que passemos os dados do componente sempre que necessário.
Em um cenário onde um componente não independente está profundamente ani-
nhado, você deverá passar dados por componentes, de um para outro, apenas para
que ele funcione. No nosso caso, se nós quisemos usar Famous dentro de Register,
nós teríamos que repassar o Array stories pelo componente Register .

18.2 Store Global


Usar “props” parecia bom no começo, mas como visto no componente Famous,
à medida que um projeto fica maior e com componentes aninhados com outros,
gerenciar dados entre eles se torna algo complicado e difícil de manter.
Então, vamos tornar os dados dos nossos exemplos um pouco mais fáceis de
trabalhar. Podemos extrair o Array stories para um arquivo .js, armazenar em
uma constante e depois importá-lo nos locais desejáveis.
Vamos criar o arquivo store.js no diretório /src.
Eliminando Dados Duplicados 291

src/store.js

1 export const store = {


2 stories: [
3 {
4 plot: 'My horse is amazing.',
5 writer: 'Mr. Weebl',
6 upvotes: 28,
7 voted: false
8 },
9 {
10 plot: 'Narwhals invented Shish Kebab.',
11 writer: 'Mr. Weebl',
12 upvotes: 8,
13 voted: false
14 },
15 {
16 plot: 'The dark side of the Force is stronger.',
17 writer: 'Darth Vader',
18 upvotes: 52,
19 voted: false
20 },
21 {
22 plot: 'One does not simply walk into Mordor',
23 writer: 'Boromir',
24 upvotes: 74,
25 voted: false
26 }
27 ]
28 }

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

Depois de ter armazenado todos os dados dentro do arquivo store.js podemos


importá-lo dentro de Stories.vue usando ES6.

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>

Estamos usando v-for para renderizar itens de um Array (store.stories). Nossa


lista de histórias está sendo exibida como antes.
Eliminando Dados Duplicados 293

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>

O mesmo se aplica a Famous.vue.

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

Se não tivéssemos ligado ao array storeis, a propriedade computada famous() teria


que ser atualizado para filtrar this.store.stories.
Uma vez que você se acostume com com os objetos globais, acreditamos que você
vai adorar usá-los!

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.

19.1 Componentes dinâmicos

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

// Component Hello returns a template containing a "msg" property of\


data
export default {
components: {
Hello
}
}
</script>

Criamos um novo projeto e modificamos o arquivo Hello.vue.


Temos o mesmo resultado de antes, mas agora estamos usando o elemento <compo-
nent is="hello">. O componente Hello está ligado ao atributo is. Para ver como
isso funciona dinamicamente, veja o próximo exemplo onde alternamos entre dois
diferentes componentes, clicando em seus links.
Primeiro, crie um componente semelhante com uma mensagem diferente, denomi-
nada Greet.vue.

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

Criamos o componente Greet para exibir uma mensagem diferente do Hello.vue.


Vamos importá-lo para a App e configurar a habilidade de alternar entre dois
componentes.

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

Login.vue em uma terceira. Não esqueça que o componente Register possui o


componente Faous, no qual retorna as histórias mais lidas.
Leia atentamente o próximo exemplo.

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

Uma página para cada componente

O Array chamado page contém os componentes no qual queremos renderizar.


Estamos usando a diretiva v-for para criar uma aba para cada um deles.
Alternando Components 302

Para navegar entre as abas, criamos um método chamado setPage.


A propriedade activePage é inicialmente configurada para 'stories'.
Quando a aba é clicada, activePage muda a ordem do componente que deve ser
exibido.
Para determinar qual aba deve estar ativa, a propriedade if é aplicada, no qual seta
a classe active se a propriedade activePage combina com o nome do componente.
Para tornar a primeira letra de cada aba capitalizada, criamos um Vue.filter(),
chamado capitalize.
Com estas poucas e simples linhas de código, realizamos um sistema de navegação
simples, trocando alguns componentes.

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.

npm install vue-router

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

import Vue from 'vue'


import VueRouter from 'vue-router'
Vue.use(VueRouter)

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

import Vue from 'vue'


import VueRouter from 'vue-router'
import Hello from '../src/components/Hello.vue'
import Login from '../src/components/Login.vue'

Vue.use(VueRouter)

const routes = [
{ path: '/', component: Hello },
{ path: '/login', component: Login }
]

No Array definimos duas rotas:

1. Quando http://localhost:3000/ é acessada (Ou qualquer porta que você


possa usar), o componente Hello.vue será renderizado.
2. Quando http://localhost:3000/login é acessado, o componente Login.vue
será renderizado.
Vue Router 306

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') }

O próximo passo é associar o router no vue, no componente App.vue.

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>

O <router-view></router-view> é o lugar onde a mágina acontece, enquanto os


componentes são renderizados.
router-link é o componente que permite aos usuários navegar através das rotas. A
propriedade to define a localização do componente, combinando uma rota definida
no main.js. Por padrão, router-link irá renderizar uma tag <a> com o atributo
href configurado para o URI desejado. Podem haver mais argumentos para uma
navegação mais complexa, que revisaremos em breve.

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.

20.3 Rotas Nomeadas


Embora as opções que revisamos para o roteamento atendam às nossas necessidades
em um pequeno projeto, como o nosso exemplo, presumivelmente você precisará
Vue Router 308

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.

20.4 Modo “History”


Antes de seguir em frente, gostaria de salientar algo sobre a URL do navegador,
quando usamos vue-router. Como você pode ver, quando uma rota muda, o símbolo
‘#’ é anexado a URL. Por exemplo, a URL é /#/login, quando navegamos para a
página de login.
Isso é causado pelo modo padrão do vue-router, chamado de “hash mode”, no qual a
URL usa um “hash” (#) para simular uma URL completa, mas sem que haja um refresh
na página quando a URL muda. Para se livrar do “hash mode”, podemos mudar para
history mode. Também vamos definir outra opção, base, que define um caminho
padrão para todas as navegações do router.
Alterar isso para qualquer coisa de seu valor inicial, que é igual a “default”, resultará
em caminhos que sempre incluirão a novo valor no URL do navegador real.
Por exemplo, se definimos base to /vuejs a página de login será /vuejs/login.
Você pode configurar qualquer coisa para ser o base, aqui vamos usar /.

src/main.js

const router = new VueRouter({


mode: 'history',
base: '/',
routes
})

Isso remove o uso do ‘** # **’ nas URLs.


Vue Router 310

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

20.5 Rotas aninhadas


Rotas aninhadas, são rotas que vivem em outras rotas. Mapear rotas aninhadas para
componentes é uma necessidade comum, e também é muito simples com vue-router.
Para demonstrar isso, vamos adicionar uma página para exibir as histórias com 2
subpáginas.

• Uma para exibir todas as histórias (StoriesAll.vue)


• Uma para exibir histórias famosas (StoriesFamous.vue)

Criaremos os componentes acima mencionados de forma similar ao App.vue.


Comecemos por registrar as novas rotas. Tudo o que temos a fazer é usar a opção
children no Array de routes e adicionar um <router-view> aninhado, que conterá
os outros componentes, StoriesPage.vue.

src/main.js

import Vue from 'vue'


import App from './App'

import Hello from './components/Hello.vue'


import Login from './components/Login.vue'
import StoriesPage from './components/StoriesPage.vue'
import StoriesAll from './components/StoriesAll.vue'
import StoriesFamous from './components/StoriesFamous.vue'

import VueRouter from 'vue-router'

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>

20.6 Definindo uma Classe CSS Ativa


Não seria bom se destacarmos o link que direciona para a página ativa? O vue-router
é inteligente o suficiente para adicionar uma classe css ao link ativo.
Tudo o que temos a fazer é adicionar uma regra para o estilo em nosso css.

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>

Nós adicionamos “exato” ao primeiro link dentro do StoriesPage.vue também.


O interessante é que o link ativo está destacado na navegação secundária também.
Vue Router 315

Classe Ativa

Classe Ativa Personalizada


Você pode alterar o nome da classe ativa (router-link-active) para qualquer
link específico, usando a propriedade active-class ou blobalmente usando o
linkActiveClass no construtor do router.
Vue Router 316

Local custom active class

<router-link :to="{ name: 'hello'}" active-class="my-active-class" e\


xact>
Home
</router-link>

Global custom active class

const router = new VueRouter({


mode: 'history',
base: '/',
linkActiveClass: 'my-active-class',
routes
})

20.7 Objeto Route


Todas as informações de um router estarão acessíveis no objeto de contexto de rotas,
também chamado router object.
O route object será injetado em cada componente em uma app que estiver com o
router ativo e será terá acesso ao this.$route. Ele será atualizado sempre que uma
transição de rota for realizada.
Abaixo está uma lista com propriedades do objeto ** $ route **.

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.

20.8 Segmentos dinâmicos


Vue Router fornece a capacidade de formar caminhos usando segmentos dinâmicos.
Segmentos dinâmicos são segmentos com dois pontos (“:”). Eles são chamados
dinâmicos porque seu valor é mutável.

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.

Nesse path, o :id é um segmento dinâmico e combinará qualquer valor


fornecido, por exemplo /user/11/posts, /user/37/posts, etc.

Quando um caminho contendo um segmento dinâmico é combinado, os segmentos


dinâmicos estarão disponíveis dentro de $route.params.
No nosso exemplo, podemos usar um segmento dinâmico para acessar uma certa
história pelo seu id, para podermos criar um componente capaz de editá-lo.
Vue Router 318

src/main.js

1 const routes = [
2 // other routes
3 {
4 path: ':id/edit',
5 name: 'stories.edit',
6 component: StoriesEdit
7 }
8 ...
9 ]

Agora, precisamos de uma maneira de vincular StoriesAll.vue com StoriesE-


dit.vue. Vamos alterar o arquivo.

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>

Aqui queremos que <router-link> renderize um botão <button> ao invés de um


link <a>. Podemos usar a propriedade tag para especificar qual tag renderizar. A tag
renderizada irá escutar os eventos de click para a navegação.
Dentro de $route.params, o id da história escolhida está disponível quando chegar
mos ao nosso destino. Com isso em mãos, podemos escolher a história que o usuário
deseja editar e trazê-lo para ele.
É hora de mostrar o arquivo StoriesEdit.vue, onde a edição será realizada.
Vue Router 321

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>

Estamo usando o id da história, para escolher o desejado story do Array de stories


com o método JavaScript find6 .
A história escolhida está pronta para edição. Observe que o id da história é mostrado
na URL.
6
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
Vue Router 323

Editando a história selecionada

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
}

A alternativa é converter o id em número, usando o wrapper de objeto Number.

src/components/StoriesEdit.vue

export default {
...
mounted () {
this.id = Number(this.$route.params.id)
}
}

Se você verificar isso no navegador, descobrirá que a história aparece como se


clicássemos no botão de edição.
Apesar disso, existe uma solução muito melhor que nos ajuda a desacoplar o compo-
nente StoriesEdit do $route. Podemos relacionar o parâmetro id da configuração
da rota a propriedade prop do componente StoriesEdit.

src/main.js

const routes = [
...
{
path: ':id/edit',
props: (route) => ({ id: Number(route.params.id) }),
name: 'stories.edit',
component: StoriesEdit
},
...
]
Vue Router 325

Agora, quando stories/:id/edit é combinado, o router irá passar um valor numé-


rico de :id como uma propriedade do componente StoriesEdit. O que falta é definir
essa propriedade e remover this.$route.params.id. Então, nosso componente se
parecerá

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>.

Você pode ver os atributos do objeto $route, usando Vue Devtools.


Vue Router 326

Dentro de $route

20.9 Apelidos para o Router


Quando definimos novas rotas, geralmente tentamos torná-los claros e representa-
tivos. Por vezes, podemos acabar com caminhos longos ou complexos, que pode ser
difícil de manusear mais tarde.
Quando queremos ver as rotas das histórias famosas através do navegador, temos que
visitar '/stories/famous'. Podemos tornar isso mais curto ao definir um apelido
(alias) global para esta rota, onde a URL mais curta nos levaria ao mesmo lugar.
Vue Router 327

src/main.js

{
path: 'famous',
name: 'stories.famous',
// match '/famous' as if it is '/stories/famous'
alias: '/famous',
component: StoriesFamous
}

Usando esta configuração, podemos usar o alias em vez do path.


Vue Router 328

Usando alias

20.10 Navegando de Forma Programática


Em algum momento, queremos navegar para uma rota não através de links, mas
programaticamente.
Para navegar até uma rota, podemos usar router.push(path). O path pode ser uma
string ou um objeto.
Se for uma string, o caminho deve ser na forma de caminho simples, o que significa
que não pode conter segmentos dinâmicos. Por exemplo router.push('/stories/11/edit').
Vue Router 329

Se for um objeto, você pode passar todos os argumentos necessários.

Programmatic navigation

router.push({ path: '/stories/11/edit' })


router.push({ name: 'stories.edit', params: {id: '11'} })

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>

Atualizamos o método saveChanges. Quando chamado, ele exibe uma mensagem no


console e usando this.$router.push(), navega de volta para /stories.
Vue Router 330

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

1 <button @click="goBack">Go back</button>


2
3 methods: {
4 ...
5 goBack () {
6 this.$router.back()
7 },
8 ...
9 }

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.

• Aplicar automaticamente classes para transições e animações CSS


• Integrar bibliotecas de animação CSS ou JavaScript de terceiros, tais como
Animate.css9 , Velocity.js10 , etc
• Usar JavaScript para manipular diretamente o DOM durante os a transição

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

Se um nome para a transição for definido, todas as classes acima mencionadas


conterão o nome em vez de ‘v-*’. Por exemplo, fade-enter, fade-leave-to, etc.

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

você talvez), o uso de transições do Vue é bastante fácil de integrar bibliotecas de


animação CSS e JS de terceiros.
Neste exemplo, vou usar Animate.css12 .

Animate.css

De acordo com a documentação, para usar Animate.css você precisa:

1. Inclua a folha de estilo no <head> do documento html


12
https://daneden.github.io/animate.css/
Vue Router 335

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 .

Comece importando o css do CDNJS, no cabeçalho do nosso arquivo index.html.


Em vez de confiar no suporte name do componente transition, desta vez usaremos
uma classe personalizada para v-enter-active.

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".

20.12 Filtros em Transições


Vue Router fornece um mecanismo conveniente para filtrar transições. Para filtrar
uma transição, você pode usar router.beforeEach() que é acionado antes de cada
transição, e router.afterEach(), que é desencadeada após, contudo afterEach()
não pode afetar a navegação.
13
https://github.com/daneden/animate.css
Vue Router 336

router.beforeEach() pode ser útil em um cenário de autorização. Por exemplo, se


um usuário não tiver permissão para acessar uma página do seu aplicativo, ele deve
ser direcionado para a página de login.
Vamos ver como podemos conseguir isso em um pequeno exemplo.

src/main.js

1 // create a dummy user object


2 let User = {
3 isAdmin: false
4 }
5
6 router.beforeEach((to, from, next) => {
7 if (to.path !== '/login' && !User.isAdmin) {
8 // Se não estiver fazendo login e não um redirecionamento de a\
9 dministrador para entrar
10 next('/login')
11 } else {
12 // Se autorizado, proceda
13 next()
14 }
15 })

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.

Quando o usuário visita /category/:name/pokemons/new Ele deve ver um


formulário para adicionar um novo Pokémon juntamente com a lista dos
Pokémons da categoria.
15
https://github.com/hootlex/the-majesty-of-vuejs-2/blob/master/homework/Chapter19/pokedex.js
Vue Router 338

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

Você pode encontrar uma solução para este exercício aqui17

17
https://github.com/hootlex/the-majesty-of-vuejs-2/tree/master/homework/Chapter19

Vous aimerez peut-être aussi