Sempre que aprendo um novo framework, gosto de utilizar uma abordagem orientada a objetos. Foi dessa maneira que aprendi quando comecei a programar e além de estar acostumado, acredito que o código fica muito mais organizado e reutilizável.
No Vue.js não foi diferente, quando vi a notação dos componentes utilizando JSON já não gostei muito e logo fui procurar saber como criar utilizando classes do TypeScript. Por sorte, quando aprendi já existia bastante material na internet e na própria documentação oficial do Vue.js 2.
Porém, na hora de utilizar as stores do Vuex não foi tão imediato. Depois de uma pesquisa achei esse pacote que fazia exatamente o que eu queria. Apesar de não ser um pacote oficial do Vue.js ele me passou bastante confiança com 1.2k estrelas no github e 135 forks na data que escrevi este artigo.
Ele se chama vuex-module-decorators e com ele podemos definir uma store do Vuex, que até então só poderia ser definida assim…
export default new Vuex.Store({
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... }
})
… da maneira abaixo, utilizando classes e decoradores:
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
@Module
export default class MinhaStore extends VuexModule {
uma_variavel = 0
@Mutation
um_metodo( ... ) { ... }
@Action({ commit: ... })
outro_metodo( ... ) { ... }
}
export default new Vuex.Store({
modules: { MinhaStore }
})
Durante este artigo, vamos ver como funciona cada decorador e outros detalhes de sintaxe do pacote.
Instalação
Primeiramente, precisamos instalar o pacote vuex-module-decorators:
$ npm install vuex-module-decorators
Dependendo de como você criou sua aplicação, isso já será o suficiente. Caso não tenha escolhido as opções de utilizar TypeScript e Babel no momento da criação do projeto, você terá que seguir uns passos adicionais, mas são bem simples.
Utilização
Para exemplificar a utilização do Vuex com classes vamos criar uma aplicação que incrementa e decrementa um contador.
Criando a Store
Assim como no prórpio Vuex, vamos dividir a nossa store em 4 partes: ações (ou actions), mutações (ou mutations), estados (ou states) e getters.
Apenas para relembrar, o Vuex segue um fluxo bem específico em que o seus componentes deveriam realizar chamadas apenas a ações, e essas por sua vez a mutações (o único local em que deveriam ser realizadas mudanças no estado da aplicação).
States
Para comparação, vamos ver como seria o estado utilizando a notação tradicional:
export default new Vuex.Store({
state: {
contador: 0
}
})
Na notação utilizando classes, primeiro vamos importar os módulos e criar a classe ContadorStore
, que será um dos módulos da Store do Vuex. Precisamos do argumento name
no decorador para que o vuex-module-decorators possa criar os atributos e métodos necessários para utilização da store nos componentes.
Para definir um estado, basta definir uma variável na classe, como abaixo.
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
@Module({ name: 'ContadorStore' })
export class ContadorStore extends VuexModule {
contador = 0
}
export default new Vuex.Store({
modules: {
ContadorStore
}
})
Para não sobrecarregar a página com códigos, nos próximos passos vamos mostrar apenas a classe
ContadorStore
, sem os códigos auxiliares.
Getters
Na notação tradicional temos:
export default new Vuex.Store({
state: {
contador: 0
},
getters: {
contador: (state) => state.contador
}
})
Repare que podemos utilizar o mesmo nome tanto para o getter quanto para a variável que controla o estado. Na notação com classes, o código abaixo produz um erro do tipo Duplicate identifier
.
@Module({ name: 'ContadorStore' })
export class ContadorStore extends VuexModule {
contador = 0
get contador() {
return this.contador
}
}
Para resolver esse, e todos os próximos conflitos de nomes, vamos apenas utilizar o prefixo _
como se fossem variáveis privadas ou protegidas (manias de quem é de Python) para todas as variáveis e métodos que não devem ser chamadas diretamente pelos componentes.
@Module({ name: 'ContadorStore' })
export class ContadorStore extends VuexModule {
_contador = 0
get contador() {
return this._contador
}
}
Mutations
Na notação tradicional temos:
export default new Vuex.Store({
state: {
contador: 0
},
getters: {
contador: (state) => state.contador
},
mutations: {
incrementar(state, delta: number) {
state.contador += Number(delta)
},
},
})
Na notação com classes, basta criarmos um método utilizando o decorador @Mutation
, e substituir state
por this
, ou seja, quem controla o nosso estado agora é a própria classe.
@Module({ name: 'ContadorStore' })
export class ContadorStore extends VuexModule {
_contador = 0
get contador() {
return this._contador
}
@Mutation
_incrementar(delta: number) {
this._contador += Number(delta)
}
}
Note que não precisamos de uma mutação para decrementar nosso contador pois para isso basta utilizar um delta negativo, como veremos nas ações.
Actions
Finalmente, para as ações, na notação tradicional temos:
export default new Vuex.Store({
state: {
contador: 0
},
getters: {
contador: (state) => state.contador
},
mutations: {
incrementar(state, delta: number) {
state.contador += Number(delta)
},
},
actions: {
incrementar(context, delta: number) {
context.commit('incrementar', delta)
},
decrementar(context, delta: number) {
context.commit('incrementar', -delta)
},
}
})
Vamos novamente criar métodos com um decorador mas temos duas maneiras de chamar o commit das mutations:
- utilizando o próprio decorador
@Action({commit: '...'})
em que o valor retornado no método é passado como argumento para a mutation; ou - chamando a mutação explicitamente com
this.context.commit('...', valor)
Vamos utilizar uma em cada ação de nosso exemplo:
@Module({ name: 'ContadorStore' })
export class ContadorStore extends VuexModule {
_contador = 0
get contador() {
return this._contador
}
@Mutation
_incrementar(delta: number) {
this._contador += Number(delta)
}
@Action({commit: '_incrementar'})
incrementar(delta: number) {
return delta
}
@Action
decrementar(delta: number) {
this.context.commit('_incrementar', -delta)
}
}
Agora sim nosso componente App.vue pode executar o dispatch nas actions incrementar
e decrementar
, pegando o valor de delta do input.
Utilizando nos Componentes
Para a aplicação, precisamos de um template que mostre o contador, um input e dois botões. Na classe TypeScript, temos a notação tradicional utilizando this.$store
e acessando os eventos da store. Mas se usamos classes para criar a store, nada mais intuitivo do que querer utilizar também nos componentes.
Com a notação tradicional, temos:
<template>
<div id="app">
contador: {{ contador }}<hr>
delta: <input type="text" v-model="delta"><br>
<button @click="incrementar">+</button>
<button @click="decrementar">-</button>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class App extends Vue {
delta = 0
get contador() {
return this.$store.getters.contador
}
incrementar() {
this.$store.dispatch('incrementar', this.delta)
}
decrementar() {
this.$store.dispatch('decrementar', this.delta)
}
}
</script>
Agora, mostrando apenas o script do componente, podemos reescrever a utilização da store com a função getModule
.
import { Component, Vue } from 'vue-property-decorator';
import { getModule } from 'vuex-module-decorators';
import { ContadorStore } from './store'
@Component
export default class App extends Vue {
contadorStore = getModule(ContadorStore, this.$store)
delta = 0
get contador() {
return this.contadorStore.contador
}
incrementar() {
this.contadorStore.incrementar(this.delta)
}
decrementar() {
this.contadorStore.decrementar(this.delta)
}
}
Essa abordagem torna muito simples a utilização de atributos e métodos de uma classe TypeScript nas stores do Vuex facilitando muito o desempenho e velocidade com que escrevemos o nosso código.
Código
O código completo deste artigo pode ser acessado no repositório do github.
class-based-vuex (this link opens in a new window) by matheusvanzan (this link opens in a new window)
Exemplo de utilização do pacote vuex-module-decorators para o blog umcodigo.com
Referências
- Documentação do Vue.js sobre componentes utilizando classes
- Documentação do vuex-module-decorators
- Imagem do fluxograma do Vuex de Digital Fortress
- Imagem de destaque de Kaizen Codes
Olavo Rocha Neto
17 julho 2021 at 14:24Excelente Artigo, era bem o que eu estava precisando.