-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
2.0 design #236
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
My thoughts are on the order of params inside actions. const store = new Vuex.Store({
actions: {
doSomething(state, payload, dispatch) {
dispatch('some-mutation', payload)
}
}
}) ...could be the in the inverse order: const store = new Vuex.Store({
actions: {
doSomething(dispatch, payload) {
dispatch('some-mutation', payload)
}
}
}) Because, inside an action you always use |
@nicolasparada definitely, the argument orders are not set in stone - I actually was thinking about it too. |
how about dynamic modules injection? |
This might have been discussed at some point, but is the Kind of wish there was some way to make getters and actions play nicely with props (from a syntax point of view, having to declare getters and computed props for one thing feels like a hassle sometimes), but don't have any ideas myself.
I'm not sure about namespacing actions. Wouldn't that restrict you from using modules if you want a single action that can trigger multiple mutations? |
Mainly, I agree with all the concepts and ideas brought in by this design. It will certainly help further simplify Vuex.
The
I would replace
In agreement with @nicolasparada, this makes simple actions easier to create. |
I agree with @blake-newman that |
Interesting, quite a step away from Redux. Redux community tried to bundle action creators with reducers, but I believe Dan is against it (1, 2). According to him, it kills the essence of flux: action objects and reducers are separated, any reducer can respond to any action object. That said, bundled modules are optional, so you can use then when they fit and fall back to idiomatic Redux when they don't. To be honest, I don't like the idea of dispatching actions and mutations through a single method. I think that everything that is dispatched should be recorded by the devtools. But actions should not be recorded because of side effects. In Redux you can dispatch anything, and "non-idiomatic" chunks are processed by middleware. But it's quite confusing, I'd rather have separate methods ( Onto suggestions. With UPDATE: Added references to Dan's words: |
@simplesmiler I think there is a real need for actions defined alongside mutations, which doesn't prevent a higher-up action from dispatching multiple namespaced mutations. I like the arguments simplification - it also keeps it closer to the current API, so the signature remains almost he same:
|
@sleewoo that's something I've been thinking too! It could also be used for hot-reloading a module. Maybe something like |
@sebastiandedeyne I also feel the nesting is a bit awkward. In 2.0 we may get rid of the options all together, so you can just do If you want a single action to trigger multiple mutations, you just need to be more explicit: dispatch('moduleA:increment')
dispatch('moduleB:increment') I think this actually makes the data flow clearer: you know you are triggering mutations in two different modules and what modules are affected. We may also need an "escape hatch" for dispatching mutations in root scope from any module: dispatch('@someGlobalMutation')
dispatch('@rootModule:mutation') And a module can listen to global mutations using the same syntax: mutations: {
'@someGlobalMutation': state => {
// state is still local state
}
} |
As for separating methods for mutations and actions - this is also something I thought about. Maybe we keep |
Feels kind of weird though, all other libraries teach us to dispatch actions, and then Vuex would call actions and dispatch mutations. I agree with the separation, just not with the naming. |
That would be nice. Maybe even consider them global (or application-wide) instead of root-scoped, and let any module issue and react to global mutations and actions. UPDATE: or even invert the logic, and make "normal" mutations global, and |
Also, should new Vuex encourage |
Keeping actions to work with only one module/mutation sounds a bit wired to me. Imagine I need to implement some module to collect stats for my app. With redux I can just attach reducer to my store and make it respond to actions I need. With Vuex I forced to be more explicit and duplicate |
I think the design concept is very reasonable. In my developments, I always prefix or suffix mutation names for a related module. (and also I do it on Redux) And I also agree with @simplesmiler. Explicitly dispatching multiple mutations is good, but I think sometimes we want to dispatch some mutations globally. In my opinion, global registration would be better than global dispatch because global dispatch can accidentally execute unexpected mutations. // normal mutation name
const RESET = 'reset'
// global mutation name
// e.g. prefixed with special character is global
const GLOBAL_RESET = '#reset'
const someModule = {
state: { value: '' },
mutations: {
[RESET] (state) { // register under the namespace
state.value = ''
}
[GLOBAL_RESET] (state) { // register to global
state.value = ''
}
}
}
// RESET is namespaced
store.dispatch(`someModule:${RESET}`)
// GLOBAL_RESET is not namespaced
store.dispatch(GLOBAL_RESET) |
I basically agree all the concept and ideas and @blake-newman. And I think that we should be provided the useful Vuex API I/F for user. |
@OEvgeny even in Redux it feels weird using a reducer for collecting app analytics - a middleware seems more appropriate. So in Vuex you'd just write a plugin for that purpose. |
@yyx990803 thanks for response. I think there are different types of stats that can be collected. Some of them we don't need to process on the server side, some we need to collect before processing, and some we will use to show in featured parts of our app. I like what with redux we can continue changing our store logic without touching whole app. As an example we can rewrite some parts of our store to fit new needs and implement new components/views for our new state without breaking or modifying our actions and current components/views. We can mix up together new and old parts of our store until it's needed. So, it gives more flexibility for further development. |
@OEvgeny I don't think namespaced modules would make it any harder - I'm not sure what aspects in the proposal makes you think that way? |
@yyx990803 I think it's all about forcing mutations inside modules to be namespaced. Then each time when I change structure of my store, I will be forced to change actions to fit this changes. I think it can be hard to do in some cases. |
|
With an option to operate on global scope you have my thumbs up. |
I'm agree with @simplesmiler about some option to achieve global actions behavior. |
Actually, now that I think about it, there's no need for auto-namespacing, because users can namespace the mutations/actions themselves. This does leave the potential for namespace clashes, but the chance seems quite low. All we really need is auto state-resolving for module actions. |
For all those interested, the proposal has been heavily revised. Feedback welcome. |
I love everything about the proposal and new features and will be moving to this version as soon as I get some time to refactor. My only question is regarding module getters receiving the sub state tree: I currently normalize all my api requests and put the different entities in different modules. This will cause all my getters to live on the root store when I want to denormalize an item. Any reason why the module getters can't receive the full state? Is it just to align with the module mutations? |
@theotherzach long snippets are more appropriate in a gist, and a proposal, especially one that directly contradicts this design, would be more fitting as a separate issue. That said, I think your usage (from my memory of glancing over what you posted before) were indeed missing a few points about Vuex:
Finally, if you don't think it's worth the extra code to gain trackability of your state (as shown from your proposal), you are probably better off just stay with component local state + the simple store pattern mentioned here. |
@posva Sorry for my over-reaction but I'm late on this project and exhausted. In short I find the following based on my experiences. I hope that I'm using Vuex wrong, but even if that's the case then perhaps this points to evidence that Vuex is easy to use wrong.
https://gist.github.com/theotherzach/92ddb73af8f9e4c8ab70ca82a49c8fc1 |
@thoughts1053 I think it's probably a good idea to expose an extra |
@yyx990803, hopefully last questions regarding getters:
|
@thoughts1053 You hit the nail on the head on both points. I find myself doing 1 a lot, and 2 would make life so much easier. Currently in 1.0 when you call a getter from a getter, it requires passing in the state/store to the next getter, and putting each getter into an object that can be accessed by all getters. Would be nice to give each getter some way of calling another getter like how you would in a component |
@yyx990803 |
@thoughts1053 Concerning 1.: Concerning 2.:
|
@LinusBorg Thanks! I saw the release last night. You've also answered both my questions regarding passing parameters to getters. #1 was my main concern, but creating a local computed property with |
Looking at the gist you posted, that should probably be split up into some state submodules to make it more maintanable. concerning the boilerplate when having async actions: The 2.0 design proposal actually makes this much easier since async actions now should return a Promise:
|
I just upgraded to
|
@analog-nico about 2). I only had to use this - https://babeljs.io/docs/plugins/transform-object-rest-spread/. No other setup was needed in my case. I agree however about the need of info in documentation. |
I didn't test it yet, so... does Object.assign() work? computed: Object.assign({},
localComputed() {/*...*/},
mapGetters(['a', 'b', 'c', 'd'])
),
methods: Object.assign({},
localMethod() {/*...*/},
mapActions(['b'])
) ?? |
@karol-f True, as you say, the note in the docs is exactly my point. Likely any toolchain will easily support the operator. But since it is ES7, chances are, it's not supported by default. So it is better having the note than letting more people like me turning their toolchain inside out. ;) @nicolasparada |
@analog-nico Ah, of course, I forgot them. |
@yyx990803 what's the recommended way regarding getters with parameters in Vuex 2.0? I used them as helpers, e.g. for some calendar I have
|
@yyx990803 I get error dispatch undefined. FYI I'm using vuex 2.3. what's wrong? |
This is not a support forum. Please ask questions on forum.vuejs.org Also, please provide code that we and other people on the forum can analyse to solve your problem. |
1. Terms naming change for better semantics
Dispatching a mutation never sounded right. Dispatch should be indicating the intention for something to happen. For mutations, we want a verb that indicates the state change is happening as soon as you call it.
store.dispatch
is nowstore.commit
.store.dispatch
will be used for firing actions instead. (see next section)The new naming better conveys the semantics behind the two methods:
2. Module Portability & Composability
Actions inside store and modules
There has been common requests on shipping actions with the store, or inside modules. Previously, the reason for not putting actions in the store/modules was mainly how do we access them. Actions defined inside the store means we need access to the store - again the singleton problem, which is now a non-issue.
Now since they are just functions, we may expose them as
store.actions
and call them directly. In fact this was the API of Vuex 0.4.x:A problem is when actions are defined inside modules, what if multiple modules define actions of the same name? Also, sometimes we may want to call an action that affect multiple stores, just like mutations.
It seems actions are also more like event listeners. Instead of calling them directly as functions, we now use
store.dispatch
to trigger them:This way, you can dispatch actions in multiple modules with a single call, just like mutations. It's also more explicit that you are firing off some side-effects in your store, instead of just calling a random function.
Getters, too
You can now define getters in the store / modules too. Similar to module mutations, module getters receive the sub state tree:
3. Composable Action Flow
Now that we are putting actions inside modules and calling them via
dispatch
, we somehow lose out on the composability of actions, because they are no longer just functions you can call. Here's how we can make them composable again:To indicate the completion of an action, return a Promise from the action.
store.dispatch
will return that Promise if there is only a single handler called. If multiple action handlers are matched, it will return a Promise that resolves when all Promises returned by those handlers are resolved.Based on (1) and
async/await
, we can have very clean composition between async actions:The convention of returning Promises also allows Vuex to:
Component Binding
The design of Vuex 0.6~1.0 had a somewhat annoying design constraint: avoid directly accessing stores inside components. The store is injected at the root component, and implicitly used via the
vuex: { getters, actions }
options. This was in preparation for Vue 2.0 SSR (server-side rendering), because in common SSR setups (directly requiring the component in Node.js and render it), dependence on a global singleton will cause that singleton to be shared across multiple requests, thus making it possible for a request to pollute the state of the next one.However, with the new
bundleRenderer
strategy implemented in Vue 2.0.0-alpha.7, this is no longer an issue. The application bundle will be run in a new context for each request, making it unnecessary to structure your app without singletons just for the sake SSR. This also means it's totally fine to justimport store from './store'
, use plain computed properties to returnstore.state.xxx
, or callingstore.dispatch()
in plain methods.This opens up path to simplifying the component binding usage, since theoretically you don't need any binding at all. Currently, the
vuex
options feels a bit clumsy and indirect.In Vuex 2.0, the
vuex
option will be deprecated in favor of just computed properties and methods. You are free to structure your Vuex store usage the way you prefer. However, we will be keeping the injection forthis.$store
so that you can do this:The above alleviates the need to import the store everywhere. But it can get verbose when you have many getters and actions in the same component. Therefore we provide two helpers,
mapGetters
andmapActions
:So in the component,
this.a
maps tothis.$store.getters.a
, andthis.d(...args)
maps tothis.$store.dispatch('d', ...args)
.If you want to map a getter/action to a different local name, use an object instead:
Finally, you can easily compose them with local computed properties and methods using Object spread operator:
2.0 so soon? How about 1.0?
1.0 contains small breaking changes but should be a very easy upgrade for existing 0.6~0.8 users. It will be maintained as a stable release in parallel to 2.0.
The text was updated successfully, but these errors were encountered: