Follow Us

Como utilizar o Vuex com classes TypeScript

Como utilizar o Vuex com classes TypeScript

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.

Exemplo de utilização do Vuex: 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).

Image for post
Fluxograma de uma Store no Vuex

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.

Dark Mode

Referências

  1. Documentação do Vue.js sobre componentes utilizando classes
  2. Documentação do vuex-module-decorators
  3. Imagem do fluxograma do Vuex de Digital Fortress
  4. Imagem de destaque de Kaizen Codes

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *