Este guia provê uma estrutura uniforme para o seu código Vue.js, tornando-o mais:
- Fácil para desenvolvedores/times entender e encontrar coisas;
- Fácil para IDE's interpretarem e dar suporte ao código;
- Fácil de (re)usar ferramentas que você já usa;
- Fácil de fazer cache e gerar bundles separadamente;
Este guia foi inspirado pelo RiotJS Style Guide por De Voorhoede.
- Mantenha simples as expressões dos componentes
- Mantenha
props
primitivas - Pense bem nas
props
do seu componente this
já é o seu componente- Estrutura do componente
- Nome de eventos do componente
- Evite
this.$parent
- Use
this.$refs
com cuidado - Use o nome do componente como escopo para o
<style>
- Documente a API do seu componente
- Crie uma demonstração
- Lint os arquivos do seu componente
Sempre construa sua aplicação (app) em pequenos módulos que façam somente uma única coisa (e a faça bem feita).
Um módulo é uma parte da aplicação que é auto-contida. A biblioteca Vue.js foi especificamente desenvolvida para ajudar-lhe na criação de módulos view-logic.
Módulos pequenos são fáceis de aprender, compreender, manter, reusar e debugar, tanto para você quanto para outros desenvolvedores;
Cada componente Vue (como qualquer outro módulo) deve ser FIRST: Focused (single responsibility), Independent, Reusable, Small e Testable.
Se o seu componente faz muitas coisas ou está grande demais, quebre-o em componentes menores, cada um fazendo uma única coisa. Como uma regra If your component does too much or gets too big, split it up into smaller components which each do just one thing. Como um princípio básico, tente manter o componente com menos de 100 linhas de código. Assegure que o componente Vue funcione isoladamente, como por exemplo, se somente ele é necessário para uma demonstração dele mesmo.
Cada nome dos componentes devem ser:
- Significativo: não super especificado, não muito abstrato;
- Pequeno: 2 ou 3 palavras.
- Pronunciável: queremos poder falar sobre eles;
Os nomes também devem ser:
- Compatível com especificação de elemento personalizado: inclua um hífen, não use nomes reservados.
- Usar
app-
como namespace: se for muito genérico, caso contrário, use uma única palavra, para que ele seja facilmente reusável em outros projetos.
- O nome deve ser usado para falar sobre o componente, logo, precisa ser pequeno, significativo e pronunciável.
<!-- recomendado -->
<app-header></app-header>
<user-list></user-list>
<range-slider></range-slider>
<!-- evite -->
<btn-group></btn-group> <!-- pequeno, mas não pronunciável. Ao invés, use `button-group` -->
<ui-slider></ui-slider> <!-- todos os componentes são elementos ui, logo é insignificativo -->
<slider></slider> <!-- Não é compatível com especificação de elemento personalizado -->
As expressões em linha do Vue.js's são 100% Javascript. Isso torna-as extremamente poderosas, porém, potencialmente complexas. Sendo assim, você deve manter simples as expressões dos componentes.
- Expressões complexas em linha são difíceis de ler;
- Expressões em linhas não podem ser reusadas em outros lugares. Isso pode levar a duplicação de código;
- IDEs tipicamente não dão suporte a sintaxe de expressões, logo, sua IDE não pode fazer autocomple ou fazer validart.
Se elas tornam-se muito complexas ou difíceis de ler, mova-as para um método ou uma computed property
!
<!-- recomendado -->
<template>
<h1>
{{ `${year}-${month}` }}
</h1>
</template>
<script>
export default {
computed: {
month() {
return this.twoDigits((new Date()).getUTCMonth() + 1);
},
year() {
return (new Date()).getUTCFullYear();
}
},
methods: {
twoDigits(num) {
return ('0' + num).slice(-2);
}
},
};
</script>
<!-- evite -->
<template>
<h1>
{{ `${(new Date()).getUTCFullYear()}-${('0' + ((new Date()).getUTCMonth()+1)).slice(-2)}` }}
</h1>
</template>
Enquanto Vue.js suporta passar objetos complexos em JavaScript via esses atributos, você deveria tentar manter as props
mais primitivas possíveis. Tente usar somente primitivas JavaScript (strings, numbers, booleans) e functions. Evite objetos complexos.
- Usando um atributo para cada props separadamente, torna o componente com uma API mais expressiva;
- Usando somente primitivas e funções como props, a API do seu componente torna-se similar com a API nativa de elementos HTML(5);
- Usando um atributo para cada prop, outros desenvolvedores podem facilmente entender o que é passado para a instância do componente;
- Quando objetos complexos forem passados, não é aparente quais propriedades e métodos do objetos são realmente usados pelo componente. Isso torna a refatoração mais difícil e pode levar a má práticas.
Use um atributo por prop usando primitivas ou funções como valor:
<!-- recomendado -->
<range-slider
:values="[10, 20]"
min="0"
max="100"
step="5"
:on-slide="updateInputs"
:on-end="updateResults">
</range-slider>
<!-- evite -->
<range-slider :config="complexConfigObject"></range-slider>
Em Vue.js, props são a API dos componentes. Uma API robusta e previsível torna o componente fácil de usar por outros desenvolvedores.
As props dos componentes são passados via atributos HTML. Os valores desses atributos em Vue.js podem ser :attr="value"
ou v-bind:attr="value"
ou nunca ser usado. Você deveria pensar bem nas props
do seu componente para permitir casos variados.
Gastar um tempo pensando sobre as props que o seu componente terá, garantirá que o seu componente sempre funcione (defensive programming). Pense neles, especialmente quando considerando o uso por outros desenvolvedores mais tarde que podem ter expectativas diferentes, props que vocês ainda nem pensou que a props do seu componente teria.
- Use valores defaults para suas props;
- Use a opção
type
para validar valores para um tipo esperado.[1*]; - Cheque se a prop existe antes de usá-la.
<template>
<input type="range" v-model="value" :max="max" :min="min">
</template>
<script>
export default {
props: {
max: {
type: Number, // [1*] This will validate the 'max' prop to be a Number.
default() { return 10; },
},
min: {
type: Number,
default() { return 0; },
},
value: {
type: Number,
default() { return 4; },
},
},
};
</script>
Dentro do contexto de Vue.js, o this
está ligado diretamente a instância do componente. Sendo assim, quando for preciso referenciá-lo em contexto diferente,
garanta que o this
está disponível como o próprio componente.
Em outras palavras mais resumidas: NÃO codifique usando códigos como const self = this;
mais. Você está salvo para usar o this
diretamente dentro do componente Vue.
- Usando
this
diretamente, significa para todos os desenvolvedores que ele é o próprio componente, podendo ser usado em todo o seu componente, facilitando o desenvolvimento.
<script>
export default {
methods: {
hello() {
return 'hello';
},
printHello() {
console.log(this.hello());
},
},
};
</script>
<!-- evite -->
<script>
export default {
methods: {
hello() {
return 'hello';
},
printHello() {
const self = this; // Desnecessário
console.log(self.hello());
},
},
};
</script>
Faz com que seja fácil de entender e de seguir uma sequência de pensamentos. Veja o porque logo abaixo.
- Fazendo com que o componente exporte um objeto limpo e bem programado, torna o código mais fácil para desenvolvedores entender e ter um padrão a seguir;
- Ordenando alfabeticamente as props, data, computed, watches e methods faz com que o conteúdo do mesmo seja mais fácil de ser encontrado;
- Agrupando o objeto exportado do componente com visão do que os atributos fazem, torna a leitura mais fácil de ler e mais fácil de ler e mais organizada (name; extends; props, data and computed; components; watch and methods; lifecycle methods, etc.);
- Use o atributo
name
. Usando o vue devtools e este atributo, tornará seu componente mais fácil de desenvolver, debugar e testar; - Use metodologias de nomenclatura CSS, como BEM, ou rscss - details?;
- Use a ordem template-script-style na organização dos seus arquivos .vue, como é recomendado pelo Evan You, criador do Vue.js.
Estrutura do componente:
<template lang="html">
<div class="Ranger__Wrapper">
<!-- ... -->
</div>
</template>
<script>
export default {
// Não se esqueça desse atributo
name: 'RangeSlider',
// componha novos componentes
extends: {},
// propriedades/variáveis do componente
props: {
bar: {}, // Ordenado Alfabeticamente
foo: {},
fooBar: {},
},
// variáveis
data() {},
computed: {},
// quando um componente usa outros componentes
components: {},
// métodos
watch: {},
methods: {},
// Lifecycle do componente
beforeCreate() {},
mounted() {},
};
</script>
<style scoped>
.Ranger__Wrapper { /* ... */ }
</style>
Vue.js liga todas as funções e expressões estritamente ligadas ao ViewModel do componente. Cada um dos componentes deve seguir uma boa nomenclatura que evitará problemas durante o desenvolvimento. Continue lendo.
- Desenvolvedores estão livres para usar nome de eventos nativos, e isso pode causar problemas com o tempo;
- A liberdade para nomear eventos podem levar a incompatibilidade de templates do DOM;
- Eventos devem ser nomeados usando kebab-cased;
- Um único evento deve ser emitido para cada ação no seu componente que pode ser interessante como uma API, por eemplo: upload-success, upload-error ou até mesmo dropzone-upload-success, dropzone-upload-error (se você acha que ter um escopo como prefixo seja necessário);
- Eventos devem terminar em verbos e usar a nomes no infinitivo (exemplo, client-api-load) ou usar substantivos (exemplo, drive-upload-success) (source);
Vue.js suporta componentes aninhados, assim como acesso ao contexto do pai deste componente. Acessar escopo fora do componente em desenvolvimento viola a regra de FIRST de desenvolvimento baseado em componentes. Sendo assim, você deveria evitar this.$parent
.
- Um componente Vue, como qualquer outro componente, deve funcionar isoladamente. Se um componente precisa acessar seu pai, essa regra é quebrada;
- Se um componente precisa acessar o seu pai, ele não mais pode ser usado em outro contexto/aplicação.
- Passe os valores do pai para o componente via props;
- Passe os métodos definidos no componente pao para o componente filho usando callbacks em expressões;
- Emita eventos do componente filho e escute-os no componente pai.
Componente em Vue.js permite o acesso do contexto de outros componentes e elementos básicos HTML via o atributo ref
. Este atributo proverá uma forma de acesso via this.$refs
do contexto de um componente ou elemento DOM. Na maioria dos casos, a necessidade de acessar o contexto de outros componentes via this.$refs
poderia ser evitado. O uso constante dessa tática levará a uma má API, visto que ele poderia ser usado.
- Um componente deve funcionar isoladamente. Se um componente não dá suporte a todos os acessos necessários, provavelmente o componente foi mau implementado/desenvolvido;
- Props e eventos devem ser o suficiente para a grande maioria dos componentes.
- Cria uma boa API para o componente;
- Sempre foque na proposta que o componente está oferecendo;
- Nunca escreva código muito específico. Se voce precisa escrever um código específico dentro de um componente genérico, significa que a API ainda não está genérica o suficiente ou talvez você precise de um outro componente;
- Cheque todas as props para ver se falta alguma coisa. Se esse for o caso, melhore-o ou crie uma issue com a proposta de melhoria;
- Cheque todos os eventos. Na maioria dos casos, desenvolvedores esquecem a comunicação entre Pai-Filho (eventos);
- Props down, events up! Melhore o seu compoennte quando for necessário com uma boa API e tenha isolamento como um objetivo;
- Usando
this.$refs
em componentes, deveria ser usado somente quando props e eventos não podem mais ser usados ou quando faz mais sentido (veja o exemplo abaixo); - Usando
this.$refs
para acessar elementos DOM (ao invés de usarjQuery
,document.getElement*
,document.queryElement
) é tranquilo, quando o elemento não pode ser manipulado com data binding ou com uma diretiva.
<!-- ótimo, não precisa de ref -->
<range :max="max"
:min="min"
@current-value="currentValue"
:step="1"></range>
<!-- ótimo exemplo para se usar this.$refs -->
<modal ref="basicModal">
<h4>Basic Modal</h4>
<button class="primary" @click="$refs.basicModal.close()">Close</button>
</modal>
<button @click="$refs.basicModal.open()">Open modal</button>
<!-- Modal component -->
<template>
<div v-show="active">
<!-- ... -->
</div>
</template>
<script>
export default {
// ...
data() {
return {
active: false,
};
},
methods: {
open() {
this.active = true;
},
hide() {
this.active = false;
},
},
// ...
};
</script>
<!-- evite acessar alguma coisa que poderia ser emitida via um evento -->
<template>
<range :max="max"
:min="min"
ref="range"
:step="1"></range>
</template>
<script>
export default {
// ...
methods: {
getRangeCurrentValue() {
return this.$refs.range.currentValue;
},
},
// ...
};
</script>
Elementos dos componentes Vue.js são elementos customizados que podem ser usados como style scope root. Alternativamente, o nome do componente pode ser usado como o namespace de uma classe CSS.
- Scoping styles to a component element improves predictability as its prevents styles leaking outside the component element;
- Usar
<style scope>
num componente, melhora a previsibilidade e previne os estilos de vazar fora do componente; - Usando o mesmo nome para o diretório do módulo, o componente Vue.js e o estilo ficam mais fáceis de serem vistos e entendidos de onde eles surgiram.
Use o nome do componente como um prefixo para gerar um namespace. Basei-se em BEM e OOCSS e use o atribute scoped
no <style>
do seu componente. O uso de scope
dirá ao
compilador Vue para adicionar uma assinatura a todas as classes que estão no seu <style>
. Essa assinatura forçará o browser (se houver suporte) será aplicada a todos os estilos
que compõem o seu componente, levanto a um CSS que não sai do contexto do seu componente.
<style scoped>
/* recomendado */
.MyExample { }
.MyExample li { }
.MyExample__item { }
/* evite */
.My-Example { } /* não tem escopo a um componente nem a um nome de um módulo; não segue nenhuma metodologia */
</style>
A instância de um componente Vue.js é criada usando um elemento do componente que está dentro da sua aplicação. A instância é configurada através da configuração dos seus atributos. Para que o componente possa ser usado por outros desenvolvedores, esses atributos - a API do seu componente - devem ser documentados em um arquivo README.md
.
- Documentação provê aos desenvolvedores uma overview de alto nível, sem ter a necessidade de ler todo o código. Isso torna o componente mais acessível e fácil de usar;
- Uma API de um componente é o conjunto de atributos. O interesse dessa API vem para desenvolvedores que querem usar (e não desenvolver) o componente;
- Documentação formaliza a API e diz aos desenvolvedores quais as funcionalidades esperadas;
README.md
é o nome de arquivo padrão para a primeira leitura de uma documentação. O serviço de repositório de código (Github, Bitbucket, Gitlab etc) mostram o conteúdo desses componentes, diretamente quando o usuário navega pelos diretórios.
Adicione um arquivo README.md
ao diretório que contém o componente:
range-slider/
├── range-slider.vue
├── range-slider.less
└── README.md
Dentro do arquivo README, descreva a funcionaldiade e o uso desse módulo. Para um componente Vue.js é muito útil a descrição das props que ele suporta, pois essas compõem a API do componente. Exemplo:
The range slider lets the user to set a numeric range by dragging a handle on a slider rail for both the start and end value.
This module uses the noUiSlider for cross browser and touch support.
<range-slider>
supports the following custom component attributes:
attribute | type | description |
---|---|---|
min |
Number | number where range starts (lower limit). |
max |
Number | Number where range ends (upper limit). |
values |
Number[] optional | Array containing start and end value. E.g. values="[10, 20]" . Defaults to [opts.min, opts.max] . |
step |
Number optional | Number to increment / decrement values by. Defaults to 1. |
on-slide |
Function optional | Function called with (values, HANDLE) while a user drags the start (HANDLE == 0 ) or end (HANDLE == 1 ) handle. E.g. on-slide={ updateInputs } , with component.updateInputs = (values, HANDLE) => { const value = values[HANDLE]; } . |
on-end |
Function optional | Function called with (values, HANDLE) when user stops dragging a handle. |
For customising the slider appearance see the Styling section in the noUiSlider docs.
Adicione um index.html
que tenha demonstrações do componente com configurações diferentes, mostrando como o componente deve ser usado.
- Uma demontração do componente prova que o component funciona isoladamente;
- Uma demontração do componente dá aos desenvolvedores uma prévia antes de entrar na documentação ou no código;
- Demonstrações ilustram todas as possíveis configurações e variações que um componente pode ser usado.
Linters melhoram a consistência de código e ajudam a rastrear erros de sintaxe. Arquivos .vue podem usar Linters quando o plugin eslint-plugin-html
for adicionado ao projeto. Se você quiser, você pode iniciar um projeto com o ESLInt ativado por padrão usando a ferramenta vue-cli
;
- Arquivos que usam Lint garantem que todos os desenvolvedores usem o mesmo estilo de código;
- Arquivos que usam Lint ajudam no rastreio de erros antes que seja tarde demais.
Para fazer com que linters estraiam o script dos seus arquivos *.vue
, configure o seu linter para acessar variáveis globais vue
e props de componentes.
ESLint requer um plugin ESLint HTML para extrair o script de um componente.
Configure o ESLint em um arquivo .eslintrc
(para que IDEs possam interpretá-lo bem):
{
"extends": "eslint:recommended",
"plugins": ["html"],
"env": {
"browser": true
},
"globals": {
"opts": true,
"vue": true
}
}
Rode o ESLint
eslint src/**/*.vue
JSHint parsea o HTML (usando --extra-ext
) e extrai o script (usando --extract=auto
).
Configure o JSHint em um arquivo .jshintrc
(para que IDEs possam interpretá-lo bem):
{
"browser": true,
"predef": ["opts", "vue"]
}
Rode os JSHint
jshint --config módulos/.jshintrc --extra-ext=html --extract=auto módulos/
Nota: JSHint não aceita as expressões vue
, porém aceita as de html
.
Faça um Fork do projeto e depois um Pull Request do que você acha que deve ser interessante ter nesse guia ou crie uma Issue.
Pablo Henrique Penha Silva
- Github pablohpsilva
- Twitter @PabloHPSilva
- Medium @pablohpsilva