galvez / vue-stator

Vuex alternative based on Vue.observable()
165 stars 3 forks source link
nuxt vue vuejs vuex

vue-stator

npm version npm downloads Circle CI Codecov

A lightweight, nearly drop-in replacement for Vuex.

npm i vue-stator --save

See documentation for Vue.js or Nuxt.js.

_Stator is the stationary part of the rotary system_ - thanks to @pimlie for the awesome name suggestion.

No more mutations

vue-stator uses Vue.observable() under the hood, which means assignments and Array changes will automatically trigger updates. So instead of actions and mutations, vue-stator only lets you define actions. To make transition from Vuex easy, it introduces a new function signature for actions that help making them look like mutations if needed.

Say you have a store/actions.js file defining global actions:

export function myAction ({ state, actions }, param) {
  state.param = param // mutates!
  actions.otherAction()
}

export function otherAction ({ state }, param) {
  state.paramDoubled = param * 2 // mutates!
}

The first argument is the local module context, the store object itself (and root state/getters/actions) is accessible from the this value within the action. This means that if you need to access the global $state, $actions or $getters you can access them using eg this.$state

Unified state and modularization

vue-stator introduces the possibility of having a unified state object. Instead of shuffling across subdirectories looking for different state definitions, you could now have a single place to look at: store/state.js.

Still, the ability to group actions and getters by a key is convenient. vue-stator fully supports module syntax which can define their own state, getters and/or actions.

In practice, this just means you can define module actions where the first argument is a context object containing:

import { plugin as VueStator } from 'vue-stator'

Vue.use(VueStator, {
  state: () => ({
    rootKey: 'a',
    auth: {
      user: null,
      loggedIn: false
    }
  }),
  modules: {
    auth: {
      actions: {
        login ({ state }, user) {
          state.user = user
          state.loggedIn = false

          this.$state.rootKey = 'b'
        }
      }
    }
  }
}

Again notice how state is a direct reference to $state.auth, to recap:

In Nuxt.js if you need to access properties from the Nuxt content (e.g. $axios or $http), then you need to provide the inject module option in your nuxt.config. See our Nuxt.js docs for more information

Beware: in Vuex, dispatching actions will always return a Promise.

In vue-stator, that's optional. If you have code that expects an action to return a Promise (by following it with .then(), for instance), make sure to return Promise.resolve() instead. Or, you can also simply switch to async/await syntax and it will work just fine.

Vue Devtools

vue-stator also has vue devtools integration. You can pass devtools: false as store option if you want to disable this. By default the value of Vue.config.devtools is used (which is false in production)

Due to differences between Vuex and vue-stator there are some remarks:

Vuex-like helpers

import { mapState, mapActions, mapGetters } from 'vue-stator'

vue-stator packs mapState, mapActions and mapGetters, which accept a dictionary of method mappings, eg (['method', 'module/namespace/method'])

You have access to everything directly in Vue's context though.

That is, you can just reference $state.something in your Vue template and it'll work.

statorMap component option

You can enable a mixin helper by passing { mixin: true } to your plugin options. Instead of calling mapState etc in your components you can now just add a statorMap option in your component:

// App.vue
Vue.use(VueStator, { mixin: true })

// my-component.vue
<script>
export default {
  statorMap: {
    // omitting the namespace is the default behaviour
    // set this to false if you dont want to omit the namespace,
    // then the properties will be available as: this['my/module/nonAliasedKey']
    //omitNamespace: true,
    state: {
      my: {
        module: {
          nonAliasedKey: true,
          aliasedKey: 'theModuleKey'
        }
    },
    getters: [
      'rootGetter',
      'my/module/myGetter'
    ],
    actions: ['myAction']
  },
  mounted () {
    this.nonAliasedKey
    this.aliasedKey
    this.rootGetter
    this.myGetter
    this.myAction()
  }
}
</script>

You can also use a function for statorMap in case you need to eg map different state for eg Server-side or Client-side

  statorMap() {
    if (process.client) {
      return { ... }
    }
    return { ... }
  }

Runtime helpers

vue-stator also provides some helper methods to interact with the store more easily.

  beforeCreate () {
    this.$stator.registerModule({
      name: 'my/module',
      state () {
        return {
          key: true
        }
      }
    })

    // or

    this.$stator.registerModule({
      state () {
        return {
          key: true
        }
      }
    }, 'my/module')
  },
  mounted () {
    this.$state.my.module.key // true
  },
  destroyed () {
    this.$stator.unregisterModule('my/module')
  }

Getters

In a further effort to make transition from Vuex, modularized getters are available in a similar fashion. The arguments passed to getter functions have the exact same signature as Vuex.

import { plugin as VueStator } from 'vue-stator'

Vue.use(VueStator, {
  state: () => ({
    user: {
      firstName: 'John',
      lastName: 'Doe'
    },
  }),
  modules: {
    user: {
      getters: {
        isFirstNameValid: state => state.firstName.length > 0,
        isLastNameValid: state => state.lastName.length > 0,
        isValid: (_, getters) => getters.isFirstNameValid && getters.isLastNameValid,
        fullName (state, getters, rootState, rootGetters) {
          if (getters.isValid) {
            return `${state.firstName} ${state.lastName}`
          }

          /* below is for example purposes only, you dont need to use the root keys here */
          if (rootGetters.user.isFirstNameValid) {
            return rootState.user.firstName
          }

          return state.lastName
        }
      }
    }
  }
}

Storage helpers

If you need your state to always be comitted to a storage provider, vue-stator provides a configuration option which will automatically store and restore that state on changes

const statorConfiguration = {
  storage: {
    provider: {
      getItem (key) {
        // do something
      },
      setItem (key, value) {
        // do something
      }
    },
    namespaces: [
      'key',
      'module/key'
    ]
  }
}

Or if you need to use multiple storage providers

const statorConfiguration = {
  storage: [
    { // object syntax
      provider () {
        if (process.client) {
          return window.localStorage
        }
      },
      namespaces: [
        'persistent-key',
        'my/module/key'
      ]
    },
    [ // array syntax
      window.sessionStorage, // this will probably fail on SSR, see object syntax
      [ 'temp-key' ]
    ]
  ]
}

Nuxt.js module

Nuxt v2.10+ is required unless you set the module option baseDir to something different then store

Using vue-stator with Nuxt.js is as easy as using it with Vuex: a store will be automatically created by loading files placed in a conventional locations, such as store/state.js.

See full documentation for the Nuxt.js module here.