-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Discussion on removing mapXXX helpers in Vuex 4 #1417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
mapXXX
helpers in Vuex 4
Thanks for opening this discussion @chrisvfritz! I'm actually thinking I guess the intention of scoped-slot based approach is that it forces the users to write application logic in the store so that the component will be just the reflection of the store state. But I think there are some cases that we want to write some logic in components. If there are something I'm missing about the approach, I'd like to know. (I didn't actually experience the approach yet) |
PLZ keep
All my modules have this boilerplate: const moduleName = `the_name_of_module`
const namespaced = true
const types = {
SOME_ACTION: 'SOME_ACTION',
...
}
// (the objects store getters, mutations and actions)
...
...
...
// EXPORTS
export { types }
export const {
mapState,
mapGetters,
mapActions,
} = createNamespacedHelpers(moduleName)
export default {
moduleName,
namespaced,
state,
getters,
mutations,
actions,
} And here is how I use in the component <script>
import { types, mapState, mapGetters, mapActions } from '@/store-modules/some-module'
export default {
computed: {
...mapGetters(['someGetter']),
},
methods: {
...mapActions({
methodName: types.SOME_ACTION,
}),
},
}
</script> As you can see here is a bit boilerplate code (but not too much); however, it is very explicit to trace where the action is mapping from, and can be scaled very well, especially when new people being added to our project. I'm also getting helped by the IDE to map my functions thanks to static analysis. PS1. I'm only calling actions in components, I don't like the idea of calling mutation since it just confusing, so no PS2. I think it is also possible to further abstract the module boilerplate, but that is my current approach for now. |
For the record, I also never use |
I'm not sure what the what Getting rid of the need for separating actions and mutations means. We still need to update the state, and since the way current Vue works, I prefer to have mutation stay in sync, while action in async. To me I believe Vuex should only working on states not anything-else. So far I can elegantly decouple Vuex and Vue-router via |
@leoyli The main reason we need mutations now is so that we can capture and label mutations for logging, plus debugging and prototyping via time travel in the devtools. However, with Vue's reactivity system, it's theoretically possible to capture and label state changes automatically. That way, actions can mutate state directly without losing data in the devtools - though improvements are also planned here actually, including an async timeline of actions. 🙂 Does that make sense? |
@chrisvfritz I'm not sure if I get your points. In the roadmap just saying it is considered to combine (?) the mutation and actions, but did not saying how or points a direction. As for the devTool, I have no idea about its implementations. I know the way Vue tract the state changing making it is powerful and reactive and what you've said is theoretically possible, but the point is the conceptual architecture of Vuex is inspired by Flux, i.e. the predictable uni-directional data flow (and that is why often these libraries are design to encourage functional paradigm). Thus, we need something like reducer or mutation to tell the store how to update its state (so it has to be a sync function). To my understanding, the reason we have actions and mutations is just a separation of concerns. Also, due to this separation of concerns, that is the reason why the doc tell us only mutation is suppose to mutate states. In React/Redux, you can not mutate the sate out of a reducer, but In Vue/Vuex, you actually can mutate the state of out of mutations. However, you can dose not mean you should. Although I can see here that Vue is more align to "reactive" and less "functional" programming style, my statement might not be that relevant. (I sometime think that React and Vue should swap their names, haha) |
When the roadmap says they'll be "combined", it's just referring to code like
Flux, functional, immutable, etc - these are strategies, but not the goal. The goal is to be able to log, describe, and replay state changes to facilitate debugging and prototyping complex state. Previously, mutations were used to describe and replay state changes, but the reactivity system can do both of these automatically. Instead of having to explicitly create and name a mutation So by embracing mutable, but reactive state, we can actually achieve the same goals, but with fewer concepts, simpler code, and less boilerplate.
Mutating state outside of a reducer is actually possible with Redux, unless you're using immutable data (e.g. with immutable.js or immer). Unlike Redux though, Vue actually logs a warning if users try to mutate state outside of mutations, making it more difficult to do accidentally. Enabling strict mode makes it impossible, throwing an error if you try. 😉 |
Wow, thank you for clarifications here. Now I see the picture. Indeed, to define the mutation types that not used in anywhere else (except in Maybe a bit off topic, I do agree functional is just a strategy; but walk way from the concept of pure functions cause me some concerns about testability. I can see now we are more or less testing the side effects, is reactive system can also play a role here?! Back to the topic, another arguable point here is the magic string. Often, I found it is almost everywhere in Vue, making it is (1) easy to make typos, (2) hard to trace where the function is coming from. And that is why I also much prefer more explicit approach, |
@leoyli Actions can still be tested the same way it was possible to test mutations, e.g.: const state = { foo: 'bar' }
const newFoo = 'baz'
myModule.actions.updateFoo({ state }, newFoo)
expect(state.foo).toEqual(newFoo) Which is testing side effects, but it's not harder than if actions were pure functions that returned a new state object, like this: const state = { foo: 'bar' }
const newFoo = 'baz'
const newState = myModule.actions.updateFoo({ state }, newFoo)
expect(newState.foo).toEqual(newFoo) And you can still break down logic into as many separate modules, exported variables, or pure functions as you want where it helps with testability. Vue's reactivity system - and even Vue and Vuex themselves - don't have to be involved at all when testing Vuex modules. |
Thanks a lot! Ok I now finally there. If I were going to upgrade Vuex 3 to 4, it sounds like I just simply move all my mutations under actions, and replace |
Are there any references to describe what this API might look like? Personally, I find scoped slots in Vue to be very awkward. I've found that using [1] (except that making provided values reactive is a bit awkward) |
The idea of having both |
Maybe |
Closing due to inactivity. This is interesting discussion regarding next iteration of Vuex, though I see people referencing this issue and sees it like this is happening. It might, but because of its age, I think we should start a new discussion on how next Vuex iteration would be when the time comes. |
@yyx990803 @ktsn Regarding this note in the current roadmap for Vuex 4:
I've experimented with this scoped-slot pattern in some apps, but it has a couple serious limitations that have led to me never using it anymore:
Vuex state/getters/actions are only conveniently available in templates and render functions. That means the convenience is unavailable for computed properties that rely on Vuex state and methods that dispatch Vuex actions, which is a big limitation.
It requires tight coupling of components to Vuex modules, which I've found doesn't scale well to large apps. Instead, I've found it very useful to never use the
mapXXX
helpers directly in my components, but in a state helpers file, which describes the public interfaces for all Vuex modules. From this file, I'll import the concerns I care about into individual components. For example:This prevents verbose and duplicated
mapXXX
code in components, replaced by a nice list of the global concerns this component cares about. But the benefit really comes in when adding new state/getters/actions or when refactoring a Vuex module. For example, if I add a newuserLoggedIn
getter toauthComputed
, it will automatically be available in any component usingauthComputed
. Or, if I rename some state or split it out into additional modules, the state helpers file allows the refactor to be done in two steps: first on the Vuex side, while updating the state helpers for compatibility, then in my components (if I actually want the public interface to change as well).For these reasons, I feel like the
mapXXX
helpers should stay and the scoped slot pattern should actually be avoided. What are others' thoughts? Is there something I've missed?The text was updated successfully, but these errors were encountered: