Padrão de projeto com TypeScript e guia de estilo para VueJS

Washington Pires
10 min readJan 29, 2019

--

Este artigo tem como objetivo apresentar uma visão geral de Vue, propor um padrão de projeto com TypeScript e incentivar o uso de um guia de estilo para o framework. Sugiro a leitura prévia do artigo “Padrões de projeto e boas práticas em JavaScript”.

Introduções

Vue

Vue.js é um framework JavaScript progressivo. Na prática, isso significa que o core da biblioteca é pequeno e focado apenas na camada de View (nos componentes de interface do usuário). Mas ele não está limitado a apenas isto, o framework foi projetado para ser facilmente integrado a outras bibliotecas ou projetos e possui suas próprias libs, dedicadas a fins específicos, como por exemplo, o Vue Router e Vuex.

Vue CLI via linha de comando

O Vue CLI é uma ferramenta que auxilia na criação de projetos em Vue. Você pode criar novos projetos via linha de comando ou via interface gráfica da própria ferramenta, que inclusive auxilia no gerenciamento de projetos já existentes. O print acima evidencia a extensibilidade do framework, que dá suporte por padrão a diversas bibliotecas populares e importantes dentro do ecossistema de JavaScript.

Vuex

O Vuex é uma biblioteca + padrão de gerenciamento de estado para aplicativos Vue.js. Com ele você pode criar uma camada que centraliza todo o gerenciamento de estado dos componentes de sua aplicação com um modelo de fluxo de dados unilateral. Com integração a extensão “Vue Devtools”, da acesso a ferramentas avançadas para debugging dos eventos e manipulações de dados.

Ciclo de vida do Vuex

TypeScript

O TypeScript é um superconjunto do ES6 que adiciona recursos que melhor suportam o uso da Programação Orientada a Objetos em JS. Ele também tem um transpilador que converte o nosso código de TypeScript (ou seja, tipos ES6+) em código JS ES5 ou ES3. Eu recomendo você ler a documentação oficial para saber para sobre todos os recursos que o TS oferece.

Padrão de projeto com TS

Introdução

Abaixo irei implementar uma arquitetura de projeto que utiliza Vue e Vuex com TypeScript. Antes de definir as tecnologias que serão usadas em um projeto, é importante avaliar o escopo e a equipe envolvida.

O padrão aqui proposto é recomendável para uma equipe composta por programadores juniores e plenos em um projeto cuja o escopo envolva uma grande complexidade ou quantidade de chamadas de APIs back-end e/ou grande volume de dados tratados no front-end.

Neste cenário hipotético, o Vuex seria de grande valia para centralizar a manipulação e acesso aos dados. E o TypeScript, por meio do suporte a tipagem estática, proporcionaria melhor controle dos tipos e evitará quebras de contrato.

Criação do projeto

Neste exemplo vamos utilizar o Vue CLI. Se você ainda não tiver ele na sua maquina, segue os comandos para instalação:

npm install -g @vue/cli

Agora a criação do projeto:

vue create project-vvts

Queremos personalizar a criação da estrutura do nosso projeto, então selecione a opção “Manually select features”:

Dica: Aperte a tecla “enter” para avançar.

Agora temos uma lista com vários recursos que podemos incluir no nosso projeto. Selecione as opções Babel, TypeScript, Vuex e Linter / Formatter conforme o print abaixo:

Dica: Para selecionar um item, navegue até ele usando as setas e aperte a tecla “espaço”.

Será necessário responder várias perguntas que estão relacionadas a configuração do nosso projeto. Segue abaixo as opções que eu selecionei:

Cada opção altera como o projeto será gerado.

Depois de tudo isso, o Vue CLI vai instalar as dependências necessárias e criar o nosso projeto. Isso pode levar bastante tempo. Se tudo der certo, você verá uma mensagem semelhante a esta:

Testando nossa aplicação

Pronto. O projeto foi criado, mas e agora?! Recomendo que você navegue pelas pastas e arquivos gerados a fim de conhecer o projeto. Neste momento não entrarei no detalhe de cada arquivo.

Projeto criado pelo Vue CLI.

Vamos testar? Entre na pasta do projeto e rode o seguinte comando:

npm run serve

O Vue CLI irá subir um servidor de desenvolvimento, aonde poderemos testar nossa aplicação:

Aplicação rodando!

Ao abrir o link “http://localhost:8080/” em seu navegador, você verá uma tela de “Welcome” do Vue. Esta é a tela padrão, vamos alterá-la depois.

Camada de dados — Vuex

Vamos desenvolver um componente simples de seleção de país. Nesse exemplo vamos usar o Vuex para criar nossa camada de dados e o Vuex Persist para persistência desta camada. Ao final deste passo a passo, nossa página ficará visualmente assim:

Campo de seleção de país com Vuex

Vamos começar criando um módulo no Vuex chamado “settings”, que irá armazenar uma lista de países (que iremos obter via API) e o país selecionado pelo usuário. Um módulo do Vuex é uma estrutura de dados que contém individualmente state, mutations, actions, getters. Eu explicarei o conceito de cada um conforme estivermos criando os arquivos.

Como estamos utilizando TypeScript, é necessário definirmos as interfaces para o nosso módulo. A interface ICountry é modelo de um país e a ISettingsState representa os dados que pretendemos manipular.

// src/vuex/modules/settings/types.tsexport interface ICountry {
name: String;
alpha2Code: string;
subregion: string;
};
export interface ISettingsState {
countries: ICountry[];
selectedCountry: ICountry;
error: boolean;
};

No Vuex, o state (estado) é o que define e armazena os dados do nosso módulo. Sempre definimos valores padrões para todas as propriedades necessárias do nosso módulo. No nosso exemplo, o state é o arquivo abaixo:

// src/vuex/modules/settings/state.tsexport default {
countries: [],
selectedCountry: {
name: 'Brazil',
alpha2Code: 'BR',
subregion: 'South America',
},
error: false,
};

Também precisamos criar explicitamente os getters. O arquivo abaixo é um conjunto de funções que acessam o state e retornam os valores das propriedades que queremos. Os getters podem possuir lógica e regra de negócio. O seu retorno pode ser o conjunto de várias propriedades do state ou conter formatações caso isto for necessário em várias partes da sua aplicação, por exemplo.

// src/vuex/modules/settings/getters.tsimport { ISettingsState, ICountry } from './types';export default {
getCountries(state: ISettingsState): ICountry[] {
return state.countries;
},
getSelectedCountry(state: ISettingsState): ICountry {
return state.selectedCountry;
},
getErrorStatus(state: ISettingsState): boolean {
return state.error;
},
};

Para alterar os dados no state, usamos as mutations. Estes devem ser mais específicos e de preferência alterar apenas uma propriedade cada um. A estrutura do arquivo é bem semelhante aos anteriores e utiliza-se as interfaces criadas lá no primeiro arquivo.

// src/vuex/modules/settings/mutations.tsimport { ISettingsState, ICountry } from './types';export default {
setCountries(state: ISettingsState, payload: ICountry[]) {
state.error = false;
state.countries = payload;
},
setSelectedCountry(state: ISettingsState, country: ICountry) {
state.error = false;
state.selectedCountry = country;
},
settingError(state: ISettingsState) {
state.error = true;
},
};

Caso você tenha se perguntado o porque as mutations devem ser mais específicas, a resposta é que o Vuex possui mais uma camada que são as actions. As actions são chamadas pelos componentes Vue e podem disparar uma ou mais mutations. Aqui você pode inserir lógica, regra de negócio e tratamento de dados antes de salvá-los. Ainda nesta mesma camada, você pode fazer a comunicação com suas APIs de back-end.

// src/vuex/modules/settings/actions.tsimport { ActionContext } from 'vuex';
import axios from 'axios';
import { ISettingsState, ICountry } from './types';
export default {
fetchCountries(store: ActionContext<ISettingsState, any>): void {
axios({
url: 'https://restcountries.eu/rest/v2/all',
}).then((response) => {
const counties = response && response.data;
store.commit('setCountries', counties);
}, (error) => {
console.log(error);
store.commit('settingError');
});
},
setSelectedCountry(store: ActionContext<ISettingsState, any>, country: ICountry): void {
store.commit('setSelectedCountry', country);
},
};

Você deve ter notado um erro relacionado a biblioteca “axios” que usamos no arquivo acima para fazer requisições HTTP. Você precisa instalar ela com o seguinte comando:

npm install axios --save

Agora precisamos criar um arquivo que irá importar e exportar em um único objeto todos os arquivos que acabamos de criar:

// src/vuex/modules/settings/index.tsimport state from './state';
import getters from './getters';
import actions from './actions';
import mutations from './mutations';
export default {
namespaced: true,
state,
getters,
actions,
mutations,
};

Antes de continuarmos, precisamos instalar mais uma dependência, que será usada para persistir os dados do nosso módulo. Esta persistência funciona da seguinte forma: A biblioteca irá espelhar os dados do Vuex no LocalStorage do navegador para poder recuperar os dados quando o usuário retornar a nossa aplicação. Comando de instalação:

npm install vuex-persist --save

Estamos perto de concluir nossa camada de dados.

Exclua o arquivo “src/store.ts”. Acesse o arquivo “src/main.ts” e altere o caminho de import do arquivo “./store” para:

import store from './vuex/store';

Crie o novo arquivo “store.ts” conforme o código e caminho abaixo:

// src/vuex/store.tsimport Vue from 'vue';
import Vuex from 'vuex';
import VuexPersist from 'vuex-persist';
import settings from './modules/settings';
const vuexLocalStorage = new VuexPersist({
key: 'vuex',
storage: window.localStorage,
});
Vue.use(Vuex);export default new Vuex.Store({
modules: {
settings,
},
plugins: [vuexLocalStorage.plugin],
});

Note que neste arquivo eu configurei o “VuexPersist” e instanciei o método “Vuex.Store”. Com isso, concluímos a implementação da nossa camada de dados.

Componente Country

Mas ainda não acabou. Precisamos acessar o módulo “settings” através do nosso componente. Vamos começar criando o nosso componente “Country”:

// src/components/Country.vue<template>
<div class="Country">
<select
v-if="countries && selectedCountry"
name="contries"
id="contries"
@change="defineCountry">
<option
v-for="(country, index) of countries"
:key="'country'+index"
:value="country.alpha2Code"
:selected="country.alpha2Code === selectedCountry.alpha2Code">
{{country.name}}
</option>
</select>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { State, Action, Getter } from 'vuex-class';
import { ICountry } from '@/vuex/modules/settings/types';
const namespace: string = 'settings';@Component
export default class Country extends Vue {
@Action('fetchCountries', { namespace }) fetchCountries: any;
@Action('setSelectedCountry', { namespace }) setCountry: any;@Getter('getCountries', { namespace }) countries!: ICountry[];@Getter('getSelectedCountry', { namespace }) selectedCountry!: ICountry;mounted() {
this.fetchCountries();
}
defineCountry(e: any) {
const country = this.countries.find(item => item.alpha2Code === e.target.value);
this.setCountry(country);
}
}
</script>

Antes que eu explique o que está acontecendo. Precisamos instalar uma dependência que adiciona os decorators do Vuex. Prometo que está é a última instalação (deste artigo, hehehe):

npm install vuex-class --save

O componente que acabamos de criar é bem semelhante a um componente VueJS comum, com exceção da sintaxe do TypeScript. A API do Vue continua sendo a mesma. Se você já estava familiarizado com VueJS, mas ficou perdido com a adição do TS, recomendo você começar a estudar por aqui.

Em resumo, nosso componente ele utiliza dos getters e actions para, respectivamente, acessar e alterar os dados do Vuex. Como você pode notar, isso acontece de forma bem simplificada e poderia ser utilizado por N componentes que compartilhariam o mesmo estado.

Falta uma última coisa antes de você testar a aplicação. Altere o componente “src/App.vue” para exibir o componente “Country.vue” ao invés do “HelloWorld.vue”. O seu componente deve ficar semelhante a este:

// src/App.vue<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<Country/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Country from './components/Country.vue';
@Component({
components: {
Country,
},
})
export default class App extends Vue {}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Se você se perdeu em algum momento, veja o código fonte deste projeto no GitHub. Como você pode ver no print abaixo, o código ficou divido por responsabilidade e estruturado de forma que de facilita a inclusão de novos módulos Vuex no projeto.

Estrutura de arquivos final

Guia de estilo

Por último, mas não menos importante: Use um guia de estilos para componentes VueJS. O Style Guide oficial para o framework é uma leitura obrigatória para quem está começando agora e quer evitar problemas comuns.

O guia é dividido em 4 categorias, sendo elas:

  1. Prioridade A: Essencial
  2. Prioridade B: Altamente Recomendado
  3. Prioridade C: Recomendado
  4. Prioridade D: Use com cautela

Toda via, eu tenho uma indicação de um guia de estilo não oficial, porém com alta adoção pela comunidade e vários motivos para você adotá-lo. O “ Style Guide para Componentes Vue.js” tem uma abordagem mais simples e didática em 14 tópicos diferentes para você e sua equipe ter um código uniforme, fácil de entender, debuggar e usar ferramentas adicionais.

Para facilitar, eis aa lista de todos os tópicos abordados:

  1. Desenvolvimento baseado em módulo
  2. Nomes de componentes Vue
  3. Mantenha simples as expressões dos componentes
  4. Mantenha props primitivas
  5. Pense bem nas props do seu componente
  6. this já é o seu componente
  7. Estrutura do componente
  8. Nome de eventos do componente
  9. Evite this.$parent
  10. Use this.$refs com cuidado
  11. Use o nome do componente como escopo para o <style>
  12. Documente a API do seu componente
  13. Crie uma demonstração
  14. Lint os arquivos do seu componente

Lembrando que essas dicas não são somente para novos projetos. Sempre é importante melhorar a qualidade do código e manutenibilidade dos projetos já em desenvolvimento ou até mesmo em produção.

Se você gostou deste artigo, contribua colaborando com dicas e sugestões nos comentários.

--

--