O que está chegando ao VueX?
Publicados: 2022-03-10Vuex é a solução para gerenciamento de estado em aplicações Vue. A próxima versão – Vuex 4 – está passando pelas etapas finais antes de ser lançada oficialmente. Esta versão trará total compatibilidade com o Vue 3, mas não adiciona novos recursos. Embora o Vuex sempre tenha sido uma solução poderosa e a primeira escolha para muitos desenvolvedores para gerenciamento de estado no Vue, alguns desenvolvedores esperavam ver mais problemas de fluxo de trabalho resolvidos. No entanto, mesmo que o Vuex 4 esteja apenas saindo, Kia King Ishii (um membro da equipe principal do Vue) está falando sobre seus planos para o Vuex 5, e estou tão empolgado com o que vi que tive que compartilhar com você todo. Observe que os planos do Vuex 5 não estão finalizados, portanto, algumas coisas podem mudar antes do lançamento do Vuex 5, mas se ele acabar muito parecido com o que você vê neste artigo, deve ser uma grande melhoria para a experiência do desenvolvedor.
Com o advento do Vue 3 e sua API de composição, as pessoas têm procurado alternativas simples feitas à mão. Por exemplo, Você pode não precisar do Vuex demonstra um padrão relativamente simples, mas flexível e robusto para usar a API de composição junto com provide/inject
para criar armazenamentos de estado compartilhados. Como Gabor afirma em seu artigo, porém, esta (e outras alternativas) só devem ser usadas em aplicativos menores porque eles não têm todas as coisas que não são diretamente sobre o código: suporte da comunidade, documentação, convenções, boas integrações do Nuxt e desenvolvedor Ferramentas.
Esse último sempre foi um dos maiores problemas para mim. A extensão do navegador Vue devtools sempre foi uma ferramenta incrível para depurar e desenvolver aplicativos Vue, e perder o inspetor Vuex com “viagem no tempo” seria uma grande perda para depurar aplicativos não triviais.
Felizmente, com o Vuex 5 poderemos ter nosso bolo e comê-lo também. Ele funcionará mais como essas alternativas de API de composição, mas manterá todos os benefícios de usar uma biblioteca oficial de gerenciamento de estado. Agora vamos dar uma olhada no que vai mudar.
Definindo uma loja
Antes de podermos fazer qualquer coisa com uma loja Vuex, precisamos definir uma. No Vuex 4, uma definição de loja ficará 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') } } })
Cada armazenamento tem quatro partes: o state
armazena os dados, os getters
fornecem o estado computado, as mutations
são usadas para alterar o estado e as actions
são os métodos chamados de fora do armazenamento para realizar qualquer coisa relacionada ao armazenamento. Normalmente, as ações não apenas confirmam uma mutação, como mostra este exemplo. Em vez disso, eles são usados para realizar tarefas assíncronas porque as mutações devem ser síncronas ou apenas implementam funcionalidades mais complicadas ou de várias etapas. As ações também não podem alterar o estado por conta própria; eles devem usar um mutante. Então, como é o 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++ } } })
Há algumas mudanças a serem observadas aqui. Primeiro, em vez de createStore
, usamos defineStore
. Essa diferença é insignificante, mas existe por razões semânticas, que abordaremos mais tarde. Em seguida, precisamos fornecer um name
para a loja, que não precisávamos antes. No passado, os módulos tinham seu próprio nome, mas não eram fornecidos pelo próprio módulo; eles eram apenas o nome da propriedade à qual foram atribuídos pela loja pai que os adicionou. Agora, não há módulos . Em vez disso, cada módulo será uma loja separada e terá um nome. Este nome é usado pelo registro Vuex, sobre o qual falaremos mais adiante.
Depois disso, precisamos tornar state
uma função que retorna o estado inicial em vez de apenas defini-lo para o estado inicial. Isso é semelhante à opção de data
nos componentes. Escrevemos getters
de maneira muito semelhante ao que fizemos no Vuex 4, mas em vez de usar o state
como parâmetro para cada getter, você pode usar this
para chegar ao estado. Da mesma forma, actions
não precisam se preocupar com a passagem de um objeto de context
: elas podem simplesmente usar this
para acessar tudo. Finalmente, não há mutations
. Em vez disso, as mutações são combinadas com actions
. Kia observou que, com muita frequência, as mutações se tornaram simples setters, tornando-as inutilmente detalhadas, então elas as removeram. Ele não mencionou se era “ok” mudar o estado diretamente de fora da loja, mas definitivamente somos permitidos e encorajados a mudar o estado diretamente de uma ação e o padrão Flux desaprova a mutação direta do estado.
Nota : Para aqueles que preferem a API de composição sobre a API de opções para criar componentes, você ficará feliz em saber que também existe uma maneira de criar lojas de maneira semelhante ao uso da API de composição.
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 } })
Como mostrado acima, o nome é passado como o primeiro argumento para defineStore
. O resto se parece com uma função de composição para componentes. Isso produzirá exatamente o mesmo resultado do exemplo anterior que usou a API de opções.
Obtendo a loja instanciada
No Vuex 4, as coisas mudaram em relação ao Vuex 3, mas vou apenas olhar para o v4 para evitar que as coisas saiam do controle. Na v4, quando você chamou createStore
, você já o instanciou. Você pode usá-lo em seu aplicativo, via app.use
ou diretamente:
import { createApp } from 'vue' import App from './App.vue' // Your root component import store from './store' // The store definition from earlier const app = createApp(App) app.use(store) app.mount('#app') // Now all your components can access it via `this.$store` // Or you can use in composition components with `useStore()` // ----------------------------------------------- // Or use directly... this is generally discouraged import store from './store' store.state.count // -> 0 store.commit('increment') store.dispatch('increment') store.getters.double // -> 4
Isso é uma coisa que o Vuex 5 torna um pouco mais complicado do que na v4. Cada aplicativo agora pode obter uma instância separada do Vuex, o que garante que cada aplicativo possa ter instâncias separadas das mesmas lojas sem compartilhar dados entre elas. Você pode compartilhar uma instância do Vuex se quiser compartilhar instâncias de lojas entre aplicativos.
import { createApp } from 'vue' import { createVuex } from 'vuex' import App from './App.vue' // Your root component const app = createApp(App) const vuex = createVuex() // create instance of Vuex app.use(vuex) // use the instance app.mount('#app')
Agora todos os seus componentes têm acesso à instância Vuex. Em vez de fornecer a definição de sua(s) loja(s) diretamente, você os importa para os componentes nos quais deseja usá-los e usa a instância Vuex para instanciar e registrá-los:
import { defineComponent } from 'vue' import store from './store' export default defineComponent({ name: 'App', computed: { counter () { return this.$vuex.store(store) } } })
Chamar $vuex.store
, instancia e registra a loja na instância Vuex. A partir desse ponto, sempre que você usar $vuex.store
nessa loja, ele retornará a loja já instanciada em vez de instanciar novamente. Você pode chamar o método store
diretamente em uma instância do Vuex criada por createVuex()
.
Agora sua loja pode ser acessada nesse componente via this.counter
. Se você estiver usando a API de composição para seu componente, poderá usar useStore
em vez de this.$vuex.store
:
import { defineComponent } from 'vue' import { useStore } from 'vuex' // import useStore import store from './store' export default defineComponent({ setup () { const counter = useStore(store) return { counter } } })
Existem prós e contras em importar a loja diretamente para o componente e instanciá-la lá. Ele permite que você divida o código e carregue lentamente a loja apenas onde for necessário, mas agora é uma dependência direta em vez de ser injetada por um pai (sem mencionar que você precisa importá-la toda vez que quiser usá-la). Se você quiser usar a injeção de dependência para fornecê-la em todo o aplicativo, especialmente se souber que será usado na raiz do aplicativo onde a divisão de código não ajudará, basta usar 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('store', store) // provide the store to all components app.mount('#app')
E você pode simplesmente injetá-lo em qualquer componente onde você vai usá-lo:
import { defineComponent } from 'vue' export default defineComponent({ name: 'App', inject: ['store'] }) // Or with Composition API import { defineComponent, inject } from 'vue' export default defineComponent({ setup () { const store = inject('store') return { store } } })
Não estou animado com essa verbosidade extra, mas é mais explícito e mais flexível, do qual sou fã. Esse tipo de código geralmente é escrito uma vez logo no início do projeto e não o incomoda novamente, embora agora você precise fornecer cada novo armazenamento ou importá-lo sempre que desejar usá-lo, mas importar ou injetar módulos de código é como geralmente temos que trabalhar com qualquer outra coisa, então está apenas fazendo o Vuex funcionar mais ao longo das linhas de como as pessoas já tendem a trabalhar.
Usando uma loja
Além de ser um fã da flexibilidade e da nova forma de definir as lojas da mesma forma que um componente usando a API de composição, há mais uma coisa que me deixa mais animado do que tudo: como as lojas são usadas. Veja como é usar uma loja no Vuex 4.
store.state.count // Access State store.getters.double // Access Getters store.commit('increment') // Mutate State store.dispatch('increment') // Run Actions
State
, getters
, mutations
e actions
são todos tratados de maneiras diferentes por meio de propriedades ou métodos diferentes. Isso tem a vantagem da explicitação, que elogiei anteriormente, mas essa explicitação não nos traz nada. E essa API só fica mais difícil de usar quando você está usando módulos com namespace. Em comparação, o Vuex 5 parece funcionar exatamente como você normalmente esperaria:
store.count // Access State store.double // Access Getters (transparent) store.increment() // Run actions // No Mutators
Tudo — o estado, os getters e as ações — está disponível diretamente na raiz da loja, simplificando o uso com muito menos verbosidade e praticamente eliminando a necessidade de usar mapState
, mapGetters
, mapActions
e mapMutations
para a API de opções ou para escrita instruções computed
extras ou funções simples para API de composição. Isso simplesmente faz com que uma loja Vuex pareça e aja exatamente como uma loja normal que você construiria, mas obtém todos os benefícios de plugins, ferramentas de depuração, documentação oficial etc.
Compondo Lojas
O aspecto final do Vuex 5 que veremos hoje é a composição. O Vuex 5 não possui módulos com namespace que podem ser acessados a partir de um único armazenamento. Cada um desses módulos seria dividido em uma loja completamente separada. Isso é simples o suficiente para lidar com componentes: eles apenas importam as lojas de que precisam e as ativam e as usam. Mas e se uma loja quiser interagir com outra? Na v4, o namespace envolve a coisa toda, então você precisa usar o namespace em suas chamadas de commit
e dispatch
, usar rootGetters
e rootState
e, em seguida, trabalhar nos namespaces dos quais deseja acessar getters e state. Veja como funciona no Vuex 5:
// 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' // Import the store you want to interact with export default defineStore({ name: 'counter', // Then `use` the store use () { return { greeter: greeterStore } }, state () { return { count: 0 } }, getters: { greetingCount () { return `${this.greeter.greeting} ${this.count}' // access it from this.greeter } } })
Com a v5, importamos a loja que desejamos usar, depois a registramos com use
e agora está acessível em toda a loja em qualquer nome de propriedade que você deu. As coisas são ainda mais simples se você estiver usando a variação da API de composição da definição da loja:
// store/counter.js import { ref, computed } from 'vue' import { defineStore } from 'vuex' import greeterStore from './greeter' // Import the store you want to interact with export default defineStore('counter', ({use}) => { // `use` is passed in to function const greeter = use(greeterStore) // use `use` and now you have full access const count = 0 const greetingCount = computed(() => { return `${greeter.greeting} ${this.count}` // access it like any other variable }) return { count, greetingCount } })
Não há mais módulos com namespace. Cada loja é separada e é usada separadamente. Você pode usar use
para disponibilizar uma loja dentro de outra loja para compô-las. Em ambos os exemplos, o use
é basicamente o mesmo mecanismo do vuex.store
anterior e eles garantem que instanciamos as lojas com a instância correta do Vuex.
Suporte TypeScript
Para usuários do TypeScript, um dos maiores aspectos do Vuex 5 é que a simplificação tornou mais simples adicionar tipos a tudo. As camadas de abstração que as versões mais antigas do Vuex tornaram quase impossível e agora, com o Vuex 4, elas aumentaram nossa capacidade de usar tipos, mas ainda há muito trabalho manual para obter uma quantidade decente de suporte a tipos, enquanto na v5 , você pode colocar seus tipos em linha, exatamente como você espera e espera.
Conclusão
O Vuex 5 parece ser quase exatamente o que eu – e provavelmente muitos outros – esperava que fosse, e sinto que não pode chegar em breve. Ele simplifica a maior parte do Vuex, removendo parte da sobrecarga mental envolvida, e só fica mais complicado ou detalhado onde adiciona flexibilidade. Deixe comentários abaixo sobre o que você acha dessas mudanças e quais mudanças você pode fazer em vez ou em adição. Ou vá direto para a fonte e adicione um RFC (Request for Comments) à lista para ver o que a equipe principal pensa.