Skip to content

New module option namespace #380

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

Closed
ktsn opened this issue Oct 9, 2016 · 15 comments
Closed

New module option namespace #380

ktsn opened this issue Oct 9, 2016 · 15 comments
Assignees

Comments

@ktsn
Copy link
Member

ktsn commented Oct 9, 2016

Last update: 2016-10-23

This proposal is come up from the discussion in #359.

Summary of proposal

Modules will have namespace option that expects string value. All getters, actions and mutations types in the module are prefixed by the given value.

Namespace is inherited to child modules implicitly and we can also nest namespace by declaring namespace option on the child modules.

Also, this option can be more useful with subStore option that is another new module option proposal #381.

Why is this worth adding?

Currently Vuex does not auto-namespace getters, actions and mutations even though they are defined in some nested modules, the namespacing is handled by developers. This design is reasonable because there are some cases that the auto-namespacing will suffer developers as discussed in #236 .

However, I think it would be useful that we have optional auto-namespacing because we probably want to prefix all getters, actions and mutations type in the same module in most cases.

Examples

export default {
  namespace: 'user/',

  // module assets
  state: { ... }, // module state will not be changed by prefix option
  getters: {
    isAdmin () { ... } // -> getters['user/isAdmin']
  },
  actions: {
    login () { ... } // -> dispatch('user/login')
  },
  mutations: {
    setName () { ... } // -> commit('user/setName')
  },

  // nested modules
  modules: {
    // inherit the prefix from parent module
    nestedA: {
      state: { ... },
      getters: {
        nestedGetterA () { ... } // -> getters['user/nestedGetterA']
      }
    },

    // nest the namespace
    nestedB: {
      namespace: 'nestedB/',

      state: { ... },
      getters: {
        nestedGetterB () { ... } // -> getters['user/nestedB/nestedGetterB']
      }
    }
  }
}
@yyx990803
Copy link
Member

I like this.

One thing about nested modules with prefix - should it become nestedB/xxx or user/nestedB/xxx? I feel that multiple nesting makes more sense.

@psi-4ward
Copy link

definitely user/nestedB/xxx as this one is expected when nestedB is a module from user

@ktsn
Copy link
Member Author

ktsn commented Oct 9, 2016

Because I thought if we want to register a root level mutation, we can clear parent prefix by overwriting.
But I've noticed we had better to define such action on root and proxy to the nested mutation. I update about the nested prefix :)

@LinusBorg
Copy link
Member

LinusBorg commented Oct 9, 2016

One thing about nested modules with prefix - should it become nestedB/xxx or user/nestedB/xxx? I feel that multiple nesting makes more sense.

But the is would mean that

  1. it would require the parent module to be prefixed as well. What happens if the parent module does not have a prefix defined? is the module name used?
  2. the above makes the nested action etc. names pretty hard to predict/keep track of - to use a module's action/getter you have to consider the config of the parent module(s) as well.

Could that be a problem, e.g. for third-party modules?

@ktsn
Copy link
Member Author

ktsn commented Oct 10, 2016

it would require the parent module to be prefixed as well. What happens if the parent module does not have a prefix defined? is the module name used?

I think parent should not add prefix in this case, like '' is defined in default if the option is not specified.

the above makes the nested action etc. names pretty hard to predict/keep track of - to use a module's action/getter you have to consider the config of the parent module(s) as well.

Yes, I think this is developer's responsibility.
Without prefix, we have to write namespace boilerplate but it let us easier to predict/keep track of them.
With prefix, we can save labor writing boilerplate but it let us hard to predict/keep track of.

We can select which the strategy of namespacing. This is the reason that I propose this prefix option as optional.

In addition, I think #381 would solve this problem because it can access module getters etc. without concern of prefixes.

for third-party modules

This is the point that we should think carefully. The problematic situation would be:

  1. a plugin provide some modules and let users add it to where they want and
  2. the plugin itself uses getters etc. inside the plugin code

e.g. following code:

// userland
import { pluginModule, plugin } from 'some-plugin'

export default new Vuex.Store({
  modules: {
    // registered under prefixed module
    libs: {
      prefix: 'libs/',
      modules: {
        somePlugin: pluginModule
      }
    }
  },
  plugins: [plugin]
})

// in plugin
export function plugin (store) {
  someObject.on((data) => {
    // this is failed since 'pluginEvent' action
    // is registered as 'libs/pluginEvent'
    store.dispatch('pluginEvent', data)
  })
}

I'm not sure that the solutions of this but here is some idea:

  1. Plugins allow to pass prefix value on their initialization.

    function createPlugin ({ prefix = '' } = {}) {
      return function (store) { /* ... */ }
    }
  2. Users can avoid to apply prefix for such plugins.

  3. Always use sub store in plugin code (if sub store would be implemented)

    // plugin module
    // this is prefixed on userland
    const pluginModule = {
      subStore: 'somePlugin',
      actions: {
        someEvent () { /* ... */ }
      }
    }
    
    // plugin function
    function plugin (_store) {
      const store = _store.subStores.somePlugin
      // add parent prefix implicitly
      store.dispatch('someEvent')
    }

Any thoughts about this?

@ktsn
Copy link
Member Author

ktsn commented Oct 10, 2016

One more thing, I think it should resolve prefixes of getters, dispatch and commit in action context in prefixed modules. But how should we provide root level of them?

I'm currently thinking about providing rootGetters and root option for dispatch and commit.

export default {
  prefix: 'prefix/',
  actions: {
    someAction ({ dispatch, commit, getters, rootGetters }) {

      getters.someGetter // -> 'prefix/someGetter'
      rootGetters.someGetter // -> 'someGetter'

      dispatch('someOtherAction') // -> 'prefix/someOtherAction'
      dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

      commit('someMutation') // -> 'prefix/someMutation'
      commit('someMutation', null, { root: true }) // -> 'someMutation'

    }
  }
}

@vinicius73
Copy link

@ktsn Excellent proposal. I was thinking of solutions to these situations and this solution would be perfect.

@jbruni
Copy link
Contributor

jbruni commented Oct 13, 2016

For the prefix to match my current "getters, actions and mutations" naming patterns, it would have to be applied differently on each case...

  • for getters and actions, I'd like the prefix to form a lowerCamelCase result; examples:
    • before: threads
    • after: conversationThreads
    • before: setAnswerable
    • after: conversationSetAnswerable
  • for mutations, I'd like the prefix to form an uppercase, underline-separated result:
    • before: SET_ANSWERABLE
    • after: CONVERSATION_SET_ANSWERABLE

If prefix is to be implemented, to support a use case like above, maybe it could also support a function as value.

@ktsn
Copy link
Member Author

ktsn commented Oct 15, 2016

To cover @jbruni's use case, I think we should change the option name to namespace and let it receive both string and Function values.

If string value is specified, it behaves as same as the original proposal.
If function value is specified, it executes the function and get the namespaced value. Then we can control namespace rule more flexible.

export default {
  namespace (type, category) {
    // 1st argument will be getters etc type before namespacing
    // 2nd argument will be 'getter', 'action' or 'mutation'
    // users must return namespaced name
    return 'user/' + type // same as `namespace: 'user/'`
  }
}

@ktsn ktsn changed the title New module option prefix New module option namespace Oct 18, 2016
@ktsn ktsn self-assigned this Oct 20, 2016
@ktsn
Copy link
Member Author

ktsn commented Oct 23, 2016

I noticed that it is difficult to implement namespaced getters I described on this comment if the namespace option accepts function. Because we cannot predict the result of the namespace function. We can proxy to the getters in the same module but cannot proxy to the one in the other modules with same namespace.

Following is the problematic case. getterA and getterB has the same namespace but we cannot access getterA from someAction while we can access getterB.

export default {
  namespace (type) {
    return 'prefix' + type[0].toUpperCase() + type.slice(1)
  },

  modules: {
    nestedA: {
      getters: {
        // prefixGetterA
        getterA: () => 1
      }
    },

    nestedB: {
      getters: {
        // prefixGetterB
        getterB: () => 2
      },

      actions: {
        someAction ({ getters, rootGetters }) {
          getters.getterB // -> 2
          getters.getterA // -> undefined
          rootGetters.prefixGetterA // -> 1
        }
      }
    }
  }
}

If we can use Proxy, this should be solved easily. But unfortunately Proxy is currently not implemented sufficient numbers of browsers yet and cannot be polyfilled.

So I think we should implement namespace: string option for now and revisit namespace: function if we can use Proxy in the future.

@Giseudo
Copy link

Giseudo commented Nov 6, 2016

@ktsn how can we use mapState to import our module states when using namespaces?
I know we can get states like this:

count: function () {
  return this.$store.state.BarModule.count;
}

But definitely would be better having mapState supporting modules namespaces like mutations, methods, actions and getters are.

@ktsn
Copy link
Member Author

ktsn commented Nov 6, 2016

I believe #381 covers that.

@yyx990803
Copy link
Member

Merged #420 :)

@PengMQ
Copy link

PengMQ commented Mar 2, 2017

Hi, is this already working in production?Cause I tied adding the 'namespace' option to my module, and I still get the 'duplicate xxx key' error and and using '"vuex": "^2.1.2"'

@LinusBorg
Copy link
Member

Yes, this is working. Please direct usage questions to forum.vuejs.org

@vuejs vuejs locked and limited conversation to collaborators Mar 2, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants