Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid the need of writing this.$store.state.donkey all the time in Vue?

I'm learning Vue and I noticed that I have the following syntax more or less everywhere.

export default {
  components: { Navigation, View1 },
  computed: {
    classObject: function() {
      return {
        alert: this.$store.state.environment !== "dev",
        info: this.$store.state.environment === "dev"
      };
    }
  }
}

It's a pain to write out this.$store.state.donkey all the time and it lowers the readability too. I'm sensing that I'm doing it in a less than optimal way. How should I refer to the state of the store?

like image 922
Konrad Viltersten Avatar asked Dec 01 '16 11:12

Konrad Viltersten


1 Answers

you can set computed properties for both states & getters i.e.

computed: {
    donkey () {
        this.$store.state.donkey
    },
    ass () {
        this.$store.getters.ass
    },
    ...

Whilst you still need to call the $state.store once you can then reference a donkey or an ass on your vm...

To make things even easier you can pull in the vuex map helpers and use them to find your ass ... or donkey:

import { mapState, mapGetters } from 'vuex'

default export {

    computed: {

        ...mapState([
            'donkey',
        ]),

        ...mapGetters([
            'ass',
        ]),

        ...mapGetters({
            isMyAss: 'ass', // you can also rename your states / getters for this component
        }),

now if you look at this.isMyAss you'll find it ... your ass

worth noting here that getters, mutations & actions are global - therefore they are referenced directly on your store, i.e. store.getters, store.commit & store.dispatch respectively. This applies whether they are in a module or in the root of your store. If they are in a module check out namespacing to prevent overwriting previously used names: vuex docs namespacing. However if you are referencing a modules state, you must prepend the name of the module, i.e. store.state.user.firstName in this example user is a module.


Edit 23/05/17

Since the time of writing Vuex has been updated and its namespacing feature is now a go to when you work with modules. Simply add namespace: true to your modules export, i.e.

# vuex/modules/foo.js
export default {
  namespace: true,
  state: {
    some: 'thing',
    ...

add the foo module to your vuex store:

# vuex/store.js
import foo from './modules/foo'

export default new Vuex.Store({

  modules: {
    foo,
    ...

then when you are pulling this module into your components you can:

export default {
  computed: {
    ...mapState('foo', [
      'some',
    ]),
    ...mapState('foo', {
      another: 'some',
    }),
    ...

this makes modules very simple and clean to use, and is a real saviour if you are nesting them multiple levels deep: namespacing vuex docs


I have put together an example fiddle to showcase the various ways you can reference and work with your vuex store:

JSFiddle Vuex Example

Or check out the below:

const userModule = {

	state: {
        firstName: '',
        surname: '',
        loggedIn: false,
    },
    
    // @params state, getters, rootstate
    getters: {
   		fullName: (state, getters, rootState) => {
        	return `${state.firstName} ${state.surname}`
        },
        userGreeting: (state, getters, rootState) => {
        	return state.loggedIn ? `${rootState.greeting} ${getters.fullName}` : 'Anonymous'
        },
    },
    
    // @params state
    mutations: {
        logIn: state => {
        	state.loggedIn = true
        },
        setName: (state, payload) => {
        	state.firstName = payload.firstName
        	state.surname = payload.surname
        },
    },
    
    // @params context
    // context.state, context.getters, context.commit (mutations), context.dispatch (actions)
    actions: {
    	authenticateUser: (context, payload) => {
        	if (!context.state.loggedIn) {
        		window.setTimeout(() => {
                	context.commit('logIn')
                	context.commit('setName', payload)
                }, 500)
            }
        },
    },
    
}


const store = new Vuex.Store({
    state: {
        greeting: 'Welcome ...',
    },
    mutations: {
        updateGreeting: (state, payload) => {
        	state.greeting = payload.message
        },
    },
    modules: {
    	user: userModule,
    },
})


Vue.component('vuex-demo', {
	data () {
    	return {
        	userFirstName: '',
        	userSurname: '',
        }
    },
	computed: {
    
        loggedInState () {
        	// access a modules state
            return this.$store.state.user.loggedIn
        },
        
        ...Vuex.mapState([
        	'greeting',
        ]),
        
        // access modules state (not global so prepend the module name)
        ...Vuex.mapState({
        	firstName: state => state.user.firstName,
        	surname: state => state.user.surname,
        }),
        
        ...Vuex.mapGetters([
        	'fullName',
        ]),
        
        ...Vuex.mapGetters({
        	welcomeMessage: 'userGreeting',
        }),
        
    },
    methods: {
    
    	logInUser () {
        	
            this.authenticateUser({
            	firstName: this.userFirstName,
            	surname: this.userSurname,
            })
        	
        },
    
    	// pass an array to reference the vuex store methods
        ...Vuex.mapMutations([
        	'updateGreeting',
        ]),
        
        // pass an object to rename
        ...Vuex.mapActions([
        	'authenticateUser',
        ]),
        
    }
})


const app = new Vue({
    el: '#app',
    store,
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>

<div id="app">

    <!-- inlining the template to make things easier to read - all of below is still held on the component not the root -->
    <vuex-demo inline-template>
        <div>
            
            <div v-if="loggedInState === false">
                <h1>{{ greeting }}</h1>
                <div>
                    <p><label>first name: </label><input type="text" v-model="userFirstName"></p>
                    <p><label>surname: </label><input type="text" v-model="userSurname"></p>
                    <button :disabled="!userFirstName || !userSurname" @click="logInUser">sign in</button>
                </div>
            </div>
            
            <div v-else>
                <h1>{{ welcomeMessage }}</h1>
                <p>your name is: {{ fullName }}</p>
                <p>your firstName is: {{ firstName }}</p>
                <p>your surname is: {{ surname }}</p>
                <div>
                    <label>Update your greeting:</label>
                    <input type="text" @input="updateGreeting({ message: $event.target.value })">
                </div>
            </div>
        
        </div>        
    </vuex-demo>
    
</div>

As you can see if you wanted to pull in mutations or actions this would be done in a similar way but in your methods using mapMutations or mapActions


Adding Mixins

To extend the above behaviour you could couple this with mixins and then you'd only have to set up the above computed properties once and pull in the mixin on the component that needs them:

animals.js (mixin file)

import { mapState, mapGetters } from 'vuex'

export default {

    computed: {

       ...mapState([
           'donkey',
           ...

your component

import animalsMixin from './mixins/animals.js'

export default {

    mixins: [
        animalsMixin,
    ],

    created () {

        this.isDonkeyAnAss = this.donkey === this.ass
        ...
like image 62
GuyC Avatar answered Sep 22 '22 07:09

GuyC