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 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 exemplo: 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 pai 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 componente 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.
- Usar
<style scope>
num componente, melhora a previsibilidade e previne os estilos de vazar para 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 demonstração do componente prova que o componente funciona isoladamente;
- Uma demonstraçã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 extraiam 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