Cosa sta arrivando su VueX?
Pubblicato: 2022-03-10Vuex è la soluzione per la gestione dello stato nelle applicazioni Vue. La prossima versione, Vuex 4, si sta facendo strada attraverso gli ultimi passaggi prima del rilascio ufficiale. Questa versione porterà la piena compatibilità con Vue 3, ma non aggiunge nuove funzionalità. Sebbene Vuex sia sempre stata una soluzione potente e la prima scelta per molti sviluppatori per la gestione dello stato in Vue, alcuni sviluppatori speravano di vedere risolti più problemi di flusso di lavoro. Tuttavia, anche se Vuex 4 sta appena uscendo dalla porta, Kia King Ishii (un membro del core team di Vue) sta parlando dei suoi piani per Vuex 5 e sono così entusiasta di quello che ho visto che ho dovuto condividerlo con te tutti. Nota che i piani di Vuex 5 non sono finalizzati, quindi alcune cose potrebbero cambiare prima del rilascio di Vuex 5, ma se finisce per lo più simile a quello che vedi in questo articolo, dovrebbe essere un grande miglioramento per l'esperienza degli sviluppatori.
Con l'avvento di Vue 3 e della sua API di composizione, le persone hanno cercato alternative semplici costruite a mano. Ad esempio, potresti non aver bisogno di Vuex dimostra un modello relativamente semplice, ma flessibile e robusto per l'utilizzo dell'API di composizione insieme a provide/inject
per creare archivi di stato condivisi. Come afferma Gabor nel suo articolo, tuttavia, questa (e altre alternative) dovrebbero essere utilizzate solo in applicazioni più piccole perché mancano di tutte quelle cose che non riguardano direttamente il codice: supporto della comunità, documentazione, convenzioni, buone integrazioni Nuxt e sviluppatore utensili.
Quest'ultimo è sempre stato uno dei problemi più grandi per me. L'estensione del browser Vue devtools è sempre stata uno strumento straordinario per il debug e lo sviluppo di app Vue e perdere l'ispettore Vuex con il "viaggio nel tempo" sarebbe una perdita piuttosto grande per il debug di applicazioni non banali.
Per fortuna, con Vuex 5 potremo avere la nostra torta e mangiarla anche noi. Funzionerà più come queste alternative API di composizione ma manterrà tutti i vantaggi dell'utilizzo di una libreria ufficiale di gestione dello stato. Ora diamo un'occhiata a cosa cambierà.
Definizione di un negozio
Prima di poter fare qualsiasi cosa con un negozio Vuex, dobbiamo definirne uno. In Vuex 4, la definizione di un negozio sarà simile a questa:
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') } } })
Ogni negozio ha quattro parti: lo state
memorizza i dati, i getters
forniscono lo stato calcolato, le mutations
vengono utilizzate per mutare lo stato e actions
sono i metodi che vengono chiamati dall'esterno del negozio per eseguire qualsiasi cosa relativa al negozio. Di solito, le azioni non commettono solo una mutazione come mostra questo esempio. Al contrario, vengono utilizzati per eseguire attività asincrone perché le mutazioni devono essere sincrone o semplicemente implementano funzionalità più complicate o multifase. Anche le azioni non possono mutare lo stato da sole; devono usare un mutatore. Allora, che aspetto ha 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++ } } })
Ci sono alcune modifiche da notare qui. Innanzitutto, invece di createStore
, utilizziamo defineStore
. Questa differenza è trascurabile, ma esiste per ragioni semantiche, di cui parleremo più avanti. Successivamente, dobbiamo fornire un name
per il negozio, di cui non avevamo bisogno prima. In passato, i moduli avevano il proprio nome, ma non erano forniti dal modulo stesso; erano solo il nome della proprietà a cui erano stati assegnati dal negozio principale che li ha aggiunti. Ora non ci sono moduli . Invece, ogni modulo sarà un negozio separato e avrà un nome. Questo nome è utilizzato dal registro Vuex, di cui parleremo più avanti.
Dopodiché, dobbiamo fare in modo che lo state
restituisca lo stato iniziale invece di impostarlo semplicemente allo stato iniziale. È simile all'opzione data
sui componenti. Scriviamo getters
molto simili a come abbiamo fatto in Vuex 4, ma invece di usare lo state
come parametro per ogni getter, puoi semplicemente usarlo per arrivare this
stato. Allo stesso modo, actions
non devono preoccuparsi del passaggio di un oggetto di context
: possono semplicemente usarlo per accedere a this
. Infine, non ci sono mutations
. Invece, le mutazioni sono combinate con actions
. Kia ha notato che troppo spesso le mutazioni sono diventate semplici setter, rendendole inutilmente dettagliate, quindi le hanno rimosse. Non ha menzionato se fosse "ok" mutare lo stato direttamente dall'esterno del negozio, ma siamo decisamente autorizzati e incoraggiati a mutare lo stato direttamente da un'azione e il modello Flux disapprova la mutazione diretta dello stato.
Nota : per coloro che preferiscono l'API di composizione rispetto all'API delle opzioni per la creazione di componenti, sarai felice di apprendere che esiste anche un modo per creare negozi in modo simile all'utilizzo dell'API di composizione.
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 } })
Come mostrato sopra, il nome viene passato come primo argomento per defineStore
. Il resto sembra proprio una funzione di composizione per i componenti. Ciò produrrà esattamente lo stesso risultato dell'esempio precedente che utilizzava l'API delle opzioni.
Ottenere l'istanza del negozio
In Vuex 4, le cose sono cambiate rispetto a Vuex 3, ma guarderò solo alla v4 per evitare che le cose sfuggano di mano. Nella v4, quando hai chiamato createStore
, l'hai già istanziata. Puoi quindi utilizzarlo nella tua app, tramite app.use
o direttamente:
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
Questa è una cosa che Vuex 5 rende un po' più complicata rispetto alla v4. Ogni app ora può ottenere un'istanza separata di Vuex, il che assicura che ogni app possa avere istanze separate degli stessi negozi senza condividere i dati tra di loro. Puoi condividere un'istanza di Vuex se desideri condividere istanze di negozi tra le app.
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')
Ora tutti i tuoi componenti hanno accesso all'istanza Vuex. Invece di fornire direttamente la definizione dei tuoi negozi, li importi nei componenti in cui desideri utilizzarli e utilizzi l'istanza Vuex per istanziarli e registrarli:
import { defineComponent } from 'vue' import store from './store' export default defineComponent({ name: 'App', computed: { counter () { return this.$vuex.store(store) } } })
Chiamando $vuex.store
, istanzia e registra il negozio nell'istanza Vuex. Da quel momento in poi, ogni volta che usi $vuex.store
su quel negozio, ti restituirà il negozio già istanziato invece di crearlo di nuovo. Puoi chiamare il metodo store
direttamente su un'istanza di Vuex creata da createVuex()
.
Ora il tuo negozio è accessibile su quel componente tramite this.counter
. Se stai utilizzando l'API di composizione per il tuo componente, puoi utilizzare useStore
invece di 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 } } })
Ci sono pro e contro nell'importare il negozio direttamente nel componente e nell'istanziarlo lì. Ti consente di dividere il codice e caricare pigramente il negozio solo dove è necessario, ma ora è una dipendenza diretta invece di essere iniettato da un genitore (per non parlare del fatto che devi importarlo ogni volta che vuoi usarlo). Se desideri utilizzare l'iniezione di dipendenza per fornirla in tutta l'app, soprattutto se sai che verrà utilizzata nella radice dell'app in cui la suddivisione del codice non sarà di aiuto, puoi semplicemente utilizzare 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 puoi semplicemente iniettarlo in qualsiasi componente in cui lo utilizzerai:
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 } } })
Non sono entusiasta di questa verbosità in più, ma è più esplicita e più flessibile, di cui sono un fan. Questo tipo di codice viene generalmente scritto una volta subito all'inizio del progetto e poi non ti dà più fastidio, anche se ora dovrai fornire ogni nuovo negozio o importarlo ogni volta che desideri usarlo, ma l'importazione o l'iniezione di moduli di codice è il modo in cui generalmente dobbiamo lavorare con qualsiasi altra cosa, quindi è solo far funzionare Vuex più in linea con il modo in cui le persone tendono già a lavorare.
Utilizzando un negozio
Oltre ad essere un fan della flessibilità e del nuovo modo di definire i negozi allo stesso modo di un componente utilizzando l'API di composizione, c'è un'altra cosa che mi rende più eccitato di tutto il resto: come vengono utilizzati i negozi. Ecco come appare usare un negozio in Vuex 4.
store.state.count // Access State store.getters.double // Access Getters store.commit('increment') // Mutate State store.dispatch('increment') // Run Actions
Lo State
, i getters
, le mutations
e actions
vengono gestiti in modi diversi tramite proprietà o metodi diversi. Questo ha il vantaggio dell'esplicitezza, che ho elogiato prima, ma questa esplicitezza non ci guadagna davvero nulla. E questa API diventa solo più difficile da usare quando si utilizzano moduli con spazio dei nomi. In confronto, Vuex 5 sembra funzionare esattamente come speri normalmente:
store.count // Access State store.double // Access Getters (transparent) store.increment() // Run actions // No Mutators
Tutto - lo stato, i getter e le azioni - è disponibile direttamente nella root del negozio, rendendolo semplice da usare con molta meno verbosità e praticamente elimina ogni necessità di usare mapState
, mapGetters
, mapActions
e mapMutations
per le opzioni API o per scrivere istruzioni extra computed
o semplici funzioni per l'API di composizione. Questo fa semplicemente sembrare un negozio Vuex e agire come un normale negozio che costruiresti tu stesso, ma ottiene tutti i vantaggi di plug-in, strumenti di debug, documentazione ufficiale, ecc.
Negozi di composizione
L'ultimo aspetto di Vuex 5 che esamineremo oggi è la componibilità. Vuex 5 non ha moduli con spazio dei nomi che sono tutti accessibili dal singolo negozio. Ciascuno di questi moduli verrebbe suddiviso in un negozio completamente separato. È abbastanza semplice da gestire per i componenti: importano semplicemente i negozi di cui hanno bisogno, li accendono e li usano. Ma cosa succede se un negozio vuole interagire con un altro negozio? Nella v4, lo spazio dei nomi complica il tutto, quindi è necessario utilizzare lo spazio dei nomi nelle chiamate di commit
e dispatch
, utilizzare rootGetters
e rootState
e quindi salire negli spazi dei nomi da cui si desidera accedere ai getter e allo stato. Ecco come funziona in 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 } } })
Con la v5, importiamo il negozio che desideriamo utilizzare, quindi lo registriamo con l' use
e ora è accessibile in tutto il negozio con qualsiasi nome di proprietà gli sia stato assegnato. Le cose sono ancora più semplici se stai utilizzando la variazione dell'API di composizione della definizione del negozio:
// 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 } })
Niente più moduli con namespace. Ogni negozio è separato e viene utilizzato separatamente. Puoi usare use
per rendere disponibile un negozio all'interno di un altro negozio per comporli. In entrambi gli esempi, use
è fondamentalmente lo stesso meccanismo di vuex.store
di prima e assicurano che istanziamo i negozi con l'istanza corretta di Vuex.
Supporto dattiloscritto
Per gli utenti di TypeScript, uno degli aspetti più importanti di Vuex 5 è che la semplificazione ha reso più semplice aggiungere tipi a tutto. I livelli di astrazione che le versioni precedenti di Vuex avevano reso quasi impossibile e in questo momento, con Vuex 4, hanno aumentato la nostra capacità di utilizzare i tipi, ma c'è ancora troppo lavoro manuale per ottenere una discreta quantità di supporto per i tipi, mentre nella v5 , puoi inserire i tuoi tipi in linea, proprio come speri e ti aspetteresti.
Conclusione
Vuex 5 sembra essere quasi esattamente quello che io - e probabilmente molti altri - speravo che fosse, e sento che non potrà arrivare abbastanza presto. Semplifica la maggior parte di Vuex, rimuovendo parte del sovraccarico mentale coinvolto e diventa più complicato o dettagliato solo dove aggiunge flessibilità. Lascia commenti qui sotto su cosa pensi di queste modifiche e quali modifiche potresti apportare invece o in aggiunta. Oppure vai direttamente alla fonte e aggiungi una RFC (Richiesta di commenti) all'elenco per vedere cosa ne pensa il team principale.