Co czeka na VueX?

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Vuex to podstawowa biblioteka zarządzania stanami dla aplikacji Vue, a główny zespół Vue ma duże plany, aby uczynić ją lepszą niż kiedykolwiek. Oto podgląd tego, gdzie chcą to zrobić.

Vuex to rozwiązanie do zarządzania stanem w aplikacjach Vue. Następna wersja — Vuex 4 — przechodzi przez ostatnie kroki przed oficjalną premierą. Ta wersja zapewni pełną zgodność z Vue 3, ale nie dodaje nowych funkcji. Chociaż Vuex zawsze był potężnym rozwiązaniem i pierwszym wyborem dla wielu programistów do zarządzania stanem w Vue, niektórzy programiści mieli nadzieję, że zostaną rozwiązane więcej problemów z przepływem pracy. Jednak nawet gdy Vuex 4 właśnie wychodzi, Kia King Ishii (członek głównego zespołu Vue) opowiada o swoich planach dotyczących Vuex 5 i jestem tak podekscytowany tym, co zobaczyłem, że musiałem się tym z wami podzielić wszystko. Pamiętaj, że plany Vuex 5 nie są sfinalizowane, więc niektóre rzeczy mogą się zmienić przed wydaniem Vuex 5, ale jeśli skończy się to w większości podobnie do tego, co widzisz w tym artykule, powinno to być dużym ulepszeniem wrażenia programisty.

Wraz z pojawieniem się Vue 3 i jego interfejsu API do tworzenia kompozycji, ludzie szukali ręcznie tworzonych prostych alternatyw. Na przykład You May Not Need Vuex demonstruje stosunkowo prosty, ale elastyczny i niezawodny wzorzec do korzystania z interfejsu API kompozycji wraz z provide/inject do tworzenia wspólnych magazynów stanu. Jak stwierdza Gabor w swoim artykule, ta (i inne alternatywy) powinny być używane tylko w mniejszych aplikacjach, ponieważ brakuje w nich wszystkich tych rzeczy, które nie są bezpośrednio związane z kodem: wsparcia społeczności, dokumentacji, konwencji, dobrych integracji Nuxt i programisty narzędzia.

Ten ostatni zawsze był dla mnie jednym z największych problemów. Rozszerzenie przeglądarki Vue devtools zawsze było niesamowitym narzędziem do debugowania i tworzenia aplikacji Vue, a utrata inspektora Vuex z „podróżami w czasie” byłaby dość dużą stratą w przypadku debugowania wszelkich nietrywialnych aplikacji.

Debugowanie Vuex za pomocą Vue Devtools
Debugowanie Vuex za pomocą narzędzi Vue Devtools. (duży podgląd)

Na szczęście dzięki Vuex 5 będziemy mogli mieć nasze ciastko i je zjeść. Będzie działać bardziej jak te alternatywy interfejsu API kompozycji, ale zachowa wszystkie zalety korzystania z oficjalnej biblioteki zarządzania stanem. Przyjrzyjmy się teraz, co się zmieni.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Definiowanie sklepu

Zanim będziemy mogli zrobić cokolwiek ze sklepem Vuex, musimy go zdefiniować. W Vuex 4 definicja sklepu będzie wyglądać tak:

 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') } } })

Każdy sklep składa się z czterech części: state przechowuje dane, getters podają stan obliczony, mutations służą do mutowania stanu, a actions to metody wywoływane spoza sklepu w celu wykonania wszystkiego, co jest związane ze sklepem. Zazwyczaj akcje nie tylko popełnią mutację, jak pokazuje ten przykład. Zamiast tego są używane do wykonywania zadań asynchronicznych, ponieważ mutacje muszą być synchroniczne lub po prostu implementują bardziej skomplikowaną lub wieloetapową funkcjonalność. Działania również nie mogą same zmutować państwa; muszą używać mutatora. Jak więc wygląda 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++ } } })

Należy tutaj odnotować kilka zmian. Po pierwsze, zamiast createStore używamy defineStore . Ta różnica jest znikoma, ale wynika to z przyczyn semantycznych, które omówimy później. Następnie musimy podać name sklepu, której wcześniej nie potrzebowaliśmy. W przeszłości moduły miały swoje własne nazwy, ale nie były dostarczane przez sam moduł; były tylko nazwą właściwości, do której zostały przypisane przez sklep nadrzędny, który je dodał. Teraz nie ma modułów . Zamiast tego każdy moduł będzie osobnym sklepem i będzie miał nazwę. Ta nazwa jest używana przez rejestr Vuex, o czym porozmawiamy później.

Następnie musimy uczynić state funkcją, która zwraca stan początkowy, zamiast po prostu ustawiać go w stanie początkowym. Jest to podobne do opcji data na komponentach. Piszemy getters bardzo podobne do tego, co zrobiliśmy w Vuex 4, ale zamiast używać state jako parametru dla każdego gettera, możesz po prostu użyć this , aby dostać się do stanu. W ten sam sposób actions nie muszą się martwić o przekazanie obiektu context : mogą po prostu użyć this , aby uzyskać dostęp do wszystkiego. Wreszcie nie ma mutations . Zamiast tego mutacje są połączone z actions . Kia zauważyła, że ​​zbyt często mutacje po prostu stały się prostymi seterami, czyniąc je bezsensownie gadatliwymi, więc je usunęli. Nie wspomniał, czy mutowanie stanu bezpośrednio spoza sklepu było „w porządku”, ale zdecydowanie wolno nam i zachęcamy do mutowania stanu bezpośrednio z akcji, a wzorzec Flux marszczy brwi na bezpośrednią mutację stanu.

Uwaga : dla tych, którzy wolą interfejs API kompozycji niż interfejs API opcji do tworzenia komponentów, z przyjemnością dowiesz się, że istnieje również sposób na tworzenie sklepów w sposób podobny do korzystania z interfejsu API kompozycji.

 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 } })

Jak pokazano powyżej, nazwa jest przekazywana jako pierwszy argument dla defineStore . Reszta wygląda jak funkcja składu komponentów. Da to dokładnie ten sam wynik, co w poprzednim przykładzie, w którym wykorzystano interfejs API opcji.

Uzyskiwanie instancji sklepu

W Vuex 4 rzeczy się zmieniły w porównaniu z Vuex 3, ale przyjrzę się tylko wersji 4, aby sprawy nie wymknęły się spod kontroli. W wersji 4, kiedy createStore , już utworzyłeś jego instancję. Następnie możesz po prostu użyć go w swojej aplikacji, za pośrednictwem app.use lub bezpośrednio:

 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

To jest jedna rzecz, którą Vuex 5 nieco bardziej komplikuje niż w v4. Każda aplikacja może teraz otrzymać oddzielną instancję Vuex, co zapewnia, że ​​każda aplikacja może mieć osobne instancje tych samych sklepów bez udostępniania między nimi danych. Możesz udostępnić instancję Vuex, jeśli chcesz udostępniać instancje sklepów między aplikacjami.

 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')

Teraz wszystkie twoje komponenty mają dostęp do instancji Vuex. Zamiast bezpośrednio podawać definicję sklepu (sklepów), importujesz je do komponentów, w których chcesz ich używać, i używasz instancji Vuex do ich tworzenia i rejestrowania:

 import { defineComponent } from 'vue' import store from './store' export default defineComponent({ name: 'App', computed: { counter () { return this.$vuex.store(store) } } })

Wywołanie $vuex.store , tworzy instancję i rejestruje sklep w instancji Vuex. Od tego momentu za każdym razem, gdy użyjesz $vuex.store w tym sklepie, zwróci ci on już utworzony sklep zamiast tworzyć go ponownie. Możesz wywołać metodę store bezpośrednio na instancji Vuex utworzonej przez createVuex() .

Teraz Twój sklep jest dostępny na tym komponencie za pośrednictwem this.counter . Jeśli używasz interfejsu Composition API dla swojego komponentu, możesz użyć useStore zamiast 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 } } })

Importowanie sklepu bezpośrednio do komponentu i tworzenie tam jego instancji ma swoje plusy i minusy. Pozwala na dzielenie kodu i leniwe ładowanie sklepu tylko tam, gdzie jest to potrzebne, ale teraz jest to bezpośrednia zależność zamiast wstrzykiwania przez rodzica (nie wspominając o tym, że musisz go importować za każdym razem, gdy chcesz go użyć). Jeśli chcesz użyć wstrzykiwania zależności, aby udostępnić go w całej aplikacji, zwłaszcza jeśli wiesz, że będzie on używany w katalogu głównym aplikacji, gdzie dzielenie kodu nie pomoże, możesz po prostu użyć 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')

Możesz go po prostu wstrzyknąć do dowolnego komponentu, w którym zamierzasz go użyć:

 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 } } })

Nie jestem podekscytowany tą dodatkową gadatliwością, ale jest bardziej jednoznaczna i bardziej elastyczna, co jestem fanem. Ten rodzaj kodu jest zazwyczaj pisany raz od razu na początku projektu, a potem nie przeszkadza ci ponownie, chociaż teraz będziesz musiał podać każdy nowy sklep lub zaimportować go za każdym razem, gdy chcesz go użyć, ale importowanie lub wstrzykiwanie modułów kodu to sposób, w jaki zwykle musimy pracować z czymkolwiek innym, więc po prostu sprawia, że ​​Vuex działa bardziej zgodnie z tym, jak ludzie już pracują.

Korzystanie ze sklepu

Oprócz tego, że jestem fanem elastyczności i nowego sposobu definiowania sklepów w taki sam sposób, jak komponent używający API kompozycji, jest jeszcze jedna rzecz, która mnie bardziej ekscytuje niż wszystko inne: jak używane są sklepy. Oto, jak wygląda korzystanie ze sklepu w 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 i actions są obsługiwane na różne sposoby za pomocą różnych właściwości lub metod. Ma to zaletę jednoznaczności, którą chwaliłem wcześniej, ale ta jednoznaczność tak naprawdę nic nam nie zyskuje. A ten interfejs API staje się trudniejszy w użyciu, gdy używasz modułów z przestrzenią nazw. Dla porównania, Vuex 5 wygląda dokładnie tak, jak byś miał nadzieję:

 store.count // Access State store.double // Access Getters (transparent) store.increment() // Run actions // No Mutators

Wszystko — stan, gettery i akcje — jest dostępne bezpośrednio w katalogu głównym sklepu, dzięki czemu jest proste w użyciu z dużo mniejszą szczegółowością i praktycznie eliminuje potrzebę korzystania z mapState , mapGetters , mapActions i mapMutations dla interfejsu API opcji lub do pisania dodatkowe instrukcje computed lub proste funkcje dla interfejsu API kompozycji. To po prostu sprawia, że ​​sklep Vuex wygląda i działa jak normalny sklep, który sam zbudowałbyś, ale ma wszystkie zalety wtyczek, narzędzi do debugowania, oficjalnej dokumentacji itp.

Komponowanie sklepów

Ostatnim aspektem Vuex 5, któremu dzisiaj przyjrzymy się, jest komposowalność. Vuex 5 nie ma modułów z przestrzenią nazw, które są dostępne z jednego sklepu. Każdy z tych modułów byłby podzielony na zupełnie osobny sklep. Jest to dość proste w przypadku komponentów: po prostu importują potrzebne sklepy, uruchamiają je i używają. Ale co, jeśli jeden sklep chce wejść w interakcję z innym? W wersji 4 przestrzeń nazw splata całą sprawę, więc musisz użyć przestrzeni nazw w swoich wywołaniach commit i dispatch , użyć rootGetters i rootState , a następnie przejść do przestrzeni nazw, z których chcesz uzyskać dostęp do getterów i stanów. Oto jak to działa w 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 } } })

W wersji 5 importujemy sklep, którego chcemy używać, a następnie rejestrujemy go w use , a teraz jest on dostępny w całym sklepie pod dowolną nazwą właściwości, którą mu nadałeś. Sprawy są jeszcze prostsze, jeśli używasz odmiany API kompozycji definicji sklepu:

 // 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 } })

Nigdy więcej modułów z przestrzenią nazw. Każdy sklep jest osobny i jest używany osobno. Możesz use , aby udostępnić sklep w innym sklepie, aby je skomponować. W obu przykładach use jest w zasadzie tym samym mechanizmem, co wcześniejszy vuex.store i zapewnia, że ​​tworzymy instancje sklepów z poprawną instancją Vuex.

Obsługa TypeScript

Dla użytkowników TypeScript, jednym z największych aspektów Vuex 5 jest to, że uproszczenie ułatwiło dodawanie typów do wszystkiego. Warstwy abstrakcji, które starsze wersje Vuex sprawiły, że było to prawie niemożliwe, a teraz, dzięki Vuex 4, zwiększyły naszą zdolność do korzystania z typów, ale wciąż jest zbyt dużo pracy ręcznej, aby uzyskać przyzwoitą obsługę typów, podczas gdy w wersji 5 , możesz umieścić swoje typy w linii, tak jak masz nadzieję i oczekiwałeś.

Wniosek

Vuex 5 wygląda na prawie dokładnie to, na co liczyłem – i prawdopodobnie wielu innych – i czuję, że nie nadejdzie wystarczająco szybko. Upraszcza większość Vuex, usuwając część związanych z tym narzutów umysłowych i staje się bardziej skomplikowana lub gadatliwa, gdy dodaje elastyczności. Zostaw poniżej komentarze na temat tego, co myślisz o tych zmianach i jakie zmiany możesz wprowadzić zamiast lub dodatkowo. Lub przejdź bezpośrednio do źródła i dodaj RFC (Request for Comments) do listy, aby zobaczyć, co myśli główny zespół.