Qual será a nova versão do Vuex?

Vuex é um gerente estadual para aplicativos Vue. Sua próxima versão é o Vuex 4, que está quase pronto para o lançamento oficial. Ele adicionará suporte para Vue 3, mas não trará nenhuma nova funcionalidade.



Embora o Vuex seja considerado uma ótima solução e muitos desenvolvedores o escolham como sua biblioteca de gerenciamento de estado principal, eles esperam obter mais recursos em versões futuras. Portanto, enquanto o Vuex 4 está se preparando para o lançamento, um de seus desenvolvedores, Kia King Ishii (parte da equipe principal), já está compartilhando planos para a próxima versão 5. Vale ressaltar que são apenas planos e algumas coisas podem mudar, porém, a direção principal já foi escolhida. Vamos falar sobre ele.



Com o advento do Vue 3 e a API de composição , os desenvolvedores começaram a criar alternativas simples. Por exemplo, o artigo “Você provavelmente não precisa da Vuex ” demonstra uma maneira simples, flexível e confiável de criar lojas com base na API de composição em conjunto com provide/inject



. Podemos presumir que essa e algumas outras alternativas são boas para pequenos aplicativos, mas como costuma acontecer, elas têm suas desvantagens: documentação, comunidade, convenção de nomenclatura, integração, ferramentas de desenvolvedor.







O último ponto é muito importante. Vue agora tem uma ótima extensão de navegadorpara auxiliar no desenvolvimento e depuração. Livrá-lo pode ser um grande desperdício, especialmente na construção de grandes aplicações. Felizmente, isso não acontecerá com o Vuex 5. Quanto às abordagens alternativas, elas funcionarão, mas não trarão tantos benefícios quanto a solução oficial. Portanto, vamos ver que tipo de vantagens eles nos prometem.



Criação de uma loja



Antes de fazer qualquer coisa com o lado, precisamos criá-lo. No Vuex 4, é assim:



import { createStore } from 'vuex'

export const counterStore = createStore({
  state: {
    count: 0
  },
  
  getters: {
    double (state) {
      return state.count * 2
    }
  },
  
  mutations: {
    increment (state) {
      state.count++
    }
  },
  
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

      
      





A loja também consiste em 4 partes: estado, onde os dados são armazenados; getters que fornecem estados computados; mutações necessárias para alterar o estado e as ações que são chamadas fora da loja para realizar operações nela. Normalmente, as ações não causam apenas mutação (como no exemplo), mas são usadas para realizar tarefas assíncronas (porque as mutações devem ser síncronas ) ou implementar alguma lógica mais complexa. Qual será a aparência do Vuex 5?



import { defineStore } from 'vuex'

export const counterStore = defineStore({
  name: 'counter',
  
  state() {
    return { count: 0 }
  },
  
  getters: {
    double () {
      return this.count * 2
    }
  },
  
  actions: {
    increment () {
      this.count++
    }
  }
})

      
      





A primeira coisa que mudou foi a mudança createStore



de nome para defineStore



. Um pouco mais tarde, ficará claro o porquê. Em seguida, havia um parâmetro name



para especificar o nome da loja. Antes disso, dividimos os lados em módulos, e os nomes dos módulos estavam na forma de objetos nomeados. Além disso, os módulos foram registrados no espaço global, por isso não eram autossuficientes e prontos para serem reutilizados. Como solução, um parâmetro teve que ser usado namespaced



para evitar que vários módulos respondessem ao mesmo tipo de mutações e ações. Acho que muitos já se depararam com isso, mas mesmo assim adicionarei um link para a documentação . Agora não temos módulos, cada loja é por padrão um armazenamento separado e independente.



Depois de especificar o nome, precisamos torná-lo uma state



função que retorna o estado inicial, não apenas o define. Isso é muito semelhante ao que parece data



nos componentes. As mudanças também afetaram os getters, em vez de state



usar uma função como parâmetro this



para acessar os dados. A mesma abordagem é aplicada a ações em this



vez de stat



como um parâmetro. Finalmente, e mais importante, as mutações são combinadas com jogos de ação. Kia comemorouque as mutações muitas vezes se tornam simples setters, tornando-as prolixas, aparentemente esse foi o motivo da exclusão. Ele não menciona se será possível fazer mudanças de estado fora da loja, por exemplo de componentes. Aqui, podemos apenas nos referir ao padrão Flux, que não recomenda fazer isso e incentiva uma abordagem de mudança de estado a partir de ações.



Adendo: Aqueles que usam a API de composição para criar componentes ficarão felizes em saber que existe uma maneira de criar uma loja de maneira semelhante.



import { ref, computed } from 'vue'
import { defineStore } from 'vuex'

export const counterStore = defineStore('counter', () => {
  const count = ref(0)

  const double = computed(() => count.value * 2)
  
  function increment () {
    count.value++
  }

  return { count, double, increment }  
})

      
      





No exemplo acima, passamos o nome da loja como o primeiro argumento defineStore



. O resto é a API de composição usual, e o resultado será exatamente o mesmo que no exemplo com a API clássica.



Inicialização da loja



Mudanças significativas nos aguardam aqui. Para descrever como será a inicialização da loja na 5ª versão, vamos ver como acontece na 4ª. Quando criamos uma loja via createStore



, nós a inicializamos imediatamente, para que possamos usá-la app.use



diretamente ou em .



import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

const app = createApp(App)

app.use(store)
app.mount('#app')

//        `this.$store`
//   `useStore()`   Composition API

import store from './store'

store.state.count // -> 0
store.commit('increment')
store.dispatch('increment')
store.getters.double // -> 4

      
      





Na 5ª versão, acessamos separadamente cada instância do Vuex, o que garante independência. Portanto, esse processo parece diferente:



import { createApp } from 'vue'
import { createVuex } from 'vuex'
import App from './App.vue'

const app = createApp(App)
const vuex = createVuex()

app.use(vuex)
app.mount('#app')

      
      





Todos os componentes agora têm a capacidade de acessar qualquer instância Vuex diretamente, em vez de acessar o espaço global. Dê uma olhada em um exemplo:



import { defineComponent } from 'vue'
import store from './store'

export default defineComponent({
  name: 'App',

  computed: {
    counter () {
      return this.$vuex.store(store)
    }
  }
})

      
      





A chamada $vuex.store



cria e inicializa o armazenamento (lembre-se de renomear createStore



). Agora, cada vez que se comunicar com este repositório $vuex.store



, a instância já criada será devolvida a você. No exemplo, this.counter



é o que podemos usar posteriormente no código. Você também pode inicializar o armazenamento via createVuex()



.



E, claro, uma opção para a API de composição, onde é $vuex.store



usada em seu lugar useStore



.



import { defineComponent } from 'vue'
import { useStore } from 'vuex'
import store from './store'

export default defineComponent({
  setup () {
    const counter = useStore(store)

    return { counter }
  }
})

      
      





A abordagem descrita acima (inicializar o armazenamento por meio de componentes) tem vantagens e desvantagens. Por um lado, é a separação do código e a capacidade de adicioná-lo apenas quando necessário. Por outro lado, adicionar uma dependência (agora você precisa importar a loja toda vez que planeja usá-la). Portanto, se você deseja usar DI, uma opção usando provide



:



import { createApp } from 'vue'
import { createVuex } from 'vuex'
import App from './App.vue'
import store from './store'

const app = createApp(App)
const vuex = createVuex()

app.use(vuex)
app.provide('name', store)
app.mount('#app')

      
      





E, em seguida, colocar a loja no componente (há um desejo de substituí-la name



por uma constante e já usá-la):



import { defineComponent } from 'vue'

export default defineComponent({
  name: 'App',
  inject: ['name']
})

// Composition API

import { defineComponent, inject } from 'vue'

export default defineComponent({
  setup () {
    const store = inject('name')

    return { store }
  }
})

      
      





Não há muito entusiasmo com esta solução, mas parece mais explícita e flexível do que a abordagem atual. O mesmo não pode ser dito sobre o uso posterior. Agora é assim:



store.state.count            // State
store.getters.double         // Getters
store.commit('increment')    // Mutations
store.dispatch('increment')  // Actions

      
      





Espera-se que a nova versão:



store.count        // State
store.double       // Getters
store.increment()  // Actions

      
      





Todas as entidades (estado, getters e ações) são diretamente acessíveis, tornando o trabalho com elas mais fácil e lógico. Ao mesmo tempo que eliminou a necessidade de mapState



, mapGetters



, mapActions



e mapMutations



, assim como a escrita computados adicionais propriedades.



Compartilhando



O último ponto a considerar é o compartilhamento. Lembramos que no Vuex 5 não temos mais módulos nomeados e cada lado é separado e independente. Isso torna possível importá-los quando necessário e usar os dados conforme necessário, assim como os componentes. Surge uma questão lógica: como usar várias lojas juntas? Na versão 4 ainda existe um namespace global e precisamos usar rootGetters



e rootState



referir-se a diferentes armazenamentos neste escopo (assim como na versão 3). A abordagem no Vuex 5 é diferente:



// store/greeter.js
import { defineStore } from 'vuex'

export default defineStore({
  name: 'greeter',
  state () {
    return { greeting: 'Hello' }
  }
})

// store/counter.js
import { defineStore } from 'vuex'
import greeterStore from './greeter'

export default defineStore({
  name: 'counter',

  use () {
    return { greeter: greeterStore }
  },
  
  state () {
    return { count: 0 }
  },
  
  getters: {
    greetingCount () {
      return `${this.greeter.greeting} ${this.count}'
    }
  }
})
      
      





Importamos a loja, registramos use



e acessamos. Tudo parece ainda mais fácil se você usar a API de composição:



// store/counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'vuex'
import greeterStore from './greeter'

export default defineStore('counter', ({use}) => {
  const greeter = use(greeterStore)
  const count = 0

  const greetingCount = computed(() => {
    return  `${greeter.greeting} ${this.count}`
  })

  return { count, greetingCount }
})

      
      





Só vale ressaltar que use



funciona exatamente da mesma forma vuex.store



e é responsável pela inicialização correta das lojas.



Suporte TypeScript



Com mudanças na API e menos abstrações, o suporte do TypeScript na versão 4 será muito melhor, mas ainda precisamos de muito trabalho manual. O lançamento da versão 5 possibilitará adicionar tipos onde for necessário e onde quisermos.



Conclusão



O Vuex 5 parece promissor e é exatamente o que muitos esperam (consertar bugs antigos, adicionar flexibilidade). Uma lista completa das principais discussões e visualizações da equipe pode ser encontrada no repositório de RFCs do Vue .



All Articles