vuejs / vuex

🗃️ Centralized State Management for Vue.js.
https://vuex.vuejs.org
MIT License
28.42k stars 9.58k forks source link

Confusing namespaced api #1500

Open dcrystalj opened 5 years ago

dcrystalj commented 5 years ago

What problem does this feature solve?

The most confusing thing when using namespaced vuex is inconsistant api with state and getters.

Example: to access state this.$store.state.user.name

to access getter this.$store.getters['user/fullName']

This is so confusing for me and my coworkers. Is there reason to have this inconsistency?

Problem occurs when you try to watch getters example:

watch: {
  '$store.state.user.name': {
    handler(name) {
      console.log(name)
    },
  },
  '$store.getters[user/fullName]': { // not working
    handler(fullName) {
      console.log(fullName)
    },
  },
}

Current solution Is to use mapGetters() helper for getters only. Why? because of weird api. Please note that we try to avoid mapGetters, mapState helpers... due to explicitly in large app

What does the proposed API look like?

Add api for getters same as is for state.

Example: $store.gettters.user.fullName

LinusBorg commented 5 years ago

Getters are computed properties. And just like you can have nested objects in your component's data, but not nested computed props, we can have nested (namespaced) state objects in vuex, but not nested getters.

So we have to do the namespacing like we do it for actions and mutations: with a slash-separated string path: 'namespace/action|mutation|getter'.

Seen that way, the state is actually the one exception to the rule, not the getters.

It it impossible to provide an API like you proposed? no, certainly not. It's just that so far, we so far felt that getters, actions and mutations sharing the same namespace syntax makes sense.

Akhawais commented 5 years ago

+1 for $store.gettters.user.fullName

I understand the need to differentiate state from actions/mutations/getters however it looks much cleaner to have it in the same style as namespaced state.

Could it be possible to provide an option for this upon creating a store?

dcrystalj commented 5 years ago

an API like you proposed? no, certainly not. It's just that so far, we so far felt that getters, actions and mutations sharing the same namespace syntax makes sense.

The way we are using vuex in our company is that we always use $store.dispatch('namespace/action') in components and we never use mutations. so far this feels ok, except possible errors in naming (less autocompletion)

to render data we use {{ $store.state.user.fullName }} or {{ $store.getters['user/fullName'] }} and we never use mapGetters or mapState. We could write extra getter for every state just to have things consistent, but as you know, less code less bugs + less work.

Another incosistent things in api are parameters for getters, actions and mutations; but this is for another issue.

I believe allowing to have consistant api $store.state['user/fullName'] would be better but i prefer object like style which is less prone to errors and feels less hackish.

mathieustan commented 5 years ago

You should use mapState or mapGetters, which make it more readable. Exemple:

import { mapState, mapGetters } from 'vuex';

export default {
  computed: {
    ...mapState({
      userFullName: state => state.user.fullName,
    }),
    // OR
    ...mapGetters({
      userFullName: 'user/getUserFullName',  // (getters name should be different)
    }),
  },
  watch: {
    userFullName() {
      ....
    },
  },
};
barakbd-bluevine commented 5 years ago

I agree, the difference in API is confusing. https://vuex.vuejs.org/guide/modules.html#binding-helpers-with-namespace

First of all, the docs do not have an example of mapGetters with moduled stores. When is comes to modules, mapActions (array declaration) is different than mapGetters(object declaration). APIs should have consistent design for an easier learn curve.

dcrystalj commented 5 years ago

@mathieustan for large scale app we found out that using any mapXxxx is just

so we never use this helper and I can tell you how much better life is :D But I agree that in docs it is all about mapXxxx and you always get a filling that this is the way to go. I think all this mapXxxx should be removed from docs examples so the newbies would know where does things come from and not be thinking we should use it like this because it is in the docs.

yolio2003 commented 5 years ago

another Confusing example: i think this:

...mapState([
  'modulex/count'
]),

should be equal to :

...mapState('modulex', {
  'modulex/count': 'count',
}),

and much more easier! but it's not!!!

yiliang114 commented 5 years ago

if a module has many properties, there will be lots of redundant codes such as $store.state.user.a, $store.state.user.b, $store.state.user.c, $store.state.user.d,$store.state.user.e ... i do not like to repeat. Maybe you mean i could use object destructuring but i think if you use destructuring to declare as global variables it just

pikax commented 5 years ago

I found that using createnamespacedhelpers is more readable than using mapXXX.

I disagree with @dcrystalj, if each developer uses different naming for things, I think that's an issue to be discuss by the team.

// you can change the component variable name, to better describe where it comes from
...mapGetters('user', {
  userName: 'name'
})

same end result as:

// user.store.js
export userStore = createNamespacedHelpers('user', module);

// component{User}.Vue
import {userStore} from './user.store.js'
...userStore.mapGetters({
  userName: 'name'
})
dcrystalj commented 5 years ago

@pikax your solution is still making code verbose and tedious.

...mapGetters('user', {
  userName: 'name'
})

this is just making one more layer of abstraction dev has to hold in the head. using $store.getters.user.name would be as clear as crystal and also one line of code. Please note that when having example of one component it's not problem. This is problematic when app is huge/ multiple years of work

vegerot commented 4 years ago

I actually came here looking for the opposite. I was wondering how I could access state by doing

store.state['a/b/c/count']

just like how I could do

store.getters['a/b/c/count']
refayathaque commented 4 years ago

Is there a way to access getters from both the root state and a namespaced module? Below, getAuthStatus is a getter in the root state, and I'm wondering if I can access both root and the namespaced module getters in one ...mapGetters call

...mapGetters(["getAuthStatus"]),
...mapGetters("cartModule", ["getCartData"])
cuebit commented 4 years ago

@refayathaque yes, with a level of verbosity:

...mapGetters({
  getAuthStatus: "getAuthStatus",
  getCartData: "cartModule/getCartData"
})
refayathaque commented 4 years ago

@refayathaque yes, with a level of verbosity:

...mapGetters({
  getAuthStatus: "getAuthStatus",
  getCartData: "cartModule/getCartData"
})

Thank you @cuebit !