Wierzę, że zrozumienie motywacji stojących za mutacjami i działaniami pozwala lepiej ocenić, kiedy użyć których i jak. Uwalnia także programistę od ciężaru niepewności w sytuacjach, gdy „reguły” stają się niejasne. Po zastanowieniu się nad ich celami doszedłem do wniosku, że chociaż zdecydowanie mogą istnieć niewłaściwe sposoby użycia akcji i mutacji, nie sądzę, że istnieje podejście kanoniczne.
Najpierw spróbujmy zrozumieć, dlaczego w ogóle przechodzimy przez mutacje lub akcje.
Po co w ogóle przechodzić przez szablon? Dlaczego nie zmienić stanu bezpośrednio w komponentach?
Ściśle mówiąc, możesz zmienić state
bezpośrednio ze swoich komponentów. Jest state
to po prostu obiekt JavaScript i nie ma nic magicznego, co cofnie wprowadzone w nim zmiany.
// Yes, you can!
this.$store.state['products'].push(product)
Jednak robiąc to, rozpraszasz mutacje swojego stanu w każdym miejscu. Tracisz możliwość po prostu otwarcia pojedynczego modułu zawierającego stan i na pierwszy rzut oka zobaczysz, jakie operacje można na nim wykonać. Posiadanie scentralizowanych mutacji rozwiązuje ten problem, aczkolwiek kosztem pewnych schematów.
// so we go from this
this.$store.state['products'].push(product)
// to this
this.$store.commit('addProduct', {product})
...
// and in store
addProduct(state, {product}){
state.products.push(product)
}
...
Myślę, że jeśli zamienisz coś krótkiego na standardowy, będziesz chciał, aby kocioł również był mały. Dlatego przypuszczam, że mutacje mają być bardzo cienkimi opakowaniami wokół natywnych operacji w państwie, prawie bez logiki biznesowej. Innymi słowy, mutacje mają być używane głównie jak setery.
Teraz, gdy scentralizowałeś swoje mutacje, masz lepszy przegląd zmian stanu, a ponieważ Twoje narzędzia (vue-devtools) są również świadome tej lokalizacji, ułatwia to debugowanie. Warto również pamiętać, że wiele wtyczek Vuex nie obserwuje stanu bezpośrednio, aby śledzić zmiany, raczej polegają na mutacjach. Zmiany stanu „poza granicami” są więc dla nich niewidoczne.
Więc mutations
, actions
co jest w każdym razie różnica?
Akcje, podobnie jak mutacje, również znajdują się w module sklepu i mogą odebrać state
obiekt. Co oznacza, że mogliby również dokonać bezpośredniej mutacji. Więc jaki jest sens posiadania obu? Jeśli uznamy, że mutacje muszą być małe i proste, oznacza to, że potrzebujemy alternatywnych środków, aby pomieścić bardziej złożoną logikę biznesową. Działania są środkiem do osiągnięcia tego. A ponieważ jak ustaliliśmy wcześniej, vue-devtools i wtyczki są świadome zmian poprzez mutacje, aby zachować spójność, powinniśmy nadal używać mutacji w naszych działaniach. Ponadto, ponieważ akcje mają obejmować wszystko, a logika, którą zawierają, może być asynchroniczna, ma sens, aby akcje były po prostu asynchroniczne od samego początku.
Często podkreśla się, że akcje mogą być asynchroniczne, podczas gdy mutacje zazwyczaj nie. Możesz zdecydować, że to rozróżnienie będzie wskazówką, że mutacje powinny być używane w przypadku wszystkiego, co jest synchroniczne (i działania w przypadku wszystkiego, co jest asynchroniczne); Jednak napotkasz pewne trudności, jeśli na przykład musisz wprowadzić więcej niż jedną mutację (synchronicznie), lub jeśli potrzebujesz pracować z Getterem z twoich mutacji, ponieważ funkcje mutacji nie otrzymują ani Getterów, ani Mutacji jako argumentów ...
... co prowadzi do ciekawego pytania.
Dlaczego mutacje nie otrzymują Getters?
Nie znalazłem jeszcze satysfakcjonującej odpowiedzi na to pytanie. Widziałem wyjaśnienia głównego zespołu, które w najlepszym razie uznałem za dyskusyjne. Jeśli podsumuję ich użycie, Gettery mają być obliczanymi (i często buforowanymi) rozszerzeniami stanu. Innymi słowy, w zasadzie nadal są stanem, chociaż wymaga to pewnych obliczeń z góry i zwykle są tylko do odczytu. Przynajmniej tak są zachęcani do używania.
Tak więc, zapobieganie bezpośredniemu dostępowi do Getterów przez mutacje oznacza, że jedna z trzech rzeczy jest teraz konieczna, jeśli potrzebujemy uzyskać dostęp z tej pierwszej do niektórych funkcji oferowanych przez tę drugą: (1) albo obliczenia stanu dostarczane przez Getter są zduplikowane w miejscu, które jest dostępne do Mutacji (nieprzyjemny zapach) lub (2) obliczona wartość (lub odpowiedni sam Getter) jest przekazywana jako wyraźny argument do Mutacji (funky), lub (3) sama logika Gettera jest powielana bezpośrednio w Mutacji , bez dodatkowej korzyści z buforowania, jak zapewnia Getter (smród).
Poniżej znajduje się przykład (2), który w większości scenariuszy, z którymi się spotkałem, wydaje się „najmniej złą” opcją.
state:{
shoppingCart: {
products: []
}
},
getters:{
hasProduct(state){
return function(product) { ... }
}
}
actions: {
addProduct({state, getters, commit, dispatch}, {product}){
// all kinds of business logic goes here
// then pull out some computed state
const hasProduct = getters.hasProduct(product)
// and pass it to the mutation
commit('addProduct', {product, hasProduct})
}
}
mutations: {
addProduct(state, {product, hasProduct}){
if (hasProduct){
// mutate the state one way
} else {
// mutate the state another way
}
}
}
Wydaje mi się, że powyższe wydaje mi się nie tylko trochę zagmatwane, ale także nieco „nieszczelne”, ponieważ część kodu obecnego w Akcji wyraźnie wycieka z wewnętrznej logiki Mutacji.
Moim zdaniem to zapowiedź kompromisu. Uważam, że zezwolenie mutacjom na automatyczne otrzymywanie Getterów stanowi pewne wyzwanie. Może to dotyczyć projektu samego Vuex lub narzędzi (Vue-devtools i in.), Lub zachowania pewnej kompatybilności wstecznej lub kombinacji wszystkich wymienionych możliwości.
Nie wierzę, że samodzielne przekazywanie Getterów do swoich mutacji jest koniecznie oznaką, że robisz coś złego. Widzę to jako po prostu „łatanie” jednej z wad frameworka.